expand treetest documentation and rationale

This commit is contained in:
Michael Mikovsky
2026-04-24 17:34:05 -06:00
parent 943c820f30
commit ef62befe9a
9 changed files with 145 additions and 1 deletions
+3
View File
@@ -14,6 +14,8 @@ impl Simulation {
for node_id in 0..self.nodes.len() {
match self.nodes[node_id].rx.try_recv() {
Ok(envelope) => {
// Record ingress before handing the frame to the protocol
// runtime so the trace shows the channel-level hop too.
self.record_trace(
NodeId(node_id),
format!("received frame via {:?}", envelope.ingress),
@@ -36,6 +38,7 @@ impl Simulation {
/// Runs frames until the network becomes idle.
pub fn drain(&mut self) -> Result<usize, SimError> {
// Count steps so callers can surface how much work one action caused.
let mut steps = 0;
while self.step()? {
steps += 1;
+31
View File
@@ -1,4 +1,7 @@
//! Construction and mode-management helpers for the simulator.
//!
//! These helpers are kept separate from runtime packet flow so scenario boot and
//! mode transitions remain easy to read and test in isolation.
use std::collections::{BTreeMap, VecDeque};
@@ -12,12 +15,28 @@ use super::types::{ChatSession, SimError, SimNode, Simulation};
impl Simulation {
/// Creates a fresh simulation from a scenario definition.
///
/// # Example
/// ```rust
/// use treetest::{scenarios::built_in_scenarios, sim::Simulation};
///
/// let scenario = built_in_scenarios().into_iter().next().unwrap();
/// let simulation = Simulation::new(scenario).unwrap();
/// assert_eq!(simulation.node(treetest::model::NodeId(0)).display_path(), "/");
/// ```
pub fn new(scenario: ScenarioDefinition) -> Result<Self, SimError> {
// Flatten the recursive scenario description once so the rest of the
// simulator can address nodes by stable ids.
let tree = DemoTree::from_root(&scenario.root);
let mut nodes = Vec::with_capacity(tree.nodes.len());
for demo_node in &tree.nodes {
// Each endpoint gets one mailbox pair. The simulator never opens a
// real socket, so every hop is just channel delivery.
let (tx, rx) = unbounded();
// Materialize child routes up front so the protocol runtime can make
// longest-prefix decisions without consulting the demo model again.
let children = demo_node
.children
.iter()
@@ -26,6 +45,8 @@ impl Simulation {
state: ConnectionState::Registered,
})
.collect::<Vec<_>>();
// Translate demo leaf metadata into protocol-runtime leaf specs.
let leaves = demo_node
.leaves
.iter()
@@ -37,6 +58,9 @@ impl Simulation {
},
})
.collect::<Vec<_>>();
// Parents are stored by path because the protocol runtime reasons in
// terms of endpoint paths rather than UI node ids.
let parent_path = demo_node
.parent
.map(|parent_id| tree.node(parent_id).path.clone());
@@ -49,6 +73,7 @@ impl Simulation {
.map_err(|error| SimError::Protocol(error.to_string()))?;
}
// Store the runtime endpoint alongside topology and mailbox state.
nodes.push(SimNode {
parent: demo_node.parent,
children: demo_node.children.clone(),
@@ -58,6 +83,8 @@ impl Simulation {
});
}
// The root starts with only its own configuration plus direct-child
// awareness, which realistic mode later uses as its initial knowledge.
let root_knowledge = RootKnowledge::new(&tree);
Ok(Self {
@@ -65,6 +92,7 @@ impl Simulation {
tree,
nodes,
root_id: NodeId(0),
// Tick counting starts at one so trace output reads naturally.
next_tick: 1,
trace: VecDeque::new(),
recorded_events: Vec::new(),
@@ -86,6 +114,9 @@ impl Simulation {
}
/// Clears deeper root memory and switches the inspector into realistic mode.
///
/// Rationale: this mirrors a host that only retains locally configured and
/// one-hop information until it learns more by introspection or traffic.
pub fn enable_realistic_mode_with_memory_reset(&mut self) {
self.root_knowledge.clear_deeper_than_one_hop();
self.inspector_mode = InspectorMode::Realistic;