Operations

An Operation describes the dependencies of a Derived Node, and how those dependencies are used to update the internal Value.

In Depends, you define an Operation by implementing the UpdateDerived trait for a struct. This struct, marked with #[derive(Operation)], symbolizes the operation itself.

Here is an example of an Operation that squares a number:

#[derive(Value, Hash)]
pub struct SomeNumber {
    pub value: i32,
}

#[derive(Operation)]
pub struct Square;

impl UpdateDerived for Square {
    // `SingleRef` is short-hand for a shared reference to a single
    // dependency of type `SomeNumber`.
    type Input<'a> = SingleRef<'a, SomeNumber>;
    // `TargetMut` is short-hand for a mutable reference to `SomeNumber`.
    type Target<'a> = TargetMut<'a, SomeNumber>;

    fn update_derived(
        input: Self::Input<'_>,
        mut target: Self::Target<'_>,
    ) -> Result<(), EarlyExit> {
        target.value = input.value.pow(2);
        Ok(())
    }
}

Square is an Operation that takes a SingleRef to SomeNumber as input, and a TargetMut of SomeNumber as its target.

The update_derived method describes how to use the dependencies (the input) to update the internal value of the derived node (the target).

This operation will take a number and square it. In practice, operations can be any function that transforms the inputs into a new state for the target.

Early Exit

For some graphs, it may be desirable to exit early from an operation. This can be achieved by returning Err(EarlyExit) from the update_derived method.

#[derive(Operation)]
pub struct CheckAllIsOk;

impl UpdateDerived for CheckAllIsOk {
    type Input<'a> = SingleRef<'a, SomeNumber>;
    type Target<'a> = TargetMut<'a, SomeNumber>;

    fn update_derived(
        input: Self::Input<'_>,
        mut target: Self::Target<'_>,
    ) -> Result<(), EarlyExit> {
        if input.value >= 100 {
            return Err(EarlyExit::new("Things are a bit too spicy!"));
        }
        target.value = input.value;
        Ok(())
    }
}

This is particularly useful if you want to short-circuit a costly computation when it's clear that the result is no longer relevant.

Early exit will be triggered by the first value which returns an Err, therefore ordering is important.

Be aware that nodes after the node which prompts the exit will not receive data during the execution, and will miss any transient state (that which will be cleaned) which is cleared up by the time they are next updated.