Handle the possibility that dynamic rules have more dynamic rules as children

This commit is contained in:
Shadowfacts 2025-02-21 11:59:40 -05:00
parent c5346bc15f
commit 62c484e590
12 changed files with 194 additions and 12 deletions

View File

@ -107,7 +107,7 @@ impl<O: 'static, S: Synchronicity> GraphBuilder<O, S> {
/// 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<O: 'static, S: Synchronicity> GraphBuilder<O, S> {
/// 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 {

View File

@ -33,6 +33,13 @@ impl<T> Input<T> {
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<T> DynamicInput<T> {
pub fn value(&self) -> impl Deref<Target = DynamicRuleOutput<T>> + '_ {
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<Cell<Option<NodeId>>>,
}
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

View File

@ -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<i32>, Input<i32>);
//!
@ -265,15 +265,17 @@ impl<O: 'static, S: Synchronicity> Graph<O, S> {
fn process_update_step<'a>(
&'a mut self,
current_idx: NodeId,
ctx: NodeUpdateContext<S>,
mut ctx: NodeUpdateContext<S>,
) -> 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 {

View File

@ -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<Synch: Synchronicity> {
&'a mut NodeUpdateContext<Synch>,
) -> Synch::UpdateResult<'a>,
>,
remove_children: Box<dyn for<'a> Fn(&Box<dyn Any>, &'a mut NodeUpdateContext<Synch>)>,
debug_fmt: Box<dyn Fn(&Box<dyn Any>, &mut std::fmt::Formatter<'_>) -> std::fmt::Result>,
}
@ -28,7 +30,7 @@ pub(crate) struct NodeUpdateContext<Synch: Synchronicity> {
pub(crate) graph: Rc<RefCell<NodeGraph<Synch>>>,
pub(crate) graph_is_valid: Rc<Cell<bool>>,
pub(crate) invalidate_dependent_nodes: bool,
pub(crate) removed_nodes: Vec<NodeId>,
pub(crate) removed_nodes: VecDeque<NodeId>,
pub(crate) added_nodes: Vec<(ErasedNode<Synch>, Rc<Cell<Option<NodeId>>>)>,
}
@ -38,7 +40,7 @@ impl<S: Synchronicity> NodeUpdateContext<S> {
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<S: Synchronicity> ErasedNode<S> {
let x = any.downcast_mut::<Box<dyn Node<V, S>>>().unwrap();
x.update(ctx)
}),
remove_children: Box::new(|any, ctx| {
let x = any.downcast_ref::<Box<dyn Node<V, S>>>().unwrap();
x.remove_children(ctx);
}),
debug_fmt: Box::new(|any, f| {
let x = any.downcast_ref::<Box<dyn Node<V, S>>>().unwrap();
x.fmt(f)
@ -87,6 +93,9 @@ impl<S: Synchronicity> ErasedNode<S> {
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<S>) {
(self.remove_children)(&self.any, ctx);
}
}
impl ErasedNode<Synchronous> {
@ -112,6 +121,7 @@ pub(crate) trait Node<Value: NodeValue, Synch: Synchronicity>: 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>) -> Synch::UpdateResult<'a>;
fn remove_children<'a>(&'a self, ctx: &'a mut NodeUpdateContext<Synch>);
fn value_rc(&self) -> &Rc<RefCell<Option<Value>>>;
}
@ -178,6 +188,8 @@ impl<V: NodeValue, S: Synchronicity> Node<V, S> for ConstNode<V, S> {
unreachable!()
}
fn remove_children<'a>(&'a self, _ctx: &'a mut NodeUpdateContext<S>) {}
fn value_rc(&self) -> &Rc<RefCell<Option<V>>> {
&self.value
}
@ -228,6 +240,8 @@ impl<V: NodeValue, S: Synchronicity> Node<V, S> for InvalidatableConstNode<V, S>
S::make_update_result(crate::synchronicity::private::Token)
}
fn remove_children<'a>(&'a self, _ctx: &'a mut NodeUpdateContext<S>) {}
fn value_rc(&self) -> &Rc<RefCell<Option<V>>> {
&self.value
}
@ -297,6 +311,8 @@ impl<R: Rule, S: Synchronicity> Node<R::Output, S> for RuleNode<R, R::Output, S>
S::make_update_result(crate::synchronicity::private::Token)
}
fn remove_children<'a>(&'a self, _ctx: &'a mut NodeUpdateContext<S>) {}
fn value_rc(&self) -> &Rc<RefCell<Option<R::Output>>> {
&self.value
}
@ -364,6 +380,8 @@ impl<V: NodeValue, P: FnOnce() -> F, F: Future<Output = V>> Node<V, Asynchronous
Box::pin(self.do_update(ctx))
}
fn remove_children<'a>(&'a self, _ctx: &'a mut NodeUpdateContext<Asynchronous>) {}
fn value_rc(&self) -> &Rc<RefCell<Option<V>>> {
&self.value
}
@ -426,6 +444,8 @@ impl<R: AsyncRule> Node<R::Output, Asynchronous> for AsyncRuleNode<R, R::Output>
Box::pin(self.do_update(ctx))
}
fn remove_children<'a>(&'a self, _ctx: &'a mut NodeUpdateContext<Asynchronous>) {}
fn value_rc(&self) -> &Rc<RefCell<Option<R::Output>>> {
&self.value
}
@ -526,6 +546,11 @@ impl<R: DynamicRule, S: Synchronicity> Node<DynamicRuleOutput<R::ChildOutput>, S
S::make_update_result(crate::synchronicity::private::Token)
}
fn remove_children<'a>(&'a self, ctx: &'a mut NodeUpdateContext<S>) {
self.rule
.remove_children(&mut DynamicRuleRemoveChildrenContext(ctx));
}
fn value_rc(&self) -> &Rc<RefCell<Option<DynamicRuleOutput<R::ChildOutput>>>> {
&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<R>(&mut self, rule: R) -> Input<R::Output>
@ -576,6 +601,35 @@ impl<'a, S: Synchronicity> DynamicRuleContext for DynamicRuleUpdateContext<'a, S
}
}
struct DynamicRuleRemoveChildrenContext<'a, Synch: Synchronicity>(&'a mut NodeUpdateContext<Synch>);
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<R>(&mut self, _rule: R) -> Input<R::Output>
where
R: Rule,
{
panic!("cannot add node while removing children");
}
fn add_dynamic_rule<R>(&mut self, _rule: R) -> Input<DynamicRuleOutput<R::ChildOutput>>
where
R: DynamicRule,
{
panic!("cannot add node while removing children");
}
fn add_invalidatable_rule<R>(&mut self, _rule: R) -> (Input<R::Output>, 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<R: AsyncDynamicRule> Node<DynamicRuleOutput<R::ChildOutput>, Asynchronous>
Box::pin(self.do_update(ctx))
}
fn remove_children<'a>(&'a self, ctx: &'a mut NodeUpdateContext<Asynchronous>) {
self.rule
.remove_children(&mut DynamicRuleRemoveChildrenContext(ctx));
}
fn value_rc(&self) -> &Rc<RefCell<Option<DynamicRuleOutput<R::ChildOutput>>>> {
&self.value
}

