Improve InputVisitable derive macro

This commit is contained in:
Shadowfacts 2025-01-05 00:07:13 -04:00
parent 55b91944b2
commit 1279a1755d
14 changed files with 399 additions and 257 deletions

View File

@ -1,8 +1,9 @@
use crate::input::{DynamicInput, Input};
use crate::node::{ use crate::node::{
AsyncConstNode, AsyncDynamicRuleNode, AsyncRuleNode, ConstNode, DynamicRuleNode, ErasedNode, AsyncConstNode, AsyncDynamicRuleNode, AsyncRuleNode, ConstNode, DynamicRuleNode, ErasedNode,
InvalidatableConstNode, Node, NodeValue, RuleNode, InvalidatableConstNode, Node, NodeValue, RuleNode,
}; };
use crate::rule::{AsyncDynamicRule, AsyncRule, DynamicInput, DynamicRule, Input, Rule}; use crate::rule::{AsyncDynamicRule, AsyncRule, DynamicRule, Rule};
use crate::synchronicity::{Asynchronous, Synchronicity, Synchronous}; use crate::synchronicity::{Asynchronous, Synchronicity, Synchronous};
use crate::util; use crate::util;
use crate::{Graph, InvalidationSignal, NodeGraph, NodeId, ValueInvalidationSignal}; use crate::{Graph, InvalidationSignal, NodeGraph, NodeId, ValueInvalidationSignal};

View File

@ -0,0 +1,137 @@
use std::{
cell::{Cell, Ref, RefCell},
ops::Deref,
rc::Rc,
};
use crate::{node::DynamicRuleOutput, rule::DynamicNodeFactory, NodeId};
pub use compute_graph_macros::InputVisitable;
/// A placeholder for the output of one node, to be used as an input for another.
///
/// To obtain an input, add a value or rule to a [`GraphBuilder`](`crate::builder::GraphBuilder`).
///
/// Note that this type implements `Clone`, so can be cloned and used as an input for multiple nodes.
pub struct Input<T> {
pub(crate) node_idx: Rc<Cell<Option<NodeId>>>,
pub(crate) value: Rc<RefCell<Option<T>>>,
}
impl<T> Input<T> {
/// Retrieves a reference to the current value of the node the input represents.
///
/// Calling this method before the node it represents has been evaluated will panic.
pub fn value(&self) -> impl Deref<Target = T> + '_ {
Ref::map(self.value.borrow(), |opt| {
opt.as_ref()
.expect("node must be evaluated before reading value")
})
}
/// Retrieves the ID of the node that this input refers to.
pub fn node_id(&self) -> NodeId {
self.node_idx.get().unwrap()
}
}
// can't derive this impl because it incorectly adds the bound T: Clone
impl<T> Clone for Input<T> {
fn clone(&self) -> Self {
Self {
node_idx: Rc::clone(&self.node_idx),
value: Rc::clone(&self.value),
}
}
}
impl<T> std::fmt::Debug for Input<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Input<{}>({:?})",
std::any::type_name::<T>(),
self.node_idx
)
}
}
/// A placeholder for the output of a dynamic rule node, to be used as an input for another.
///
/// See [`GraphBuilder::add_dynamic_rule`](`crate::builder::GraphBuilder::add_dynamic_rule`).
///
/// A dependency on a dynamic input represents both a dependency on the dynamic node itself,
/// as well as dependencies on each of the nodes that are the output of the dynamic node.
#[derive(Clone)]
pub struct DynamicInput<T> {
pub(crate) input: Input<DynamicRuleOutput<T>>,
}
impl<T> DynamicInput<T> {
/// Retrieves a reference to the current value of the dynamic node (i.e., the set of inputs
/// representing the nodes that are the outputs of the dynamic node).
pub fn value(&self) -> impl Deref<Target = DynamicRuleOutput<T>> + '_ {
self.input.value()
}
}
// TODO: i really want Input to be able to implement Deref somehow
/// A type that can visit arbitrary [`Input`]s.
///
/// You generally do not implement this trait yourself. An implementation is provided to
/// [`InputVisitable::visit_inputs`].
pub trait InputVisitor {
/// Visit an input whose value is of type `T`.
fn visit<T>(&mut self, input: &Input<T>);
}
/// Common supertrait of [`Rule`](`crate::rule::Rule`) and [`AsyncRule`](`crate::rule::AsyncRule`)
/// that defines how rule inputs are visited.
///
/// The implementation of this trait can generally be derived using [`derive@InputVisitable`].
pub trait InputVisitable {
/// Visits all the [`Input`]s of this rule.
///
/// This method is called when the graph is built/modified in order to establish edges of the graph,
/// representing the dependencies. Any input that the [`InputVisitor::visit`] is called with is
/// considered a dependency of the rule's node.
///
/// While it is permitted for the dependencies of a rule to change after it has been added to the graph,
/// doing so only permitted before the graph has been built or during the callback of
/// [`Graph::modify`](`crate::Graph::modify`). Changes to the rule's dependencies outside of that will
/// not be detected and will not be represented in the graph.
fn visit_inputs(&self, visitor: &mut impl InputVisitor);
}
impl<T> InputVisitable for Input<T> {
fn visit_inputs(&self, visitor: &mut impl InputVisitor) {
visitor.visit(self);
}
}
impl<T> InputVisitable for DynamicInput<T> {
fn visit_inputs(&self, visitor: &mut impl InputVisitor) {
// Visit the dynamic node itself
visitor.visit(&self.input);
// And visit all the nodes it produces
let maybe_dynamic_output = self.input.value.borrow();
if let Some(dynamic_output) = maybe_dynamic_output.as_ref() {
// This might be slightly overzealous: it is possible for a node to only depend on the
// dynamic node itself, and not directly depend on any of the nodes the dynamic node produces.
for input in dynamic_output.inputs.iter() {
visitor.visit(input);
}
} else {
// Haven't evaluated the dynamic node for the first time yet.
// Upon doing so, if the nodes it produces change, we'll modify the graph
// and end up back here in the other branch.
}
}
}
// no-op impl, here for convenience so you don't have to have to have as many `#[skip_input]`s
impl<ID, T> InputVisitable for DynamicNodeFactory<ID, T> {
fn visit_inputs(&self, _visitor: &mut impl InputVisitor) {}
}

