linear_flow/
linear_flow.rs

1//! Example: Linear packet flow through A -> B -> C
2//!
3//! This example demonstrates:
4//! - Creating a simple linear graph
5//! - Injecting a packet
6//! - Running the network until blocked
7//! - Starting epochs and processing packets
8//! - Sending output salvos to continue flow
9
10use netrun_sim::graph::{
11    Edge, Graph, Node, Port, PortRef, PortSlotSpec, PortState, PortType,
12    SalvoCondition, SalvoConditionTerm,
13};
14use netrun_sim::net::{
15    Net, NetAction, NetActionResponse, NetActionResponseData, PacketLocation,
16};
17use std::collections::HashMap;
18
19fn main() {
20    // Create a linear graph: A -> B -> C
21    let graph = create_linear_graph();
22    println!("Created graph with {} nodes", graph.nodes().len());
23
24    // Create a network from the graph
25    let mut net = Net::new(graph);
26
27    // Create a packet outside the network
28    let packet_id = match net.do_action(&NetAction::CreatePacket(None)) {
29        NetActionResponse::Success(NetActionResponseData::Packet(id), _) => {
30            println!("Created packet: {}", id);
31            id
32        }
33        _ => panic!("Failed to create packet"),
34    };
35
36    // Transport packet to the edge A -> B
37    let edge_a_b = PacketLocation::Edge(Edge {
38        source: PortRef {
39            node_name: "A".to_string(),
40            port_type: PortType::Output,
41            port_name: "out".to_string(),
42        },
43        target: PortRef {
44            node_name: "B".to_string(),
45            port_type: PortType::Input,
46            port_name: "in".to_string(),
47        },
48    });
49    net.do_action(&NetAction::TransportPacketToLocation(packet_id.clone(), edge_a_b));
50    println!("Placed packet on edge A -> B");
51
52    // Run the network - packet moves to B's input port and triggers an epoch
53    net.do_action(&NetAction::RunNetUntilBlocked);
54    println!("Ran network until blocked");
55
56    // Check for startable epochs
57    let startable = net.get_startable_epochs();
58    println!("Startable epochs: {}", startable.len());
59
60    if let Some(epoch_id) = startable.first() {
61        // Start the epoch
62        match net.do_action(&NetAction::StartEpoch(epoch_id.clone())) {
63            NetActionResponse::Success(NetActionResponseData::StartedEpoch(epoch), _) => {
64                println!("Started epoch {} on node {}", epoch.id, epoch.node_name);
65
66                // In a real scenario, external code would process the packet here
67                // For this example, we'll just consume it and create an output
68
69                // Consume the input packet
70                net.do_action(&NetAction::ConsumePacket(packet_id));
71                println!("Consumed input packet");
72
73                // Create an output packet
74                let output_packet = match net.do_action(&NetAction::CreatePacket(Some(epoch.id.clone()))) {
75                    NetActionResponse::Success(NetActionResponseData::Packet(id), _) => id,
76                    _ => panic!("Failed to create output packet"),
77                };
78                println!("Created output packet: {}", output_packet);
79
80                // Load it into the output port
81                net.do_action(&NetAction::LoadPacketIntoOutputPort(output_packet.clone(), "out".to_string()));
82                println!("Loaded packet into output port");
83
84                // Send the output salvo
85                net.do_action(&NetAction::SendOutputSalvo(epoch.id.clone(), "default".to_string()));
86                println!("Sent output salvo - packet is now on edge B -> C");
87
88                // Finish the epoch
89                net.do_action(&NetAction::FinishEpoch(epoch.id));
90                println!("Finished epoch");
91
92                // Run the network again - packet moves to C
93                net.do_action(&NetAction::RunNetUntilBlocked);
94                println!("Ran network until blocked again");
95
96                // Check for new startable epochs at C
97                let startable_c = net.get_startable_epochs();
98                println!("New startable epochs (should be at C): {}", startable_c.len());
99            }
100            _ => panic!("Failed to start epoch"),
101        }
102    }
103
104    println!("\nLinear flow example complete!");
105}
106
107/// Creates a linear graph: A -> B -> C
108fn create_linear_graph() -> Graph {
109    let nodes = vec![
110        create_node("A", vec![], vec!["out"]),
111        create_node("B", vec!["in"], vec!["out"]),
112        create_node("C", vec!["in"], vec![]),
113    ];
114
115    let edges = vec![
116        create_edge("A", "out", "B", "in"),
117        create_edge("B", "out", "C", "in"),
118    ];
119
120    let graph = Graph::new(nodes, edges);
121    assert!(graph.validate().is_empty(), "Graph validation failed");
122    graph
123}
124
125fn create_node(name: &str, in_ports: Vec<&str>, out_ports: Vec<&str>) -> Node {
126    let in_ports_map: HashMap<String, Port> = in_ports
127        .iter()
128        .map(|p| (p.to_string(), Port { slots_spec: PortSlotSpec::Infinite }))
129        .collect();
130
131    let out_ports_map: HashMap<String, Port> = out_ports
132        .iter()
133        .map(|p| (p.to_string(), Port { slots_spec: PortSlotSpec::Infinite }))
134        .collect();
135
136    // Default input salvo condition: trigger when any input port is non-empty
137    let mut in_salvo_conditions = HashMap::new();
138    if !in_ports.is_empty() {
139        in_salvo_conditions.insert(
140            "default".to_string(),
141            SalvoCondition {
142                max_salvos: 1,
143                ports: in_ports.iter().map(|s| s.to_string()).collect(),
144                term: SalvoConditionTerm::Port {
145                    port_name: in_ports[0].to_string(),
146                    state: PortState::NonEmpty,
147                },
148            },
149        );
150    }
151
152    // Default output salvo condition: can always send when port is non-empty
153    let mut out_salvo_conditions = HashMap::new();
154    if !out_ports.is_empty() {
155        out_salvo_conditions.insert(
156            "default".to_string(),
157            SalvoCondition {
158                max_salvos: 0, // unlimited
159                ports: out_ports.iter().map(|s| s.to_string()).collect(),
160                term: SalvoConditionTerm::Port {
161                    port_name: out_ports[0].to_string(),
162                    state: PortState::NonEmpty,
163                },
164            },
165        );
166    }
167
168    Node {
169        name: name.to_string(),
170        in_ports: in_ports_map,
171        out_ports: out_ports_map,
172        in_salvo_conditions,
173        out_salvo_conditions,
174    }
175}
176
177fn create_edge(src_node: &str, src_port: &str, tgt_node: &str, tgt_port: &str) -> Edge {
178    Edge {
179        source: PortRef {
180            node_name: src_node.to_string(),
181            port_type: PortType::Output,
182            port_name: src_port.to_string(),
183        },
184        target: PortRef {
185            node_name: tgt_node.to_string(),
186            port_type: PortType::Input,
187            port_name: tgt_port.to_string(),
188        },
189    }
190}