Improve InputVisitable derive macro
This commit is contained in:
parent
55b91944b2
commit
1279a1755d
@ -1,8 +1,9 @@
|
||||
use crate::input::{DynamicInput, Input};
|
||||
use crate::node::{
|
||||
AsyncConstNode, AsyncDynamicRuleNode, AsyncRuleNode, ConstNode, DynamicRuleNode, ErasedNode,
|
||||
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::util;
|
||||
use crate::{Graph, InvalidationSignal, NodeGraph, NodeId, ValueInvalidationSignal};
|
||||
|
137
crates/compute_graph/src/input.rs
Normal file
137
crates/compute_graph/src/input.rs
Normal 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) {}
|
||||
}
|
@ -44,16 +44,17 @@
|
||||
//! ```
|
||||
|
||||
pub mod builder;
|
||||
pub mod input;
|
||||
pub mod node;
|
||||
pub mod rule;
|
||||
pub mod synchronicity;
|
||||
mod util;
|
||||
|
||||
use builder::{BuildGraphError, GraphBuilder};
|
||||
use input::{Input, InputVisitor};
|
||||
use node::{ErasedNode, NodeUpdateContext, NodeValue};
|
||||
use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences, NodeIndexable, NodeRef};
|
||||
use petgraph::{stable_graph::StableDiGraph, visit::EdgeRef};
|
||||
use rule::{Input, InputVisitor};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
@ -524,9 +525,8 @@ mod tests {
|
||||
use rule::DynamicNodeFactory;
|
||||
|
||||
use super::*;
|
||||
use crate::rule::{
|
||||
AsyncDynamicRule, AsyncRule, ConstantRule, DynamicInput, DynamicRule, InputVisitable, Rule,
|
||||
};
|
||||
use crate::input::{DynamicInput, InputVisitable};
|
||||
use crate::rule::{AsyncDynamicRule, AsyncRule, ConstantRule, DynamicRule, Rule};
|
||||
|
||||
#[test]
|
||||
fn rule_output_with_no_inputs() {
|
||||
@ -831,7 +831,7 @@ mod tests {
|
||||
struct DynamicSum(DynamicInput<i32>);
|
||||
impl InputVisitable for DynamicSum {
|
||||
fn visit_inputs(&self, visitor: &mut impl InputVisitor) {
|
||||
visitor.visit_dynamic(&self.0);
|
||||
self.0.visit_inputs(visitor);
|
||||
}
|
||||
}
|
||||
impl Rule for DynamicSum {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::input::InputVisitable;
|
||||
use crate::rule::{
|
||||
AsyncDynamicRule, AsyncDynamicRuleContext, AsyncRule, DynamicInput, DynamicRule,
|
||||
DynamicRuleContext, InputVisitable, Rule,
|
||||
AsyncDynamicRule, AsyncDynamicRuleContext, AsyncRule, DynamicRule, DynamicRuleContext, Rule,
|
||||
};
|
||||
use crate::synchronicity::{Asynchronous, Synchronicity};
|
||||
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>) {
|
||||
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));
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
use crate::input::{Input, InputVisitable, InputVisitor};
|
||||
use crate::node::{DynamicRuleOutput, NodeValue};
|
||||
use crate::{InvalidationSignal, NodeId};
|
||||
pub use compute_graph_macros::InputVisitable;
|
||||
use std::cell::{Cell, Ref, RefCell};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::future::Future;
|
||||
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.
|
||||
///
|
||||
@ -130,7 +127,7 @@ pub trait DynamicRuleContext {
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Either this method or [`DynamicNodeFactory::all_nodes`] should only be called once per evaluation.
|
||||
@ -252,104 +249,6 @@ pub trait AsyncDynamicRuleContext: DynamicRuleContext {
|
||||
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.
|
||||
///
|
||||
/// Note that, because [`Rule::evaluate`] returns an owned value, this rule's value type must implement `Clone`.
|
||||
|
@ -1,9 +1,9 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Literal;
|
||||
use proc_macro2::{Literal, Span};
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::{
|
||||
parse_macro_input, Attribute, Data, DataStruct, DeriveInput, Field, Fields, GenericArgument,
|
||||
GenericParam, PathArguments, Type,
|
||||
parse_macro_input, Attribute, Data, DataEnum, DataStruct, DeriveInput, Field, Fields,
|
||||
GenericArgument, GenericParam, Index, Member, PathArguments, Type,
|
||||
};
|
||||
|
||||
extern crate proc_macro;
|
||||
@ -11,83 +11,41 @@ extern crate proc_macro;
|
||||
/// 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
|
||||
/// 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 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
|
||||
/// before the referenced node has been evaluated is forbidden.
|
||||
#[proc_macro_derive(InputVisitable, attributes(ignore_input))]
|
||||
pub fn derive_rule(input: TokenStream) -> TokenStream {
|
||||
#[proc_macro_derive(InputVisitable, attributes(skip_visit))]
|
||||
pub fn derive_input_visitable(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
if let Data::Struct(ref data) = input.data {
|
||||
derive_rule_struct(&input, data)
|
||||
} else {
|
||||
TokenStream::from(
|
||||
syn::Error::new(
|
||||
input.ident.span(),
|
||||
"Only structs can derive `InputVisitable`",
|
||||
)
|
||||
match input.data {
|
||||
Data::Struct(ref data) => derive_input_visitable_struct(&input, data),
|
||||
Data::Enum(ref data) => derive_input_visitable_enum(&input, data),
|
||||
Data::Union(_) => TokenStream::from(
|
||||
syn::Error::new(input.ident.span(), "Unions cannot derive `InputVisitable`")
|
||||
.to_compile_error(),
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_rule_struct(input: &DeriveInput, data: &DataStruct) -> 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`")
|
||||
}
|
||||
fn derive_input_visitable_struct(input: &DeriveInput, data: &DataStruct) -> TokenStream {
|
||||
let visit_inputs = derive_visit_inputs(
|
||||
&data.fields,
|
||||
|field| {
|
||||
let ident = field.ident.clone().unwrap();
|
||||
quote!(&self.#ident)
|
||||
},
|
||||
|i| {
|
||||
let member = Member::Unnamed(Index {
|
||||
index: i as u32,
|
||||
span: Span::call_site(),
|
||||
});
|
||||
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![],
|
||||
};
|
||||
quote!(&self.#member)
|
||||
},
|
||||
);
|
||||
|
||||
let input_value_methods = match data.fields {
|
||||
Fields::Named(ref named) => named
|
||||
@ -139,23 +97,138 @@ fn derive_rule_struct(input: &DeriveInput, data: &DataStruct) -> TokenStream {
|
||||
Fields::Unit => vec![],
|
||||
};
|
||||
|
||||
TokenStream::from(quote!(
|
||||
|
||||
impl #generics ::compute_graph::rule::InputVisitable for #name #generics_only_names #where_clause {
|
||||
fn visit_inputs(&self, visitor: &mut impl ::compute_graph::rule::InputVisitor) {
|
||||
#(#visit_inputs)*
|
||||
}
|
||||
let input_visitable_impl = make_derive_impl(
|
||||
input,
|
||||
quote!(::compute_graph::input::InputVisitable for),
|
||||
quote!(
|
||||
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)*
|
||||
}
|
||||
),
|
||||
);
|
||||
|
||||
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)> {
|
||||
if field.attrs.iter().any(|attr| is_ignore_attr(attr)) {
|
||||
if field.attrs.iter().any(is_skip_attr) {
|
||||
return None;
|
||||
}
|
||||
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() {
|
||||
Ok(path) => path.is_ident("ignore_input"),
|
||||
Ok(path) => path.is_ident("skip_visit"),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
use compute_graph::input::{DynamicInput, Input, InputVisitable};
|
||||
use compute_graph::node::NodeValue;
|
||||
use compute_graph::rule::{DynamicInput, Input, InputVisitable, Rule};
|
||||
use compute_graph::rule::Rule;
|
||||
|
||||
#[derive(InputVisitable)]
|
||||
struct Add(Input<i32>, Input<i32>, i32);
|
||||
struct Add(Input<i32>, Input<i32>, #[skip_visit] i32);
|
||||
|
||||
impl Rule for Add {
|
||||
type Output = i32;
|
||||
@ -15,6 +16,7 @@ impl Rule for Add {
|
||||
struct Add2 {
|
||||
a: Input<i32>,
|
||||
b: Input<i32>,
|
||||
#[skip_visit]
|
||||
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)]
|
||||
mod tests {
|
||||
use compute_graph::{
|
||||
builder::GraphBuilder,
|
||||
rule::{ConstantRule, DynamicRule, InputVisitor},
|
||||
input::InputVisitor,
|
||||
rule::{ConstantRule, DynamicRule},
|
||||
synchronicity::Synchronous,
|
||||
};
|
||||
|
||||
@ -102,9 +118,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_ignore() {
|
||||
#[allow(unused)]
|
||||
#[derive(InputVisitable)]
|
||||
struct Ignore {
|
||||
#[ignore_input]
|
||||
#[skip_visit]
|
||||
input: Input<i32>,
|
||||
}
|
||||
let mut builder = GraphBuilder::<i32, Synchronous>::new();
|
||||
@ -113,13 +130,42 @@ mod tests {
|
||||
fn visit<T>(&mut self, _input: &Input<T>) {
|
||||
assert!(false);
|
||||
}
|
||||
fn visit_dynamic<T>(&mut self, _input: &DynamicInput<T>) {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
Ignore {
|
||||
input: builder.add_value(0),
|
||||
}
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ use std::collections::HashMap;
|
||||
use chrono::Datelike;
|
||||
use compute_graph::{
|
||||
builder::GraphBuilder,
|
||||
rule::{Input, InputVisitable, Rule},
|
||||
input::{Input, InputVisitable},
|
||||
rule::Rule,
|
||||
synchronicity::Asynchronous,
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
@ -10,7 +10,8 @@ use base64::{Engine, prelude::BASE64_STANDARD};
|
||||
use compute_graph::{
|
||||
InvalidationSignal,
|
||||
builder::GraphBuilder,
|
||||
rule::{Input, InputVisitable, Rule},
|
||||
input::{Input, InputVisitable, InputVisitor},
|
||||
rule::Rule,
|
||||
synchronicity::Asynchronous,
|
||||
};
|
||||
use grass::{Fs, Options, OutputStyle};
|
||||
@ -88,7 +89,7 @@ struct CompileScss {
|
||||
fonts: HashMap<&'static str, Input<String>>,
|
||||
}
|
||||
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() {
|
||||
visitor.visit(input);
|
||||
}
|
||||
@ -157,7 +158,7 @@ impl<'a> Fs for TrackingFs<'a> {
|
||||
}
|
||||
|
||||
#[derive(InputVisitable)]
|
||||
struct ReadFile(PathBuf);
|
||||
struct ReadFile(#[skip_visit] PathBuf);
|
||||
impl Rule for ReadFile {
|
||||
type Output = String;
|
||||
fn evaluate(&mut self) -> Self::Output {
|
||||
|
@ -1,6 +1,7 @@
|
||||
use compute_graph::{
|
||||
builder::GraphBuilder,
|
||||
rule::{Input, InputVisitable, Rule},
|
||||
input::{Input, InputVisitable},
|
||||
rule::Rule,
|
||||
synchronicity::Asynchronous,
|
||||
};
|
||||
|
||||
|
@ -7,10 +7,8 @@ use std::rc::Rc;
|
||||
|
||||
use compute_graph::{
|
||||
builder::GraphBuilder,
|
||||
rule::{
|
||||
DynamicInput, DynamicNodeFactory, DynamicRule, DynamicRuleContext, Input, InputVisitable,
|
||||
Rule,
|
||||
},
|
||||
input::{DynamicInput, Input, InputVisitable},
|
||||
rule::{DynamicNodeFactory, DynamicRule, DynamicRuleContext, Rule},
|
||||
synchronicity::Asynchronous,
|
||||
};
|
||||
use content::{HtmlContent, Post};
|
||||
@ -97,6 +95,7 @@ fn find_index(path: PathBuf) -> Option<PathBuf> {
|
||||
#[derive(InputVisitable)]
|
||||
struct MakeReadNodes {
|
||||
files: Input<Vec<PathBuf>>,
|
||||
#[skip_visit]
|
||||
watcher: Rc<RefCell<FileWatcher>>,
|
||||
node_factory: DynamicNodeFactory<PathBuf, ReadPostOutput>,
|
||||
}
|
||||
@ -129,6 +128,7 @@ pub type ReadPostOutput = Option<Post<HtmlContent>>;
|
||||
|
||||
#[derive(InputVisitable)]
|
||||
struct ReadPost {
|
||||
#[skip_visit]
|
||||
path: PathBuf,
|
||||
}
|
||||
impl Rule for ReadPost {
|
||||
|
@ -2,7 +2,8 @@ use std::collections::HashMap;
|
||||
|
||||
use compute_graph::{
|
||||
builder::GraphBuilder,
|
||||
rule::{DynamicInput, DynamicNodeFactory, DynamicRule, Input, InputVisitable, Rule},
|
||||
input::{DynamicInput, Input, InputVisitable},
|
||||
rule::{DynamicNodeFactory, DynamicRule, Rule},
|
||||
synchronicity::Asynchronous,
|
||||
};
|
||||
use serde::Serialize;
|
||||
@ -77,6 +78,7 @@ impl DynamicRule for MakePostsByTags {
|
||||
#[derive(InputVisitable)]
|
||||
struct PostsByTag {
|
||||
posts: DynamicInput<ReadPostOutput>,
|
||||
#[skip_visit]
|
||||
tag: Tag,
|
||||
}
|
||||
impl Rule for PostsByTag {
|
||||
@ -131,7 +133,7 @@ struct Entry {
|
||||
#[derive(InputVisitable)]
|
||||
struct MakeWriteTagPages {
|
||||
tags: DynamicInput<TagAndPosts>,
|
||||
#[ignore_input]
|
||||
#[skip_visit]
|
||||
templates: Input<Templates>,
|
||||
build_context_factory: DynamicNodeFactory<String, Context>,
|
||||
render_factory: DynamicNodeFactory<String, ()>,
|
||||
|
@ -6,8 +6,9 @@ use std::{
|
||||
use chrono::Local;
|
||||
use compute_graph::{
|
||||
builder::GraphBuilder,
|
||||
input::{Input, InputVisitable},
|
||||
node::NodeValue,
|
||||
rule::{Input, InputVisitable, Rule},
|
||||
rule::Rule,
|
||||
synchronicity::Asynchronous,
|
||||
};
|
||||
use log::error;
|
||||
@ -39,7 +40,11 @@ pub fn make_graph(
|
||||
}
|
||||
|
||||
#[derive(InputVisitable)]
|
||||
pub struct AddTemplate(String, PathBuf, Input<Templates>);
|
||||
pub struct AddTemplate(
|
||||
#[skip_visit] String,
|
||||
#[skip_visit] PathBuf,
|
||||
Input<Templates>,
|
||||
);
|
||||
impl AddTemplate {
|
||||
pub fn new(name: &str, path: PathBuf, base: Input<Templates>) -> Self {
|
||||
Self(name.into(), path, base)
|
||||
@ -86,9 +91,11 @@ static CB: Lazy<u64> = Lazy::new(|| {
|
||||
.as_secs()
|
||||
});
|
||||
|
||||
#[derive(InputVisitable)]
|
||||
pub struct BuildTemplateContext<T, F> {
|
||||
permalink: TemplatePermalink,
|
||||
input: Input<T>,
|
||||
#[skip_visit]
|
||||
func: 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> {
|
||||
type Output = Context;
|
||||
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 {
|
||||
Constant(&'static str),
|
||||
ConstantOwned(String),
|
||||
Constant(#[skip_visit] &'static str),
|
||||
ConstantOwned(#[skip_visit] String),
|
||||
Dynamic(Input<String>),
|
||||
}
|
||||
impl From<&'static str> for TemplatePermalink {
|
||||
@ -147,23 +147,14 @@ impl From<Input<String>> for TemplatePermalink {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(InputVisitable)]
|
||||
pub struct RenderTemplate {
|
||||
#[skip_visit]
|
||||
pub name: &'static str,
|
||||
pub output_path: TemplateOutputPath,
|
||||
pub templates: Input<Templates>,
|
||||
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 {
|
||||
type Output = ();
|
||||
|
||||
@ -190,8 +181,9 @@ impl Rule for RenderTemplate {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(InputVisitable)]
|
||||
pub enum TemplateOutputPath {
|
||||
Constant(PathBuf),
|
||||
Constant(#[skip_visit] PathBuf),
|
||||
Dynamic(Input<PathBuf>),
|
||||
}
|
||||
impl<'a> From<&'a str> for TemplateOutputPath {
|
||||
|
@ -8,9 +8,12 @@ use std::io::{BufWriter, Write};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use compute_graph::builder::GraphBuilder;
|
||||
use compute_graph::rule::{DynamicInput, Input, InputVisitable, Rule};
|
||||
use compute_graph::synchronicity::Synchronicity;
|
||||
use compute_graph::{
|
||||
builder::GraphBuilder,
|
||||
input::{DynamicInput, Input, InputVisitable},
|
||||
rule::Rule,
|
||||
synchronicity::Synchronicity,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
pub fn output_writer(path: impl AsRef<Path>) -> Result<impl Write, std::io::Error> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user