View File

@ -44,16 +44,17 @@
//! ``` //! ```
pub mod builder; pub mod builder;
pub mod input;
pub mod node; pub mod node;
pub mod rule; pub mod rule;
pub mod synchronicity; pub mod synchronicity;
mod util; mod util;
use builder::{BuildGraphError, GraphBuilder}; use builder::{BuildGraphError, GraphBuilder};
use input::{Input, InputVisitor};
use node::{ErasedNode, NodeUpdateContext, NodeValue}; use node::{ErasedNode, NodeUpdateContext, NodeValue};
use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences, NodeIndexable, NodeRef}; use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences, NodeIndexable, NodeRef};
use petgraph::{stable_graph::StableDiGraph, visit::EdgeRef}; use petgraph::{stable_graph::StableDiGraph, visit::EdgeRef};
use rule::{Input, InputVisitor};
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::VecDeque; use std::collections::VecDeque;
@ -524,9 +525,8 @@ mod tests {
use rule::DynamicNodeFactory; use rule::DynamicNodeFactory;
use super::*; use super::*;
use crate::rule::{ use crate::input::{DynamicInput, InputVisitable};
AsyncDynamicRule, AsyncRule, ConstantRule, DynamicInput, DynamicRule, InputVisitable, Rule, use crate::rule::{AsyncDynamicRule, AsyncRule, ConstantRule, DynamicRule, Rule};
};
#[test] #[test]
fn rule_output_with_no_inputs() { fn rule_output_with_no_inputs() {
@ -831,7 +831,7 @@ mod tests {
struct DynamicSum(DynamicInput<i32>); struct DynamicSum(DynamicInput<i32>);
impl InputVisitable for DynamicSum { impl InputVisitable for DynamicSum {
fn visit_inputs(&self, visitor: &mut impl InputVisitor) { fn visit_inputs(&self, visitor: &mut impl InputVisitor) {
visitor.visit_dynamic(&self.0); self.0.visit_inputs(visitor);
} }
} }
impl Rule for DynamicSum { impl Rule for DynamicSum {

View File

@ -1,6 +1,6 @@
use crate::input::InputVisitable;
use crate::rule::{ use crate::rule::{
AsyncDynamicRule, AsyncDynamicRuleContext, AsyncRule, DynamicInput, DynamicRule, AsyncDynamicRule, AsyncDynamicRuleContext, AsyncRule, DynamicRule, DynamicRuleContext, Rule,
DynamicRuleContext, InputVisitable, Rule,
}; };
use crate::synchronicity::{Asynchronous, Synchronicity}; use crate::synchronicity::{Asynchronous, Synchronicity};
use crate::{Graph, Input, InputVisitor, InvalidationSignal, NodeGraph, NodeId, Synchronous}; use crate::{Graph, Input, InputVisitor, InvalidationSignal, NodeGraph, NodeId, Synchronous};
@ -257,24 +257,6 @@ fn visit_inputs<V: InputVisitable>(visitable: &V, visitor: &mut dyn FnMut(NodeId
fn visit<T>(&mut self, input: &Input<T>) { fn visit<T>(&mut self, input: &Input<T>) {
self.0(input.node_idx.get().unwrap()); self.0(input.node_idx.get().unwrap());
} }
fn visit_dynamic<T>(&mut self, input: &DynamicInput<T>) {
// Visit the dynamic node itself
self.visit(&input.input);
// And visit all the nodes it produces
let maybe_dynamic_output = input.input.value.borrow();
if let Some(dynamic_output) = maybe_dynamic_output.as_ref() {
// This might be slightly overzealous: it is possible for a node to only depend on the
// dynamic node itself, and not directly depend on any of the nodes the dynamic node produces.
for input in dynamic_output.inputs.iter() {
self.visit(input);
}
} else {
// Haven't evaluated the dynamic node for the first time yet.
// Upon doing so, if the nodes it produces change, we'll modify the graph
// and end up back here in the other branch.
}
}
} }
visitable.visit_inputs(&mut InputIndexVisitor(visitor)); visitable.visit_inputs(&mut InputIndexVisitor(visitor));
} }

View File

@ -1,12 +1,9 @@
use crate::input::{Input, InputVisitable, InputVisitor};
use crate::node::{DynamicRuleOutput, NodeValue}; use crate::node::{DynamicRuleOutput, NodeValue};
use crate::{InvalidationSignal, NodeId}; use crate::{InvalidationSignal, NodeId};
pub use compute_graph_macros::InputVisitable;
use std::cell::{Cell, Ref, RefCell};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::future::Future; use std::future::Future;
use std::hash::Hash; use std::hash::Hash;
use std::ops::Deref;
use std::rc::Rc;
/// A rule produces a value for a graph node using its [`Input`]s. /// A rule produces a value for a graph node using its [`Input`]s.
/// ///
@ -130,7 +127,7 @@ pub trait DynamicRuleContext {
/// Helper type for working with [`DynamicRule`]s. /// Helper type for working with [`DynamicRule`]s.
/// ///
/// When implementing [`DynamicRule::evaluate`], call the [`DynamicNodeFactory::add_rule`] method for /// When implementing [`DynamicRule::evaluate`], call the [`DynamicNodeFactory::add_node`] method for
/// each of the nodes that should be in the output. Then, call [`DynamicNodeFactory::all_nodes`] to produce /// each of the nodes that should be in the output. Then, call [`DynamicNodeFactory::all_nodes`] to produce
/// the output (i.e., `Vec` of [`Input`]s) of the dynamic node. /// the output (i.e., `Vec` of [`Input`]s) of the dynamic node.
/// ///
@ -188,7 +185,7 @@ impl<ID: Hash + Eq + Clone, ChildOutput> DynamicNodeFactory<ID, ChildOutput> {
input input
} }
/// Removes any nodes that were previously output but which have not had [`DynamicNodeFactory::add_rule`] /// Removes any nodes that were previously output but which have not had [`DynamicNodeFactory::add_node`]
/// called during this evaluation. /// called during this evaluation.
/// ///
/// Either this method or [`DynamicNodeFactory::all_nodes`] should only be called once per evaluation. /// Either this method or [`DynamicNodeFactory::all_nodes`] should only be called once per evaluation.
@ -252,104 +249,6 @@ pub trait AsyncDynamicRuleContext: DynamicRuleContext {
R: AsyncRule; R: AsyncRule;
} }
/// Common supertrait of [`Rule`] and [`AsyncRule`] that defines how rule inputs are visited.
///
/// The implementation of this trait can generally be derived using [`derive@InputVisitable`].
pub trait InputVisitable {
/// Visits all the [`Input`]s of this rule.
///
/// This method is called when the graph is built/modified in order to establish edges of the graph,
/// representing the dependencies. Any input that the [`InputVisitor::visit`] is called with is
/// considered a dependency of the rule's node.
///
/// While it is permitted for the dependencies of a rule to change after it has been added to the graph,
/// doing so only permitted before the graph has been built or during the callback of
/// [`Graph::modify`](`crate::Graph::modify`). Changes to the rule's dependencies outside of that will
/// not be detected and will not be represented in the graph.
fn visit_inputs(&self, visitor: &mut impl InputVisitor);
}
/// A placeholder for the output of one node, to be used as an input for another.
///
/// To obtain an input, add a value or rule to a [`GraphBuilder`](`crate::builder::GraphBuilder`).
///
/// Note that this type implements `Clone`, so can be cloned and used as an input for multiple nodes.
pub struct Input<T> {
pub(crate) node_idx: Rc<Cell<Option<NodeId>>>,
pub(crate) value: Rc<RefCell<Option<T>>>,
}
impl<T> Input<T> {
/// Retrieves a reference to the current value of the node the input represents.
///
/// Calling this method before the node it represents has been evaluated will panic.
pub fn value(&self) -> impl Deref<Target = T> + '_ {
Ref::map(self.value.borrow(), |opt| {
opt.as_ref()
.expect("node must be evaluated before reading value")
})
}
/// Retrieves the ID of the node that this input refers to.
pub fn node_id(&self) -> NodeId {
self.node_idx.get().unwrap()
}
}
// can't derive this impl because it incorectly adds the bound T: Clone
impl<T> Clone for Input<T> {
fn clone(&self) -> Self {
Self {
node_idx: Rc::clone(&self.node_idx),
value: Rc::clone(&self.value),
}
}
}
impl<T> std::fmt::Debug for Input<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Input<{}>({:?})",
std::any::type_name::<T>(),
self.node_idx
)
}
}
/// A placeholder for the output of a dynamic rule node, to be used as an input for another.
///
/// See [`GraphBuilder::add_dynamic_rule`](`crate::builder::GraphBuilder::add_dynamic_rule`).
///
/// A dependency on a dynamic input represents both a dependency on the dynamic node itself,
/// as well as dependencies on each of the nodes that are the output of the dynamic node.
#[derive(Clone)]
pub struct DynamicInput<T> {
pub(crate) input: Input<DynamicRuleOutput<T>>,
}
impl<T> DynamicInput<T> {
/// Retrieves a reference to the current value of the dynamic node (i.e., the set of inputs
/// representing the nodes that are the outputs of the dynamic node).
pub fn value(&self) -> impl Deref<Target = DynamicRuleOutput<T>> + '_ {
self.input.value()
}
}
// TODO: i really want Input to be able to implement Deref somehow
/// A type that can visit arbitrary [`Input`]s.
///
/// You generally do not implement this trait yourself. An implementation is provided to
/// [`InputVisitable::visit_inputs`].
pub trait InputVisitor {
/// Visit an input whose value is of type `T`.
fn visit<T>(&mut self, input: &Input<T>);
/// Visit a dynamic input whose child value is of type `T`.
fn visit_dynamic<T>(&mut self, input: &DynamicInput<T>);
}
/// A simple rule that provides a constant value. /// A simple rule that provides a constant value.
/// ///
/// Note that, because [`Rule::evaluate`] returns an owned value, this rule's value type must implement `Clone`. /// Note that, because [`Rule::evaluate`] returns an owned value, this rule's value type must implement `Clone`.

