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