Graph
Record the dependency relationships and operational results of nodes in the system
Graph
is dagrs's main body. 'Graph
is a network that satisfies FBP logic, provides node dependencies, and runs all of its nodes completely asynchronously.
Graph's structure
pub struct Graph {
/// Define the Net struct that holds all nodes
nodes: HashMap<NodeId, Arc<Mutex<dyn Node>>>,
/// Store a task's running result.Execution results will be read
/// and written asynchronously by several threads.
execute_states: HashMap<NodeId, Arc<ExecState>>,
/// Count all the nodes
node_count: usize,
/// Global environment variables for this Net job.
/// It should be set before the Net job runs.
env: Arc<EnvVar>,
/// Mark whether the net task can continue to execute.
/// When an error occurs during the execution of any task, This flag will still be set to true
is_active: Arc<AtomicBool>,
/// Node's in_degree, used for check loop
in_degree: HashMap<NodeId, usize>,
}
The above fields provide information such as the storage of node operation results, node information contained in the graph, and graph operation status.
Create a Graph
A Graph
contains multiple nodes, which can be added as long as they implement the [Node
] trait.
add node
Firstly, it is necessary to initialize a node that implements the Node
trait. Let's use DefaultNode
as an example.
use dagrs::NodeTable;
let mut node_table = NodeTable::new();
let node_name = "Node X";
let node = DefaultNode::new(NodeName::from(node_name), &mut node_table);
let node1_name = "Node Y";
let node1 = DefaultNode::with_action(
NodeName::from(node1_name),
HelloAction::new(),
&mut node_table,
);
After calling the new function to create a default Graph
, it is necessary to add nodes to the Graph
, Graph
provides the add_node function to add any node that implements the Node
trait.
let mut graph = Graph::new();
graph.add_node(node);
add edge between nodes
Once the nodes are added to the Graph
, it is necessary to establish relationships between them by adding edges. The Graph
provides the add_edge function to define a directed edge between two nodes, where the source node is connected to one or more target nodes.
For example, after initializing and adding two nodes (node
and node1
) to the Graph
, you can establish an edge from node
to node1
as follows:
let node_id = node.id();
let node1_id = node1.id();
graph.add_edge(node_id, vec![node1_id]);
Here, node.id()
and node1.id()
retrieve the unique identifiers of the nodes node
and node1
, respectively. The add_edge
function is called with the node_id
as the source node and a vector containing node1_id
as the target node(s). This creates a directed edge from node
to node1
.
If you need to connect a node to multiple target nodes, you can include all target node IDs in the vector, like this:
let target_nodes = vec![node1_id, node2_id, node3_id];
graph.add_edge(node_id, target_nodes);
This flexibility allows you to construct complex directed graphs by defining edges between various nodes.
Execute a Graph and Retrieve Execution Results
After adding nodes and edges to the Graph
, it is ready to be executed. The Graph
provides the start function to begin the execution process, ensuring that all nodes are processed in the correct order based on their dependencies. Once execution is complete, you can retrieve the results produced by individual nodes.
For example, to start a Graph
and obtain the execution result of a specific node (node1
), you can perform the following steps:
graph.start();
let out = graph.execute_states[&node1_id].get_output().unwrap();
let out: &String = out.get().unwrap();
assert_eq!(out, "Hello world");
- Start the Graph: The
start
function begins the execution of theGraph
, processing all nodes in a manner that respects their dependencies and actions. - Access Execution State: After execution, the
execute_states
field of theGraph
contains the state of each node. The state for a specific node can be accessed using its unique identifier (node1_id
in this case). - Retrieve the Node Output:
- Call
get_output()
on the node's execution state to obtain its output, which is wrapped in anOption
. - Use
unwrap()
to extract the actual output value if it exists.
- Call
- Validate the Output: In this example, the output is expected to be of type
String
, so it is further extracted usingget()
. The result can then be validated with assertions, such as checking if the output equals"Hello world"
.
This process ensures that the Graph
is executed properly, and the output of individual nodes can be reliably retrieved and verified.