Skip to main content

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, MaxSalvos, Node, PacketCount, Port, PortRef, PortSlotSpec, PortState, PortType,
12    SalvoCondition, SalvoConditionTerm,
13};
14use netrun_sim::net::{
15    NetAction, NetActionResponse, NetActionResponseData, NetSim, 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 = NetSim::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(
50        packet_id.clone(),
51        edge_a_b,
52    ));
53    println!("Placed packet on edge A -> B");
54
55    // Run the network - packet moves to B's input port and triggers an epoch
56    net.run_until_blocked();
57    println!("Ran network until blocked");
58
59    // Check for startable epochs
60    let startable = net.get_startable_epochs();
61    println!("Startable epochs: {}", startable.len());
62
63    if let Some(epoch_id) = startable.first() {
64        // Start the epoch
65        match net.do_action(&NetAction::StartEpoch(epoch_id.clone())) {
66            NetActionResponse::Success(NetActionResponseData::StartedEpoch(epoch), _) => {
67                println!("Started epoch {} on node {}", epoch.id, epoch.node_name);
68
69                // In a real scenario, external code would process the packet here
70                // For this example, we'll just consume it and create an output
71
72                // Consume the input packet
73                net.do_action(&NetAction::ConsumePacket(packet_id));
74                println!("Consumed input packet");
75
76                // Create an output packet
77                let output_packet =
78                    match net.do_action(&NetAction::CreatePacket(Some(epoch.id.clone()))) {
79                        NetActionResponse::Success(NetActionResponseData::Packet(id), _) => id,
80                        _ => panic!("Failed to create output packet"),
81                    };
82                println!("Created output packet: {}", output_packet);
83
84                // Load it into the output port
85                net.do_action(&NetAction::LoadPacketIntoOutputPort(
86                    output_packet.clone(),
87                    "out".to_string(),
88                ));
89                println!("Loaded packet into output port");
90
91                // Send the output salvo
92                net.do_action(&NetAction::SendOutputSalvo(
93                    epoch.id.clone(),
94                    "default".to_string(),
95                ));
96                println!("Sent output salvo - packet is now on edge B -> C");
97
98                // Finish the epoch
99                net.do_action(&NetAction::FinishEpoch(epoch.id));
100                println!("Finished epoch");
101
102                // Run the network again - packet moves to C
103                net.run_until_blocked();
104                println!("Ran network until blocked again");
105
106                // Check for new startable epochs at C
107                let startable_c = net.get_startable_epochs();
108                println!(
109                    "New startable epochs (should be at C): {}",
110                    startable_c.len()
111                );
112            }
113            _ => panic!("Failed to start epoch"),
114        }
115    }
116
117    println!("\nLinear flow example complete!");
118}
119
120/// Creates a linear graph: A -> B -> C
121fn create_linear_graph() -> Graph {
122    let nodes = vec![
123        create_node("A", vec![], vec!["out"]),
124        create_node("B", vec!["in"], vec!["out"]),
125        create_node("C", vec!["in"], vec![]),
126    ];
127
128    let edges = vec![
129        create_edge("A", "out", "B", "in"),
130        create_edge("B", "out", "C", "in"),
131    ];
132
133    let graph = Graph::new(nodes, edges);
134    assert!(graph.validate().is_empty(), "Graph validation failed");
135    graph
136}
137
138fn create_node(name: &str, in_ports: Vec<&str>, out_ports: Vec<&str>) -> Node {
139    let in_ports_map: HashMap<String, Port> = in_ports
140        .iter()
141        .map(|p| {
142            (
143                p.to_string(),
144                Port {
145                    slots_spec: PortSlotSpec::Infinite,
146                },
147            )
148        })
149        .collect();
150
151    let out_ports_map: HashMap<String, Port> = out_ports
152        .iter()
153        .map(|p| {
154            (
155                p.to_string(),
156                Port {
157                    slots_spec: PortSlotSpec::Infinite,
158                },
159            )
160        })
161        .collect();
162
163    // Default input salvo condition: trigger when any input port is non-empty
164    let mut in_salvo_conditions = HashMap::new();
165    if !in_ports.is_empty() {
166        in_salvo_conditions.insert(
167            "default".to_string(),
168            SalvoCondition {
169                max_salvos: MaxSalvos::Finite(1),
170                ports: in_ports
171                    .iter()
172                    .map(|s| (s.to_string(), PacketCount::All))
173                    .collect(),
174                term: SalvoConditionTerm::Port {
175                    port_name: in_ports[0].to_string(),
176                    state: PortState::NonEmpty,
177                },
178            },
179        );
180    }
181
182    // Default output salvo condition: can always send when port is non-empty
183    let mut out_salvo_conditions = HashMap::new();
184    if !out_ports.is_empty() {
185        out_salvo_conditions.insert(
186            "default".to_string(),
187            SalvoCondition {
188                max_salvos: MaxSalvos::Infinite,
189                ports: out_ports
190                    .iter()
191                    .map(|s| (s.to_string(), PacketCount::All))
192                    .collect(),
193                term: SalvoConditionTerm::Port {
194                    port_name: out_ports[0].to_string(),
195                    state: PortState::NonEmpty,
196                },
197            },
198        );
199    }
200
201    Node {
202        name: name.to_string(),
203        in_ports: in_ports_map,
204        out_ports: out_ports_map,
205        in_salvo_conditions,
206        out_salvo_conditions,
207    }
208}
209
210fn create_edge(src_node: &str, src_port: &str, tgt_node: &str, tgt_port: &str) -> Edge {
211    Edge {
212        source: PortRef {
213            node_name: src_node.to_string(),
214            port_type: PortType::Output,
215            port_name: src_port.to_string(),
216        },
217        target: PortRef {
218            node_name: tgt_node.to_string(),
219            port_type: PortType::Input,
220            port_name: tgt_port.to_string(),
221        },
222    }
223}