From 62c484e590e39774ea880dc713966bcd6c0a7501 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Fri, 21 Feb 2025 11:59:40 -0500 Subject: [PATCH] Handle the possibility that dynamic rules have more dynamic rules as children --- crates/compute_graph/src/builder.rs | 6 +-- crates/compute_graph/src/input.rs | 24 +++++++++++ crates/compute_graph/src/lib.rs | 21 ++++++++-- crates/compute_graph/src/node.rs | 65 +++++++++++++++++++++++++++-- crates/compute_graph/src/rule.rs | 25 ++++++++++- src/generator/css/character_sets.rs | 5 +++ src/generator/posts.rs | 18 ++++++++ src/generator/rss.rs | 8 ++++ src/generator/static_files.rs | 5 +++ src/generator/tags.rs | 13 ++++++ src/generator/tutorials.rs | 5 +++ src/generator/tv.rs | 11 +++++ 12 files changed, 194 insertions(+), 12 deletions(-) diff --git a/crates/compute_graph/src/builder.rs b/crates/compute_graph/src/builder.rs index dd45c98..33c3c8e 100644 --- a/crates/compute_graph/src/builder.rs +++ b/crates/compute_graph/src/builder.rs @@ -107,7 +107,7 @@ impl GraphBuilder { /// the value of the node can be replaced, invalidating the node in the process. /// /// ```rust - /// # use compute_graph::{builder::GraphBuilder, rule::{Rule, Input, InputVisitable}}; + /// # use compute_graph::{builder::GraphBuilder, rule::Rule, input::{Input, InputVisitable}}; /// let mut builder = GraphBuilder::new(); /// let (input, signal) = builder.add_invalidatable_value(0); /// # #[derive(InputVisitable)] @@ -152,10 +152,10 @@ impl GraphBuilder { /// as well as an [`InvalidationSignal`] which can be used to indicate that the node has been invalidated. /// /// ```rust - /// # use compute_graph::{builder::GraphBuilder, rule::{Rule, Input, InputVisitable}}; + /// # use compute_graph::{builder::GraphBuilder, rule::Rule, input::{Input, InputVisitable}}; /// let mut builder = GraphBuilder::new(); /// # #[derive(InputVisitable)] - /// # struct IncrementAfterEvaluate(i32); + /// # struct IncrementAfterEvaluate(#[skip_visit] i32); /// # impl Rule for IncrementAfterEvaluate { /// # type Output = i32; /// # fn evaluate(&mut self) -> i32 { diff --git a/crates/compute_graph/src/input.rs b/crates/compute_graph/src/input.rs index 54ae3f6..fd518db 100644 --- a/crates/compute_graph/src/input.rs +++ b/crates/compute_graph/src/input.rs @@ -33,6 +33,13 @@ impl Input { pub fn node_id(&self) -> NodeId { self.node_idx.get().unwrap() } + + /// Converts this input to a type-erased input. + pub fn as_any_input(&self) -> AnyInput { + AnyInput { + node_idx: Rc::clone(&self.node_idx), + } + } } // can't derive this impl because it incorectly adds the bound T: Clone @@ -73,6 +80,23 @@ impl DynamicInput { pub fn value(&self) -> impl Deref> + '_ { self.input.value() } + + /// Converts this input to a type-erased input. + pub fn as_any_input(&self) -> AnyInput { + self.input.as_any_input() + } +} + +/// A type erased [`Input`]. +pub struct AnyInput { + node_idx: Rc>>, +} + +impl AnyInput { + /// Get the ID of the node that this input represents. + pub fn node_id(&self) -> NodeId { + self.node_idx.get().unwrap() + } } // TODO: i really want Input to be able to implement Deref somehow diff --git a/crates/compute_graph/src/lib.rs b/crates/compute_graph/src/lib.rs index f195e87..8b8ef16 100644 --- a/crates/compute_graph/src/lib.rs +++ b/crates/compute_graph/src/lib.rs @@ -7,7 +7,7 @@ //! dependencies. For example, an arithmetic operation can be implemented like so: //! //! ```rust -//! # use compute_graph::{builder::GraphBuilder, rule::{Rule, Input, InputVisitable}}; +//! # use compute_graph::{builder::GraphBuilder, rule::Rule, input::{Input, InputVisitable}}; //! let mut builder = GraphBuilder::new(); //! let a = builder.add_value(1); //! let b = builder.add_value(2); @@ -31,7 +31,7 @@ //! The `Add` rule is implemented as follows: //! //! ```rust -//! # use compute_graph::{builder::GraphBuilder, rule::{Rule, Input, InputVisitable}}; +//! # use compute_graph::{builder::GraphBuilder, rule::Rule, input::{Input, InputVisitable}}; //! #[derive(InputVisitable)] //! struct Add(Input, Input); //! @@ -265,15 +265,17 @@ impl Graph { fn process_update_step<'a>( &'a mut self, current_idx: NodeId, - ctx: NodeUpdateContext, + mut ctx: NodeUpdateContext, ) -> UpdateStepResult { let mut graph = self.node_graph.borrow_mut(); let mut nodes_changed = false; - for idx_to_remove in ctx.removed_nodes { + while let Some(idx_to_remove) = ctx.removed_nodes.pop_front() { assert!( idx_to_remove != current_idx, "cannot remove node curently being evaluated" ); + let node = &graph[idx_to_remove]; + node.remove_children(&mut ctx); let (index_to_remove_in_sorted, _) = self .sorted_nodes .iter() @@ -867,6 +869,9 @@ mod tests { } self.node_factory.all_nodes(ctx) } + fn remove_children(&self, ctx: &mut impl rule::DynamicRuleContext) { + self.node_factory.remove_all(ctx); + } } let all_inputs = builder.add_dynamic_rule(CountUpTo { count, @@ -908,6 +913,11 @@ mod tests { } self.1.clone() } + fn remove_children(&self, ctx: &mut impl rule::DynamicRuleContext) { + for input in self.1.iter() { + ctx.remove_node(input.node_id()); + } + } } let all_inputs = builder.add_async_dynamic_rule(CountUpTo(count, vec![])); builder.set_output(DynamicSum(all_inputs)); @@ -951,6 +961,9 @@ mod tests { } self.node_factory.all_nodes(ctx) } + fn remove_children(&self, ctx: &mut impl rule::DynamicRuleContext) { + self.node_factory.remove_all(ctx); + } } let signals = Rc::new(RefCell::new(vec![])); let all_inputs = builder.add_dynamic_rule(CountUpTo { diff --git a/crates/compute_graph/src/node.rs b/crates/compute_graph/src/node.rs index 8fd64b7..131113e 100644 --- a/crates/compute_graph/src/node.rs +++ b/crates/compute_graph/src/node.rs @@ -7,6 +7,7 @@ use crate::{Graph, Input, InputVisitor, InvalidationSignal, NodeGraph, NodeId, S use quote::ToTokens; use std::any::Any; use std::cell::{Cell, RefCell}; +use std::collections::VecDeque; use std::future::Future; use std::rc::Rc; @@ -21,6 +22,7 @@ pub(crate) struct ErasedNode { &'a mut NodeUpdateContext, ) -> Synch::UpdateResult<'a>, >, + remove_children: Box Fn(&Box, &'a mut NodeUpdateContext)>, debug_fmt: Box, &mut std::fmt::Formatter<'_>) -> std::fmt::Result>, } @@ -28,7 +30,7 @@ pub(crate) struct NodeUpdateContext { pub(crate) graph: Rc>>, pub(crate) graph_is_valid: Rc>, pub(crate) invalidate_dependent_nodes: bool, - pub(crate) removed_nodes: Vec, + pub(crate) removed_nodes: VecDeque, pub(crate) added_nodes: Vec<(ErasedNode, Rc>>)>, } @@ -38,7 +40,7 @@ impl NodeUpdateContext { graph: Rc::clone(&graph.node_graph), graph_is_valid: Rc::clone(&graph.is_valid), invalidate_dependent_nodes: false, - removed_nodes: vec![], + removed_nodes: VecDeque::new(), added_nodes: vec![], } } @@ -71,6 +73,10 @@ impl ErasedNode { let x = any.downcast_mut::>>().unwrap(); x.update(ctx) }), + remove_children: Box::new(|any, ctx| { + let x = any.downcast_ref::>>().unwrap(); + x.remove_children(ctx); + }), debug_fmt: Box::new(|any, f| { let x = any.downcast_ref::>>().unwrap(); x.fmt(f) @@ -87,6 +93,9 @@ impl ErasedNode { pub(crate) fn visit_inputs(&self, f: &mut dyn FnMut(NodeId) -> ()) { (self.visit_inputs)(&self.any, f); } + pub(crate) fn remove_children(&self, ctx: &mut NodeUpdateContext) { + (self.remove_children)(&self.any, ctx); + } } impl ErasedNode { @@ -112,6 +121,7 @@ pub(crate) trait Node: std::fmt::Debug { fn invalidate(&mut self); fn visit_inputs(&self, visitor: &mut dyn FnMut(NodeId) -> ()); fn update<'a>(&'a mut self, ctx: &'a mut NodeUpdateContext) -> Synch::UpdateResult<'a>; + fn remove_children<'a>(&'a self, ctx: &'a mut NodeUpdateContext); fn value_rc(&self) -> &Rc>>; } @@ -178,6 +188,8 @@ impl Node for ConstNode { unreachable!() } + fn remove_children<'a>(&'a self, _ctx: &'a mut NodeUpdateContext) {} + fn value_rc(&self) -> &Rc>> { &self.value } @@ -228,6 +240,8 @@ impl Node for InvalidatableConstNode S::make_update_result(crate::synchronicity::private::Token) } + fn remove_children<'a>(&'a self, _ctx: &'a mut NodeUpdateContext) {} + fn value_rc(&self) -> &Rc>> { &self.value } @@ -297,6 +311,8 @@ impl Node for RuleNode S::make_update_result(crate::synchronicity::private::Token) } + fn remove_children<'a>(&'a self, _ctx: &'a mut NodeUpdateContext) {} + fn value_rc(&self) -> &Rc>> { &self.value } @@ -364,6 +380,8 @@ impl F, F: Future> Node(&'a self, _ctx: &'a mut NodeUpdateContext) {} + fn value_rc(&self) -> &Rc>> { &self.value } @@ -426,6 +444,8 @@ impl Node for AsyncRuleNode Box::pin(self.do_update(ctx)) } + fn remove_children<'a>(&'a self, _ctx: &'a mut NodeUpdateContext) {} + fn value_rc(&self) -> &Rc>> { &self.value } @@ -526,6 +546,11 @@ impl Node, S S::make_update_result(crate::synchronicity::private::Token) } + fn remove_children<'a>(&'a self, ctx: &'a mut NodeUpdateContext) { + self.rule + .remove_children(&mut DynamicRuleRemoveChildrenContext(ctx)); + } + fn value_rc(&self) -> &Rc>>> { &self.value } @@ -545,7 +570,7 @@ impl<'a, S: Synchronicity> DynamicRuleUpdateContext<'a, S> { impl<'a, S: Synchronicity> DynamicRuleContext for DynamicRuleUpdateContext<'a, S> { fn remove_node(&mut self, id: NodeId) { - self.0.removed_nodes.push(id); + self.0.removed_nodes.push_back(id); } fn add_rule(&mut self, rule: R) -> Input @@ -576,6 +601,35 @@ impl<'a, S: Synchronicity> DynamicRuleContext for DynamicRuleUpdateContext<'a, S } } +struct DynamicRuleRemoveChildrenContext<'a, Synch: Synchronicity>(&'a mut NodeUpdateContext); + +impl<'a, S: Synchronicity> DynamicRuleContext for DynamicRuleRemoveChildrenContext<'a, S> { + fn remove_node(&mut self, id: NodeId) { + self.0.removed_nodes.push_back(id); + } + + fn add_rule(&mut self, _rule: R) -> Input + where + R: Rule, + { + panic!("cannot add node while removing children"); + } + + fn add_dynamic_rule(&mut self, _rule: R) -> Input> + where + R: DynamicRule, + { + panic!("cannot add node while removing children"); + } + + fn add_invalidatable_rule(&mut self, _rule: R) -> (Input, InvalidationSignal) + where + R: Rule, + { + panic!("cannot add node while removing children"); + } +} + struct DynamicRuleLabel<'a, R: DynamicRule>(&'a R); impl<'a, R: DynamicRule> std::fmt::Display for DynamicRuleLabel<'a, R> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -652,6 +706,11 @@ impl Node, Asynchronous> Box::pin(self.do_update(ctx)) } + fn remove_children<'a>(&'a self, ctx: &'a mut NodeUpdateContext) { + self.rule + .remove_children(&mut DynamicRuleRemoveChildrenContext(ctx)); + } + fn value_rc(&self) -> &Rc>>> { &self.value } diff --git a/crates/compute_graph/src/rule.rs b/crates/compute_graph/src/rule.rs index 3d50ebf..e52d471 100644 --- a/crates/compute_graph/src/rule.rs +++ b/crates/compute_graph/src/rule.rs @@ -10,7 +10,7 @@ use std::hash::Hash; /// A rule for addition could be implemented like so: /// /// ```rust -/// # use compute_graph::rule::{Rule, Input, InputVisitable}; +/// # use compute_graph::{rule::Rule, input::{Input, InputVisitable}}; /// #[derive(InputVisitable)] /// struct Add(Input, Input); /// @@ -47,7 +47,7 @@ pub trait Rule: InputVisitable + 'static { /// A rule produces a value for a graph node asynchronously. /// /// ```rust -/// # use compute_graph::rule::{AsyncRule, Input, InputVisitable}; +/// # use compute_graph::{rule::AsyncRule, input::{Input, InputVisitable}}; /// # async fn do_async_work(_: i32) -> i32 { 0 } /// #[derive(InputVisitable)] /// struct AsyncMath(Input); @@ -91,6 +91,12 @@ pub trait DynamicRule: InputVisitable + 'static { /// the specific additions/removals. fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec>; + /// Removes any children of this rule that have been added to the graph. + /// + /// Called before this rule is removed, if this dynamic rule itself was the child of another + /// dynamic rule that is now removing it. + fn remove_children(&self, ctx: &mut impl DynamicRuleContext); + #[allow(unused_variables)] fn node_label(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { Ok(()) @@ -214,6 +220,15 @@ impl DynamicNodeFactory { self.finalize_nodes(ctx); self.existing_nodes.values().cloned().collect() } + + /// Removes all added child nodes _without resetting the factory_. + /// + /// This method should generally only be called from [`DynamicRule::remove_children`]. + pub fn remove_all(&self, ctx: &mut impl DynamicRuleContext) { + for (_, input) in self.existing_nodes.iter() { + ctx.remove_node(input.node_id()); + } + } } /// An asynchronous rule whose output is further nodes in the graph. @@ -233,6 +248,12 @@ pub trait AsyncDynamicRule: InputVisitable + 'static { ctx: &'a mut impl AsyncDynamicRuleContext, ) -> impl Future>> + 'a; + /// Removes any children of this rule that have been added to the graph. + /// + /// Called before this rule is removed, if this dynamic rule itself was the child of another + /// dynamic rule that is now removing it. + fn remove_children(&self, ctx: &mut impl DynamicRuleContext); + #[allow(unused_variables)] fn node_label(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { Ok(()) diff --git a/src/generator/css/character_sets.rs b/src/generator/css/character_sets.rs index f92091f..a183ba4 100644 --- a/src/generator/css/character_sets.rs +++ b/src/generator/css/character_sets.rs @@ -61,6 +61,7 @@ impl InputVisitable for MakeBuildDynamicCharacterSets { } impl DynamicRule for MakeBuildDynamicCharacterSets { type ChildOutput = CharacterSets; + fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec> { for dynamic_input in self.strings.iter() { for input in dynamic_input.value().inputs.iter() { @@ -71,6 +72,10 @@ impl DynamicRule for MakeBuildDynamicCharacterSets { } self.factory.all_nodes(ctx) } + + fn remove_children(&self, ctx: &mut impl DynamicRuleContext) { + self.factory.remove_all(ctx); + } } struct UnionCharacterSets(Vec>); diff --git a/src/generator/posts.rs b/src/generator/posts.rs index effb886..a86d1e1 100644 --- a/src/generator/posts.rs +++ b/src/generator/posts.rs @@ -107,6 +107,7 @@ impl MakeReadNodes { } impl DynamicRule for MakeReadNodes { type ChildOutput = ReadPostOutput; + fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec> { for file in self.files.value().iter() { self.node_factory.add_node(ctx, file.clone(), |ctx| { @@ -119,6 +120,10 @@ impl DynamicRule for MakeReadNodes { } self.node_factory.all_nodes(ctx) } + + fn remove_children(&self, ctx: &mut impl DynamicRuleContext) { + self.node_factory.remove_all(ctx); + } } pub type ReadPostOutput = Option>; @@ -171,6 +176,7 @@ impl MakeConvertToHTML { } impl DynamicRule for MakeConvertToHTML { type ChildOutput = Option>; + fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec> { for post_input in self.posts.value().inputs.iter() { self.factory.add_node(ctx, post_input.node_id(), |ctx| { @@ -179,6 +185,10 @@ impl DynamicRule for MakeConvertToHTML { } self.factory.all_nodes(ctx) } + + fn remove_children(&self, ctx: &mut impl DynamicRuleContext) { + self.factory.remove_all(ctx); + } } #[derive(InputVisitable)] @@ -272,6 +282,7 @@ impl MakeWritePosts { } impl DynamicRule for MakeWritePosts { type ChildOutput = String; + fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec> { for post_input in self.posts.value().inputs.iter() { if post_input.value().is_some() { @@ -323,6 +334,13 @@ impl DynamicRule for MakeWritePosts { self.build_context_factory.finalize_nodes(ctx); self.render_factory.all_nodes(ctx) } + + fn remove_children(&self, ctx: &mut impl DynamicRuleContext) { + self.permalink_factory.remove_all(ctx); + self.output_path_factory.remove_all(ctx); + self.build_context_factory.remove_all(ctx); + self.render_factory.remove_all(ctx); + } } #[derive(InputVisitable)] diff --git a/src/generator/rss.rs b/src/generator/rss.rs index 3cf12cc..3748640 100644 --- a/src/generator/rss.rs +++ b/src/generator/rss.rs @@ -37,6 +37,7 @@ impl RecentPosts { } impl DynamicRule for RecentPosts { type ChildOutput = ReadPostOutput; + fn evaluate(&mut self, _ctx: &mut impl DynamicRuleContext) -> Vec> { let mut posts = vec![]; for post_input in self.posts().inputs.iter() { @@ -52,6 +53,8 @@ impl DynamicRule for RecentPosts { .take(10) .collect() } + + fn remove_children(&self, _ctx: &mut impl DynamicRuleContext) {} } #[derive(InputVisitable)] @@ -69,6 +72,7 @@ impl ConvertRecentsToRSSContent { } impl DynamicRule for ConvertRecentsToRSSContent { type ChildOutput = Option>; + fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec> { for post_input in self.posts.value().inputs.iter() { self.factory.add_node(ctx, post_input.node_id(), |ctx| { @@ -77,6 +81,10 @@ impl DynamicRule for ConvertRecentsToRSSContent { } self.factory.all_nodes(ctx) } + + fn remove_children(&self, ctx: &mut impl DynamicRuleContext) { + self.factory.remove_all(ctx); + } } #[derive(InputVisitable)] diff --git a/src/generator/static_files.rs b/src/generator/static_files.rs index f4c5c8a..8932d28 100644 --- a/src/generator/static_files.rs +++ b/src/generator/static_files.rs @@ -45,6 +45,7 @@ impl ReadStatics { } impl DynamicRule for ReadStatics { type ChildOutput = (); + fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec> { let files = read_files_recursive(content_path("static")).expect("reading static directory"); for ent in files { @@ -62,6 +63,10 @@ impl DynamicRule for ReadStatics { } self.factory.all_nodes(ctx) } + + fn remove_children(&self, ctx: &mut impl DynamicRuleContext) { + self.factory.remove_all(ctx); + } } #[derive(InputVisitable)] diff --git a/src/generator/tags.rs b/src/generator/tags.rs index 50eab42..ce9c39d 100644 --- a/src/generator/tags.rs +++ b/src/generator/tags.rs @@ -57,6 +57,7 @@ impl MakePostsByTags { } impl DynamicRule for MakePostsByTags { type ChildOutput = TagAndPosts; + fn evaluate( &mut self, ctx: &mut impl compute_graph::rule::DynamicRuleContext, @@ -100,6 +101,12 @@ impl DynamicRule for MakePostsByTags { self.posts_by_year_factory.finalize_nodes(ctx); self.tag_and_posts_factory.all_nodes(ctx) } + + fn remove_children(&self, ctx: &mut impl compute_graph::rule::DynamicRuleContext) { + self.posts_by_tag_factory.remove_all(ctx); + self.posts_by_year_factory.remove_all(ctx); + self.tag_and_posts_factory.remove_all(ctx); + } } #[derive(InputVisitable)] @@ -183,6 +190,7 @@ impl MakeWriteTagPages { } impl DynamicRule for MakeWriteTagPages { type ChildOutput = String; + fn evaluate( &mut self, ctx: &mut impl compute_graph::rule::DynamicRuleContext, @@ -216,4 +224,9 @@ impl DynamicRule for MakeWriteTagPages { self.build_context_factory.finalize_nodes(ctx); self.render_factory.all_nodes(ctx) } + + fn remove_children(&self, ctx: &mut impl compute_graph::rule::DynamicRuleContext) { + self.build_context_factory.remove_all(ctx); + self.render_factory.remove_all(ctx); + } } diff --git a/src/generator/tutorials.rs b/src/generator/tutorials.rs index 8ec2f91..76746d5 100644 --- a/src/generator/tutorials.rs +++ b/src/generator/tutorials.rs @@ -201,6 +201,7 @@ impl MakeRenderTutorials { } impl DynamicRule for MakeRenderTutorials { type ChildOutput = String; + fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec> { assert!(!self.evaluated); self.evaluated = true; @@ -221,6 +222,10 @@ impl DynamicRule for MakeRenderTutorials { } self.render_factory.all_nodes(ctx) } + + fn remove_children(&self, ctx: &mut impl DynamicRuleContext) { + self.render_factory.remove_all(ctx); + } } #[derive(Serialize)] diff --git a/src/generator/tv.rs b/src/generator/tv.rs index 850409a..f1ab787 100644 --- a/src/generator/tv.rs +++ b/src/generator/tv.rs @@ -113,6 +113,7 @@ impl MakeReadShows { } impl DynamicRule for MakeReadShows { type ChildOutput = Option; + fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec> { let result = self.read_shows(ctx); if let Err(e) = result { @@ -120,6 +121,10 @@ impl DynamicRule for MakeReadShows { } self.factory.all_nodes(ctx) } + + fn remove_children(&self, ctx: &mut impl DynamicRuleContext) { + self.factory.remove_all(ctx); + } } #[derive(InputVisitable)] @@ -282,6 +287,7 @@ impl MakeRenderShows { } impl DynamicRule for MakeRenderShows { type ChildOutput = String; + fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec> { for show_input in self.shows.value().inputs.iter() { if let Some(show) = show_input.value().as_ref() { @@ -312,6 +318,11 @@ impl DynamicRule for MakeRenderShows { self.build_context_factory.finalize_nodes(ctx); self.render_factory.all_nodes(ctx) } + + fn remove_children(&self, ctx: &mut impl DynamicRuleContext) { + self.build_context_factory.remove_all(ctx); + self.render_factory.remove_all(ctx); + } } #[derive(InputVisitable)]