Dependencies

In most real-world applications, you'll have nodes that depend on multiple other nodes. Depends allows you to define complex dependencies using the Dependencies derive macro.

Here's an example where an operation depends on two numbers and multiplies them:

// Outline the inner types of two nodes we wish to depend on.
#[derive(Dependencies)]
pub struct TwoNumbers {
    pub left: SomeNumber,
    pub right: SomeNumber,
}

#[derive(Operation)]
pub struct Multiply;

impl UpdateDerived for Multiply {
    // [TwoNumbersRef] is generated by the [Dependencies] macro, and
    // represents read-references to nodes containing the declared types.
    type Input<'a> = TwoNumbersRef<'a> where Self: 'a;
    type Target<'a> = TargetMut<'a, SomeNumber> where Self: 'a;

    fn update_derived(
        TwoNumbersRef { left, right }: TwoNumbersRef<'_>,
        mut target: TargetMut<'_, SomeNumber>,
    ) -> Result<(), EarlyExit> {
        target.value = left.value * right.value;
        Ok(())
    }
}

By marking TwoNumbers with #[derive(Dependencies)], you tell Depends that TwoNumbers is a set of dependencies. The Dependencies derive macro generates two new types for you: TwoNumbersDep and TwoNumbersRef.

Here's a table that shows the equivalent types for single and multiple dependencies:

Dependency<A>TwoNumbers
ConstructorDependency::<A>::new(a: A)TwoNumbers::init(a: A, b: B)
Initialised TypeDependency<A>TwoNumbersDep<A, B>
Reference TypeSingleRef<'a, A>TwoNumbersRef<'a>

This table describes the equivalent constructors, initialised types, and reference types for single (Dependency<A>) and multiple dependencies (TwoNumbers). You can use TwoNumbersRef<'a> with any operation that requires two NumberValues. This allows you to build complex, flexible dependency graphs.

Checking Specific Dependency State

There are situations where it's useful to know which specific dependencies have caused update_mut to be called. For this reason, the is_dirty method is available on each dependency reference.

#[derive(Value, Hash)]
pub struct StuffToBuy {
    amount: i32,
    last_purchase_time: i64,
}

#[derive(Operation)]
pub struct CheckBankBalance;

#[derive(Dependencies)]
pub struct TimeAndMoney {
    pub time: i64,
    pub money: i32,
}

impl UpdateDerived for CheckBankBalance {
    type Input<'a> = TimeAndMoneyRef<'a> where Self: 'a;
    type Target<'a> = TargetMut<'a, StuffToBuy> where Self: 'a;

    fn update_derived(
        TimeAndMoneyRef { time, money }: Self::Input<'_>,
        mut target: Self::Target<'_>,
    ) -> Result<(), EarlyExit> {
        // Is dirty is a trait implemented on all dependencies to indicate
        // that the inner value of this node has changed since last observed.
        if !money.is_dirty() {
            // Time's always changing, we don't need to check it.
            return Ok(());
        }
        // It's been a while since we've bought anything and we've just been
        // paid.
        if time.value() - target.value().last_purchase_time > 24 * 60 * 60 {
            target.value_mut().last_purchase_time = *time.value();
            target.value_mut().amount = money.value() / 10;
        }
        Ok(())
    }
}

The most common is example is time. It's expected that no node uses methods such as Utc::now(), as this is a side-effect which will result in non-deterministic behaviour.

Instead, you should 'set' the time for the graph by providing it as an Input Node and creating edges to the nodes which require it.