2024-11-03 01:27:51 -04:00

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) {}
}