View File

@ -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<i32>, Input<i32>);
///
@ -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<i32>);
@ -91,6 +91,12 @@ pub trait DynamicRule: InputVisitable + 'static {
/// the specific additions/removals.
fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec<Input<Self::ChildOutput>>;
/// 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<ID: Hash + Eq + Clone, ChildOutput> DynamicNodeFactory<ID, ChildOutput> {
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<Output = Vec<Input<Self::ChildOutput>>> + '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(())

View File

@ -61,6 +61,7 @@ impl InputVisitable for MakeBuildDynamicCharacterSets {
}
impl DynamicRule for MakeBuildDynamicCharacterSets {
type ChildOutput = CharacterSets;
fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec<Input<Self::ChildOutput>> {
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<Input<CharacterSets>>);

View File

@ -107,6 +107,7 @@ impl MakeReadNodes {
}
impl DynamicRule for MakeReadNodes {
type ChildOutput = ReadPostOutput;
fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec<Input<Self::ChildOutput>> {
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<Post<AnyContent>>;
@ -171,6 +176,7 @@ impl MakeConvertToHTML {
}
impl DynamicRule for MakeConvertToHTML {
type ChildOutput = Option<Post<HtmlContent>>;
fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec<Input<Self::ChildOutput>> {
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<Input<Self::ChildOutput>> {
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)]

View File

@ -37,6 +37,7 @@ impl RecentPosts {
}
impl DynamicRule for RecentPosts {
type ChildOutput = ReadPostOutput;
fn evaluate(&mut self, _ctx: &mut impl DynamicRuleContext) -> Vec<Input<Self::ChildOutput>> {
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<Post<HtmlContent>>;
fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec<Input<Self::ChildOutput>> {
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)]

View File

@ -45,6 +45,7 @@ impl ReadStatics {
}
impl DynamicRule for ReadStatics {
type ChildOutput = ();
fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec<Input<Self::ChildOutput>> {
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)]

View File

@ -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);
}
}

View File

@ -201,6 +201,7 @@ impl MakeRenderTutorials {
}
impl DynamicRule for MakeRenderTutorials {
type ChildOutput = String;
fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec<Input<Self::ChildOutput>> {
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)]

View File

@ -113,6 +113,7 @@ impl MakeReadShows {
}
impl DynamicRule for MakeReadShows {
type ChildOutput = Option<Show>;
fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec<Input<Self::ChildOutput>> {
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<Input<Self::ChildOutput>> {
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)]