152 lines
5.2 KiB
Rust
152 lines
5.2 KiB
Rust
use crate::node::NodeValue;
|
|
use crate::NodeId;
|
|
pub use compute_graph_macros::InputVisitable;
|
|
use std::cell::{Ref, RefCell};
|
|
use std::ops::Deref;
|
|
use std::rc::Rc;
|
|
|
|
/// A rule produces a value for a graph node using its [`Input`]s.
|
|
///
|
|
/// A rule for addition could be implemented like so:
|
|
///
|
|
/// ```rust
|
|
/// # use compute_graph::rule::{Rule, Input, InputVisitable};
|
|
/// #[derive(InputVisitable)]
|
|
/// struct Add(Input<i32>, Input<i32>);
|
|
///
|
|
/// impl Rule for Add {
|
|
/// type Output = i32;
|
|
///
|
|
/// fn evaluate(&mut self) -> Self::Output {
|
|
/// *self.input_0() + *self.input_1()
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
pub trait Rule: InputVisitable + 'static {
|
|
/// The type of the output value of the rule.
|
|
type Output: NodeValue;
|
|
|
|
/// Produces the value of this rule using its inputs.
|
|
///
|
|
/// Note that the receiver of this method is a mutable reference to the rule itself. Rules are permitted
|
|
/// to have internal state that they modify during evaluation.
|
|
///
|
|
/// The following guarantees are made about rule evaluation:
|
|
/// 1. A rule will only be evaluated when one or more of its dependencies has changed. Note that "changed"
|
|
/// referes to returning `false` from [`NodeValue::node_value_eq`] for the dependency.
|
|
/// 2. A rule will never be evaluated before _all_ of its dependencies up-to-date. That is, it will never
|
|
/// be evaluated with mix of valid and invalid dependencies.
|
|
fn evaluate(&mut self) -> Self::Output;
|
|
}
|
|
|
|
/// A rule produces a value for a graph node asynchronously.
|
|
///
|
|
/// ```rust
|
|
/// # use compute_graph::rule::{AsyncRule, Input, InputVisitable};
|
|
/// # async fn do_async_work(_: i32) -> i32 { 0 }
|
|
/// #[derive(InputVisitable)]
|
|
/// struct AsyncMath(Input<i32>);
|
|
///
|
|
/// impl AsyncRule for AsyncMath {
|
|
/// type Output = i32;
|
|
///
|
|
/// async fn evaluate(&mut self) -> Self::Output {
|
|
/// do_async_work(*self.input_0()).await
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
pub trait AsyncRule: InputVisitable + 'static {
|
|
/// The type of the output value of the rule.
|
|
type Output: NodeValue;
|
|
|
|
/// Asynchronously produces the value of this rule using its inputs.
|
|
///
|
|
/// See [`Rule::evaluate`] for additional details; the same considerations apply.
|
|
async fn evaluate(&mut self) -> Self::Output;
|
|
}
|
|
|
|
/// 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.
|
|
#[derive(Debug)]
|
|
pub struct Input<T> {
|
|
pub(crate) node_idx: 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")
|
|
})
|
|
}
|
|
}
|
|
|
|
// 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: self.node_idx,
|
|
value: Rc::clone(&self.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>);
|
|
}
|
|
|
|
/// 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`.
|
|
pub struct ConstantRule<T>(T);
|
|
|
|
impl<T> ConstantRule<T> {
|
|
/// Constructs a new constant rule with the given value.
|
|
pub fn new(value: T) -> Self {
|
|
Self(value)
|
|
}
|
|
}
|
|
|
|
impl<T: Clone + NodeValue> Rule for ConstantRule<T> {
|
|
type Output = T;
|
|
|
|
fn evaluate(&mut self) -> Self::Output {
|
|
self.0.clone()
|
|
}
|
|
}
|
|
|
|
impl<T> InputVisitable for ConstantRule<T> {
|
|
fn visit_inputs(&self, _visitor: &mut impl InputVisitor) {}
|
|
}
|