View File

@ -1,9 +1,9 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Literal; use proc_macro2::{Literal, Span};
use quote::{format_ident, quote, ToTokens}; use quote::{format_ident, quote, ToTokens};
use syn::{ use syn::{
parse_macro_input, Attribute, Data, DataStruct, DeriveInput, Field, Fields, GenericArgument, parse_macro_input, Attribute, Data, DataEnum, DataStruct, DeriveInput, Field, Fields,
GenericParam, PathArguments, Type, GenericArgument, GenericParam, Index, Member, PathArguments, Type,
}; };
extern crate proc_macro; extern crate proc_macro;
@ -11,83 +11,41 @@ extern crate proc_macro;
/// Derive an implementation of the `InputVisitable` trait and helper methods. /// Derive an implementation of the `InputVisitable` trait and helper methods.
/// ///
/// This macro generates an implementation of the `InputVisitable` trait and the `visit_inputs` method that /// This macro generates an implementation of the `InputVisitable` trait and the `visit_inputs` method that
/// calls `visit` on each field of the struct that is of type `Input<T>` or `DynamicInput<T>` for any `T`. /// calls `visit_inputs` on each field of the struct that is does not have the `#[skip_visit]` attribute.
/// ///
/// The macro also generates helper methods for accessing the value of each input less verbosely. /// For structs only, the macro also generates helper methods for accessing the value of each input less verbosely.
/// For unnamed struct fields, the methods generated have the form `input_0`, `input_1`, etc. /// For unnamed struct fields, the methods generated have the form `input_0`, `input_1`, etc.
/// For named fields, the generated method name matches the field name. In both cases, the method /// For named fields, the generated method name matches the field name. In both cases, the method
/// returns a reference to the input value. As with the `Input::value` method, calling the helper methods /// returns a reference to the input value. As with the `Input::value` method, calling the helper methods
/// before the referenced node has been evaluated is forbidden. /// before the referenced node has been evaluated is forbidden.
#[proc_macro_derive(InputVisitable, attributes(ignore_input))] #[proc_macro_derive(InputVisitable, attributes(skip_visit))]
pub fn derive_rule(input: TokenStream) -> TokenStream { pub fn derive_input_visitable(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput); let input = parse_macro_input!(input as DeriveInput);
if let Data::Struct(ref data) = input.data { match input.data {
derive_rule_struct(&input, data) Data::Struct(ref data) => derive_input_visitable_struct(&input, data),
} else { Data::Enum(ref data) => derive_input_visitable_enum(&input, data),
TokenStream::from( Data::Union(_) => TokenStream::from(
syn::Error::new( syn::Error::new(input.ident.span(), "Unions cannot derive `InputVisitable`")
input.ident.span(), .to_compile_error(),
"Only structs can derive `InputVisitable`", ),
)
.to_compile_error(),
)
} }
} }
fn derive_rule_struct(input: &DeriveInput, data: &DataStruct) -> TokenStream { fn derive_input_visitable_struct(input: &DeriveInput, data: &DataStruct) -> TokenStream {
let name = &input.ident; let visit_inputs = derive_visit_inputs(
let lt = &input.generics.lt_token; &data.fields,
let params = &input.generics.params; |field| {
let gt = &input.generics.gt_token; let ident = field.ident.clone().unwrap();
let generics = quote!(#lt #params #gt); quote!(&self.#ident)
let where_clause = &input.generics.where_clause; },
let params_only_names = params.iter().map(|p| match p { |i| {
GenericParam::Lifetime(_) => { let member = Member::Unnamed(Index {
panic!("Lifetime generics aren't supported when deriving `InputVisitable`") index: i as u32,
} span: Span::call_site(),
GenericParam::Type(ty) => &ty.ident, });
GenericParam::Const(_) => { quote!(&self.#member)
panic!("Const generics aren't supported when deriving `InputVisitable`") },
} );
});
let generics_only_names = quote!(#lt #(#params_only_names),* #gt);
let visit_inputs = match data.fields {
Fields::Named(ref named) => named
.named
.iter()
.flat_map(|field| {
if let Some((_ty, is_dynamic)) = input_value_type(field) {
let ident = field.ident.as_ref().unwrap();
if is_dynamic {
Some(quote!(visitor.visit_dynamic(&self.#ident);))
} else {
Some(quote!(visitor.visit(&self.#ident);))
}
} else {
None
}
})
.collect::<Vec<_>>(),
Fields::Unnamed(ref unnamed) => unnamed
.unnamed
.iter()
.enumerate()
.flat_map(|(i, field)| {
if let Some((_ty, is_dynamic)) = input_value_type(field) {
let idx_lit = Literal::usize_unsuffixed(i);
if is_dynamic {
Some(quote!(visitor.visit_dynamic(&self.#idx_lit);))
} else {
Some(quote!(visitor.visit(&self.#idx_lit);))
}
} else {
None
}
})
.collect::<Vec<_>>(),
Fields::Unit => vec![],
};
let input_value_methods = match data.fields { let input_value_methods = match data.fields {
Fields::Named(ref named) => named Fields::Named(ref named) => named
@ -139,23 +97,138 @@ fn derive_rule_struct(input: &DeriveInput, data: &DataStruct) -> TokenStream {
Fields::Unit => vec![], Fields::Unit => vec![],
}; };
TokenStream::from(quote!( let input_visitable_impl = make_derive_impl(
input,
impl #generics ::compute_graph::rule::InputVisitable for #name #generics_only_names #where_clause { quote!(::compute_graph::input::InputVisitable for),
fn visit_inputs(&self, visitor: &mut impl ::compute_graph::rule::InputVisitor) { quote!(
#(#visit_inputs)* fn visit_inputs(&self, visitor: &mut impl ::compute_graph::input::InputVisitor) {
#visit_inputs
} }
} ),
);
impl #generics #name #generics_only_names #where_clause { let input_value_methods_impl = make_derive_impl(
input,
quote!(),
quote!(
#(#input_value_methods)* #(#input_value_methods)*
} ),
);
TokenStream::from(quote!(
#input_visitable_impl
#input_value_methods_impl
)) ))
} }
fn derive_input_visitable_enum(input: &DeriveInput, data: &DataEnum) -> TokenStream {
let variants = data.variants.iter().map(|variant| {
let ident = &variant.ident;
let fields = match &variant.fields {
Fields::Named(fields) => {
let field_refs = fields.named.iter().filter(should_visit_field).map(|field| {
let ident = field.ident.as_ref().expect("named field must have ident");
quote!(ref #ident)
});
quote!({ #(#field_refs,)* .. })
}
Fields::Unnamed(fields) => {
let field_refs = fields.unnamed.iter().enumerate().map(|(i, field)| {
if should_visit_field(&field) {
let ident = format_ident!("field_{i}");
quote!(ref #ident)
} else {
quote!(_)
}
});
quote!((#(#field_refs,)*))
}
Fields::Unit => quote!(),
};
let variant_visit_inputs = derive_visit_inputs(
&variant.fields,
|field| field.ident.clone().unwrap(),
|i| format_ident!("field_{i}"),
);
quote!(Self::#ident #fields => {
#variant_visit_inputs
})
});
make_derive_impl(
input,
quote!(::compute_graph::input::InputVisitable for),
quote!(
fn visit_inputs(&self, visitor: &mut impl ::compute_graph::input::InputVisitor) {
match self {
#(#variants)*
}
}
),
)
.into()
}
fn derive_visit_inputs<NamedAccessTokens: ToTokens, UnnamedAccessTokens: ToTokens>(
fields: &Fields,
named_field_access: impl Fn(&Field) -> NamedAccessTokens,
unnamed_field_access: impl Fn(usize) -> UnnamedAccessTokens,
) -> proc_macro2::TokenStream {
match fields {
Fields::Named(ref named) => {
let fields = named.named.iter().filter(should_visit_field).map(|field| {
let field_access = named_field_access(field);
quote!(::compute_graph::input::InputVisitable::visit_inputs(#field_access, visitor);)
});
quote!(#(#fields)*)
}
Fields::Unnamed(ref unnamed) => {
let fields = unnamed
.unnamed
.iter()
.enumerate()
.filter(|(_i, field)| should_visit_field(field))
.map(|(i, _field)| {
let field_access = unnamed_field_access(i);
quote!(::compute_graph::input::InputVisitable::visit_inputs(#field_access, visitor);)
});
quote!(#(#fields)*)
}
Fields::Unit => proc_macro2::TokenStream::new(),
}
}
fn make_derive_impl(
input: &DeriveInput,
trait_for: proc_macro2::TokenStream,
body: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let name = &input.ident;
let lt = &input.generics.lt_token;
let params = &input.generics.params;
let gt = &input.generics.gt_token;
let generics = quote!(#lt #params #gt);
let where_clause = &input.generics.where_clause;
let params_only_names = params.iter().map(|p| match p {
GenericParam::Lifetime(_) => {
panic!("Lifetime generics aren't supported when deriving `InputVisitable`")
}
GenericParam::Type(ty) => &ty.ident,
GenericParam::Const(_) => {
panic!("Const generics aren't supported when deriving `InputVisitable`")
}
});
let generics_only_names = quote!(#lt #(#params_only_names),* #gt);
quote!(
impl #generics #trait_for #name #generics_only_names #where_clause {
#body
}
)
}
fn input_value_type(field: &Field) -> Option<(&Type, bool)> { fn input_value_type(field: &Field) -> Option<(&Type, bool)> {
if field.attrs.iter().any(|attr| is_ignore_attr(attr)) { if field.attrs.iter().any(is_skip_attr) {
return None; return None;
} }
if let Type::Path(ref path) = field.ty { if let Type::Path(ref path) = field.ty {
@ -183,9 +256,13 @@ fn input_value_type(field: &Field) -> Option<(&Type, bool)> {
} }
} }
fn is_ignore_attr(attr: &Attribute) -> bool { fn should_visit_field(field: &&Field) -> bool {
!field.attrs.iter().any(is_skip_attr)
}
fn is_skip_attr(attr: &Attribute) -> bool {
match attr.meta.require_path_only() { match attr.meta.require_path_only() {
Ok(path) => path.is_ident("ignore_input"), Ok(path) => path.is_ident("skip_visit"),
Err(_) => false, Err(_) => false,
} }
} }

View File

@ -1,8 +1,9 @@
use compute_graph::input::{DynamicInput, Input, InputVisitable};
use compute_graph::node::NodeValue; use compute_graph::node::NodeValue;
use compute_graph::rule::{DynamicInput, Input, InputVisitable, Rule}; use compute_graph::rule::Rule;
#[derive(InputVisitable)] #[derive(InputVisitable)]
struct Add(Input<i32>, Input<i32>, i32); struct Add(Input<i32>, Input<i32>, #[skip_visit] i32);
impl Rule for Add { impl Rule for Add {
type Output = i32; type Output = i32;
@ -15,6 +16,7 @@ impl Rule for Add {
struct Add2 { struct Add2 {
a: Input<i32>, a: Input<i32>,
b: Input<i32>, b: Input<i32>,
#[skip_visit]
c: i32, c: i32,
} }
@ -47,11 +49,25 @@ impl Rule for Sum {
} }
} }
#[derive(InputVisitable)]
enum E {
A(#[skip_visit] i32, Input<i32>),
B {
#[skip_visit]
x: i32,
y: Input<i32>,
},
C {
x: Input<i32>,
},
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use compute_graph::{ use compute_graph::{
builder::GraphBuilder, builder::GraphBuilder,
rule::{ConstantRule, DynamicRule, InputVisitor}, input::InputVisitor,
rule::{ConstantRule, DynamicRule},
synchronicity::Synchronous, synchronicity::Synchronous,
}; };
@ -102,9 +118,10 @@ mod tests {
#[test] #[test]
fn test_ignore() { fn test_ignore() {
#[allow(unused)]
#[derive(InputVisitable)] #[derive(InputVisitable)]
struct Ignore { struct Ignore {
#[ignore_input] #[skip_visit]
input: Input<i32>, input: Input<i32>,
} }
let mut builder = GraphBuilder::<i32, Synchronous>::new(); let mut builder = GraphBuilder::<i32, Synchronous>::new();
@ -113,13 +130,42 @@ mod tests {
fn visit<T>(&mut self, _input: &Input<T>) { fn visit<T>(&mut self, _input: &Input<T>) {
assert!(false); assert!(false);
} }
fn visit_dynamic<T>(&mut self, _input: &DynamicInput<T>) {
unreachable!();
}
} }
Ignore { Ignore {
input: builder.add_value(0), input: builder.add_value(0),
} }
.visit_inputs(&mut Visitor); .visit_inputs(&mut Visitor);
} }
#[test]
fn test_enum() {
let mut builder = GraphBuilder::<i32, Synchronous>::new();
let input = builder.add_value(1);
struct Visitor(bool, Input<i32>);
impl InputVisitor for Visitor {
fn visit<T>(&mut self, input: &Input<T>) {
assert_eq!(input.node_id(), self.1.node_id());
assert!(!self.0);
self.0 = true;
}
}
let a = E::A(0, input.clone());
let mut visitor = Visitor(false, input.clone());
InputVisitable::visit_inputs(&a, &mut visitor);
assert!(visitor.0);
let b = E::B {
x: 0,
y: input.clone(),
};
let mut visitor = Visitor(false, input.clone());
InputVisitable::visit_inputs(&b, &mut visitor);
assert!(visitor.0);
let c = E::C { x: input.clone() };
let mut visitor = Visitor(false, input);
InputVisitable::visit_inputs(&c, &mut visitor);
assert!(visitor.0);
}
} }

View File

@ -3,7 +3,8 @@ use std::collections::HashMap;
use chrono::Datelike; use chrono::Datelike;
use compute_graph::{ use compute_graph::{
builder::GraphBuilder, builder::GraphBuilder,
rule::{Input, InputVisitable, Rule}, input::{Input, InputVisitable},
rule::Rule,
synchronicity::Asynchronous, synchronicity::Asynchronous,
}; };
use serde::Serialize; use serde::Serialize;

View File

@ -10,7 +10,8 @@ use base64::{Engine, prelude::BASE64_STANDARD};
use compute_graph::{ use compute_graph::{
InvalidationSignal, InvalidationSignal,
builder::GraphBuilder, builder::GraphBuilder,
rule::{Input, InputVisitable, Rule}, input::{Input, InputVisitable, InputVisitor},
rule::Rule,
synchronicity::Asynchronous, synchronicity::Asynchronous,
}; };
use grass::{Fs, Options, OutputStyle}; use grass::{Fs, Options, OutputStyle};
@ -88,7 +89,7 @@ struct CompileScss {
fonts: HashMap<&'static str, Input<String>>, fonts: HashMap<&'static str, Input<String>>,
} }
impl InputVisitable for CompileScss { impl InputVisitable for CompileScss {
fn visit_inputs(&self, visitor: &mut impl compute_graph::rule::InputVisitor) { fn visit_inputs(&self, visitor: &mut impl InputVisitor) {
for input in self.fonts.values() { for input in self.fonts.values() {
visitor.visit(input); visitor.visit(input);
} }
@ -157,7 +158,7 @@ impl<'a> Fs for TrackingFs<'a> {
} }
#[derive(InputVisitable)] #[derive(InputVisitable)]
struct ReadFile(PathBuf); struct ReadFile(#[skip_visit] PathBuf);
impl Rule for ReadFile { impl Rule for ReadFile {
type Output = String; type Output = String;
fn evaluate(&mut self) -> Self::Output { fn evaluate(&mut self) -> Self::Output {

View File

@ -1,6 +1,7 @@
use compute_graph::{ use compute_graph::{
builder::GraphBuilder, builder::GraphBuilder,
rule::{Input, InputVisitable, Rule}, input::{Input, InputVisitable},
rule::Rule,
synchronicity::Asynchronous, synchronicity::Asynchronous,
}; };

View File

@ -7,10 +7,8 @@ use std::rc::Rc;
use compute_graph::{ use compute_graph::{
builder::GraphBuilder, builder::GraphBuilder,
rule::{ input::{DynamicInput, Input, InputVisitable},
DynamicInput, DynamicNodeFactory, DynamicRule, DynamicRuleContext, Input, InputVisitable, rule::{DynamicNodeFactory, DynamicRule, DynamicRuleContext, Rule},
Rule,
},
synchronicity::Asynchronous, synchronicity::Asynchronous,
}; };
use content::{HtmlContent, Post}; use content::{HtmlContent, Post};
@ -97,6 +95,7 @@ fn find_index(path: PathBuf) -> Option<PathBuf> {
#[derive(InputVisitable)] #[derive(InputVisitable)]
struct MakeReadNodes { struct MakeReadNodes {
files: Input<Vec<PathBuf>>, files: Input<Vec<PathBuf>>,
#[skip_visit]
watcher: Rc<RefCell<FileWatcher>>, watcher: Rc<RefCell<FileWatcher>>,
node_factory: DynamicNodeFactory<PathBuf, ReadPostOutput>, node_factory: DynamicNodeFactory<PathBuf, ReadPostOutput>,
} }
@ -129,6 +128,7 @@ pub type ReadPostOutput = Option<Post<HtmlContent>>;
#[derive(InputVisitable)] #[derive(InputVisitable)]
struct ReadPost { struct ReadPost {
#[skip_visit]
path: PathBuf, path: PathBuf,
} }
impl Rule for ReadPost { impl Rule for ReadPost {

View File

@ -2,7 +2,8 @@ use std::collections::HashMap;
use compute_graph::{ use compute_graph::{
builder::GraphBuilder, builder::GraphBuilder,
rule::{DynamicInput, DynamicNodeFactory, DynamicRule, Input, InputVisitable, Rule}, input::{DynamicInput, Input, InputVisitable},
rule::{DynamicNodeFactory, DynamicRule, Rule},
synchronicity::Asynchronous, synchronicity::Asynchronous,
}; };
use serde::Serialize; use serde::Serialize;
@ -77,6 +78,7 @@ impl DynamicRule for MakePostsByTags {
#[derive(InputVisitable)] #[derive(InputVisitable)]
struct PostsByTag { struct PostsByTag {
posts: DynamicInput<ReadPostOutput>, posts: DynamicInput<ReadPostOutput>,
#[skip_visit]
tag: Tag, tag: Tag,
} }
impl Rule for PostsByTag { impl Rule for PostsByTag {
@ -131,7 +133,7 @@ struct Entry {
#[derive(InputVisitable)] #[derive(InputVisitable)]
struct MakeWriteTagPages { struct MakeWriteTagPages {
tags: DynamicInput<TagAndPosts>, tags: DynamicInput<TagAndPosts>,
#[ignore_input] #[skip_visit]
templates: Input<Templates>, templates: Input<Templates>,
build_context_factory: DynamicNodeFactory<String, Context>, build_context_factory: DynamicNodeFactory<String, Context>,
render_factory: DynamicNodeFactory<String, ()>, render_factory: DynamicNodeFactory<String, ()>,

View File

@ -6,8 +6,9 @@ use std::{
use chrono::Local; use chrono::Local;
use compute_graph::{ use compute_graph::{
builder::GraphBuilder, builder::GraphBuilder,
input::{Input, InputVisitable},
node::NodeValue, node::NodeValue,
rule::{Input, InputVisitable, Rule}, rule::Rule,
synchronicity::Asynchronous, synchronicity::Asynchronous,
}; };
use log::error; use log::error;
@ -39,7 +40,11 @@ pub fn make_graph(
} }
#[derive(InputVisitable)] #[derive(InputVisitable)]
pub struct AddTemplate(String, PathBuf, Input<Templates>); pub struct AddTemplate(
#[skip_visit] String,
#[skip_visit] PathBuf,
Input<Templates>,
);
impl AddTemplate { impl AddTemplate {
pub fn new(name: &str, path: PathBuf, base: Input<Templates>) -> Self { pub fn new(name: &str, path: PathBuf, base: Input<Templates>) -> Self {
Self(name.into(), path, base) Self(name.into(), path, base)
@ -86,9 +91,11 @@ static CB: Lazy<u64> = Lazy::new(|| {
.as_secs() .as_secs()
}); });
#[derive(InputVisitable)]
pub struct BuildTemplateContext<T, F> { pub struct BuildTemplateContext<T, F> {
permalink: TemplatePermalink, permalink: TemplatePermalink,
input: Input<T>, input: Input<T>,
#[skip_visit]
func: F, func: F,
} }
impl<T, F: Fn(&T, &mut Context) -> ()> BuildTemplateContext<T, F> { impl<T, F: Fn(&T, &mut Context) -> ()> BuildTemplateContext<T, F> {
@ -100,14 +107,6 @@ impl<T, F: Fn(&T, &mut Context) -> ()> BuildTemplateContext<T, F> {
} }
} }
} }
impl<T, F> InputVisitable for BuildTemplateContext<T, F> {
fn visit_inputs(&self, visitor: &mut impl compute_graph::rule::InputVisitor) {
if let TemplatePermalink::Dynamic(ref input) = self.permalink {
visitor.visit(input);
}
visitor.visit(&self.input);
}
}
impl<T: 'static, F: Fn(&T, &mut Context) -> () + 'static> Rule for BuildTemplateContext<T, F> { impl<T: 'static, F: Fn(&T, &mut Context) -> () + 'static> Rule for BuildTemplateContext<T, F> {
type Output = Context; type Output = Context;
fn evaluate(&mut self) -> Self::Output { fn evaluate(&mut self) -> Self::Output {
@ -126,9 +125,10 @@ impl<T: 'static, F: Fn(&T, &mut Context) -> () + 'static> Rule for BuildTemplate
} }
} }
#[derive(InputVisitable)]
pub enum TemplatePermalink { pub enum TemplatePermalink {
Constant(&'static str), Constant(#[skip_visit] &'static str),
ConstantOwned(String), ConstantOwned(#[skip_visit] String),
Dynamic(Input<String>), Dynamic(Input<String>),
} }
impl From<&'static str> for TemplatePermalink { impl From<&'static str> for TemplatePermalink {
@ -147,23 +147,14 @@ impl From<Input<String>> for TemplatePermalink {
} }
} }
#[derive(InputVisitable)]
pub struct RenderTemplate { pub struct RenderTemplate {
#[skip_visit]
pub name: &'static str, pub name: &'static str,
pub output_path: TemplateOutputPath, pub output_path: TemplateOutputPath,
pub templates: Input<Templates>, pub templates: Input<Templates>,
pub context: Input<Context>, pub context: Input<Context>,
} }
// TODO: derive InputVisitable for enums?
// make Input impl InputVisitable, then the derived impl can just call visit_inputs on everything
impl InputVisitable for RenderTemplate {
fn visit_inputs(&self, visitor: &mut impl compute_graph::rule::InputVisitor) {
if let TemplateOutputPath::Dynamic(ref input) = self.output_path {
visitor.visit(input);
}
visitor.visit(&self.templates);
visitor.visit(&self.context);
}
}
impl Rule for RenderTemplate { impl Rule for RenderTemplate {
type Output = (); type Output = ();
@ -190,8 +181,9 @@ impl Rule for RenderTemplate {
} }
} }
} }
#[derive(InputVisitable)]
pub enum TemplateOutputPath { pub enum TemplateOutputPath {
Constant(PathBuf), Constant(#[skip_visit] PathBuf),
Dynamic(Input<PathBuf>), Dynamic(Input<PathBuf>),
} }
impl<'a> From<&'a str> for TemplateOutputPath { impl<'a> From<&'a str> for TemplateOutputPath {

View File

@ -8,9 +8,12 @@ use std::io::{BufWriter, Write};
use std::path::{Component, Path, PathBuf}; use std::path::{Component, Path, PathBuf};
use anyhow::anyhow; use anyhow::anyhow;
use compute_graph::builder::GraphBuilder; use compute_graph::{
use compute_graph::rule::{DynamicInput, Input, InputVisitable, Rule}; builder::GraphBuilder,
use compute_graph::synchronicity::Synchronicity; input::{DynamicInput, Input, InputVisitable},
rule::Rule,
synchronicity::Synchronicity,
};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
pub fn output_writer(path: impl AsRef<Path>) -> Result<impl Write, std::io::Error> { pub fn output_writer(path: impl AsRef<Path>) -> Result<impl Write, std::io::Error> {