Node

Abstraction of a task.

In Dagrs, we provide the trait Node as the representation of user's task. Node is the basic scheduling unit of Graph.

Node's interfaces

pub trait Node: Send + Sync {
    /// id is the unique identifier of each node, it will be assigned 
    /// by the [`NodeTable`].
    /// when creating a new node, you can find this node through 
    /// this identifier.
    fn id(&self) -> NodeId;
    /// The node's name.
    fn name(&self) -> NodeName;
    /// Input Channels of this node.
    fn input_channels(&mut self) -> &mut InChannels;
    /// Output Channels of this node.
    fn output_channels(&mut self) -> &mut OutChannels;
    /// Execute a run of this node.
    fn run(&mut self, env: Arc<EnvVar>) -> Output;
}

The above methods ensure the communication and execution function when Graph schedules Tasks.

Dynamic execution logics

It is possible to execute different logic on the same data layout. So we provide trait Action as the abstraction of a task's logic.

Here is an example of implementing Action, that simply returns Output::Out containing a String "Hello world".

use dagrs::{Action, InChannels, OutChannels, EnvVar, Output};
/// An implementation of [`Action`] that returns [`Output::Out`] containing a String "Hello world".
#[derive(Default)]
pub struct HelloAction;
#[async_trait]
impl Action for HelloAction {
    async fn run(&self, _: &mut InChannels, _: &OutChannels, _: Arc<EnvVar>) -> Output {
        Output::Out(Some(Content::new("Hello world".to_string())))
    }
}

Create a node

Get a node id

Before creating a new node, let's first get to know the NodeTable - a mapping of nodes' names to their ids. During runtime, we need to figure out the specific node to send/receive messages to/from it. Therefore, each node will be assigned a unique id before the run.

You can call the alloc_id_for method of NodeTable to alloc an id by providing a name, just like the following example.

use dagrs::NodeTable;
let node_table = NodeTable::default();
let name = "Node Name";
let id = node_table.alloc_id_for(name);

Create a default node

If you don't need a Node to hold any extra information, DefaultNode is the best choice.

We can create a DefaultNode with a custom Action and other necceray fields. The example below creates a DefaultNode with a name of "My Node", an action of HelloAction (defined above), a NodeId assigned by the node_table, and empty input_channels and output_channels.

use dagrs::{NodeName, NodeTable, DefaultNode, EmptyAction};

let node_name = "My Node";
let mut node_table = NodeTable::new();
let mut node = DefaultNode::with_action(
    NodeName::from(node_name),
    HelloAction::default(),
    &mut node_table,
);

// Check if node table has key-value pair (node.name, node.id)
assert_eq!(node_table.get(node_name).unwrap(), &node.id());

let env = Arc::new(EnvVar::new(node_table));
let out = node.run(env).get_out().unwrap();
let out: &String = out.get().unwrap();
assert_eq!(out, "Hello world");

Implement a customized node

Don't worry if the DefaultNode can't satisfy your need. You are free to implement a customized Node like the following example:

use dagrs::{Node, NodeId, NodeName, EnvVar, Output, InChannels, OutChannels};

struct HelloNode {
    id: NodeId,
    name: NodeName,
    in_channels: InChannels,
    out_channels: OutChannels,
    /*Put your custom fields here.*/
};

impl Node for HelloNode {
    fn id(&self) -> NodeId {
        self.id
    }

    fn name(&self) -> NodeName {
        self.name.clone()
    }

    fn input_channels(&mut self) -> &mut InChannels {
        &mut self.in_channels
    }

    fn output_channels(&mut self) -> &mut OutChannels {
        &mut self.out_channels
    }

    fn run(&mut self, env: Arc<EnvVar>) -> Output {
        println!("Hello World");
        Output::Out(None)
    }
}

The HelloNode above simply prints "Hello World" to stdout when run. If needed, the execution logic of this node can also be replaced with an Action, just like the DefaultNode.

If you need many custom Nodes like this, it can be annoying to duplicate these necceray fields and methods required by the Node trait. The auto_node macro can help you out.