Skip to content

Commit f5c8526

Browse files
committed
feat: new exec loop
1 parent 46bd289 commit f5c8526

7 files changed

Lines changed: 622 additions & 0 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//! Runtime execution internals.
2+
//!
3+
//! These types are owned by the execution engine lifecycle and are not part of
4+
//! the transport-level flow contracts.
5+
6+
pub mod registry;
7+
pub mod render;
8+
pub mod store;
9+
pub mod trace;
10+
pub mod tracer;
11+
pub mod value_store;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//! Registry metadata types for runtime handler discovery.
2+
3+
use std::collections::HashMap;
4+
5+
use crate::types::execution::signature::HandlerSignature;
6+
7+
/// Static registry entry metadata.
8+
#[derive(Debug, Clone, PartialEq, Eq)]
9+
pub struct HandlerRegistration {
10+
pub handler_id: String,
11+
pub signature: HandlerSignature,
12+
pub description: Option<String>,
13+
}
14+
15+
/// Read-only handler metadata registry.
16+
#[derive(Debug, Clone, Default)]
17+
pub struct HandlerRegistry {
18+
pub handlers: HashMap<String, HandlerRegistration>,
19+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
//! Human-readable trace renderer.
2+
3+
use std::collections::HashMap;
4+
5+
use crate::runtime::execution::trace::{ArgKind, EdgeKind, ExecFrame, Outcome, TraceRun};
6+
7+
fn frame_micros(frame: &ExecFrame) -> Option<u128> {
8+
frame
9+
.end
10+
.map(|end| end.duration_since(frame.start).as_micros())
11+
}
12+
13+
/// Render a trace as plain-text tree output.
14+
pub fn render_trace(run: &TraceRun) -> String {
15+
let mut by_id: HashMap<u64, &ExecFrame> = HashMap::new();
16+
for frame in &run.frames {
17+
by_id.insert(frame.frame_id, frame);
18+
}
19+
20+
let mut output = String::new();
21+
if let Some(total_us) = total_duration_us(&run.frames) {
22+
output.push_str(&format!("Total: {}us\n", total_us));
23+
}
24+
render_frame(run.root, &by_id, "", true, &mut output);
25+
if let Some(total_us) = total_duration_us(&run.frames) {
26+
output.push_str(&format!("Summary: total_time={}us\n", total_us));
27+
}
28+
output
29+
}
30+
31+
fn render_frame(
32+
id: u64,
33+
by_id: &HashMap<u64, &ExecFrame>,
34+
prefix: &str,
35+
is_last: bool,
36+
output: &mut String,
37+
) {
38+
let frame = by_id[&id];
39+
40+
let branch = if prefix.is_empty() {
41+
""
42+
} else if is_last {
43+
"\\- "
44+
} else {
45+
"+- "
46+
};
47+
48+
let duration = frame_micros(frame)
49+
.map(|us| format!(" ({}us)", us))
50+
.unwrap_or_default();
51+
52+
output.push_str(&format!(
53+
"{prefix}{branch}#{frame_id} node={node_id} fn={function}{duration}\n",
54+
prefix = prefix,
55+
branch = branch,
56+
frame_id = frame.frame_id,
57+
node_id = frame.node_id,
58+
function = frame.function_name,
59+
duration = duration
60+
));
61+
62+
for arg in &frame.args {
63+
let continuation = if prefix.is_empty() || is_last {
64+
" "
65+
} else {
66+
"| "
67+
};
68+
69+
let kind = match &arg.kind {
70+
ArgKind::Literal => "lit".to_string(),
71+
ArgKind::Reference { reference, hit } => {
72+
let suffix = if *hit { "hit" } else { "miss" };
73+
format!("ref({:?}, {})", reference, suffix)
74+
}
75+
ArgKind::Thunk {
76+
node_id,
77+
eager,
78+
executed,
79+
} => {
80+
let mode = if *eager { "eager" } else { "lazy" };
81+
let exec = if *executed { "executed" } else { "deferred" };
82+
format!("thunk(node={}, {}, {})", node_id, mode, exec)
83+
}
84+
};
85+
86+
output.push_str(&format!(
87+
"{prefix}{continuation} arg[{index}] {kind:<24} {preview}\n",
88+
prefix = prefix,
89+
continuation = continuation,
90+
index = arg.index,
91+
kind = kind,
92+
preview = arg.preview
93+
));
94+
}
95+
96+
let outcome = match &frame.outcome {
97+
Some(Outcome::Success { value_preview }) => format!("[SUCCESS] {}", value_preview),
98+
Some(Outcome::Failure { error_preview }) => format!("[FAILURE] {}", error_preview),
99+
Some(Outcome::Return { value_preview }) => format!("[RETURN] {}", value_preview),
100+
Some(Outcome::Respond { value_preview }) => format!("[RESPOND] {}", value_preview),
101+
Some(Outcome::Stop) => "[STOP]".to_string(),
102+
None => "...".to_string(),
103+
};
104+
105+
let continuation = if prefix.is_empty() || is_last {
106+
" "
107+
} else {
108+
"| "
109+
};
110+
output.push_str(&format!(
111+
"{prefix}{continuation} => {outcome}\n",
112+
prefix = prefix,
113+
continuation = continuation,
114+
outcome = outcome
115+
));
116+
117+
if frame.children.is_empty() {
118+
return;
119+
}
120+
121+
let mut runtime_idx = 0usize;
122+
for (idx, (edge, child_id)) in frame.children.iter().enumerate() {
123+
let edge_last = idx + 1 == frame.children.len();
124+
let edge_branch = if edge_last { "\\- " } else { "+- " };
125+
126+
let edge_text = match edge {
127+
EdgeKind::Next => "NEXT".to_string(),
128+
EdgeKind::EagerCall { arg_index } => format!("eager(arg#{})", arg_index),
129+
EdgeKind::RuntimeCall { label } => {
130+
runtime_idx += 1;
131+
match label {
132+
Some(label_text) => format!("runtime(call #{}) {}", runtime_idx, label_text),
133+
None => format!("runtime(call #{})", runtime_idx),
134+
}
135+
}
136+
};
137+
138+
output.push_str(&format!(
139+
"{prefix}{edge_branch}{edge_text}\n",
140+
prefix = prefix,
141+
edge_branch = edge_branch,
142+
edge_text = edge_text
143+
));
144+
145+
let child_prefix = format!("{}{}", prefix, if edge_last { " " } else { "| " });
146+
render_frame(*child_id, by_id, &child_prefix, true, output);
147+
}
148+
}
149+
150+
fn total_duration_us(frames: &[ExecFrame]) -> Option<u128> {
151+
let mut start = None;
152+
let mut end = None;
153+
154+
for frame in frames {
155+
start = Some(match start {
156+
Some(current) if frame.start > current => current,
157+
Some(_) | None => frame.start,
158+
});
159+
160+
if let Some(frame_end) = frame.end {
161+
end = Some(match end {
162+
Some(current) if frame_end < current => current,
163+
Some(_) | None => frame_end,
164+
});
165+
}
166+
}
167+
168+
match (start, end) {
169+
(Some(start_time), Some(end_time)) => Some(end_time.duration_since(start_time).as_micros()),
170+
_ => None,
171+
}
172+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//! Mutable execution state for a single flow run.
2+
3+
use std::collections::HashMap;
4+
5+
use tucana::shared::Value;
6+
7+
use crate::types::errors::runtime_error::RuntimeError;
8+
use crate::types::execution::ids::{FrameId, NodeId};
9+
10+
/// Runtime outcome persisted per node.
11+
#[derive(Debug, Clone)]
12+
pub enum NodeOutcome {
13+
Success(Value),
14+
Failure(RuntimeError),
15+
}
16+
17+
/// Input slot key for runtime-provided temporary inputs (for iterators/predicates).
18+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19+
pub struct InputSlotKey {
20+
pub node_id: NodeId,
21+
pub parameter_index: i32,
22+
pub input_index: i32,
23+
}
24+
25+
/// Store that captures mutable runtime execution state.
26+
#[derive(Debug, Clone, Default)]
27+
pub struct ExecutionStore {
28+
pub node_outcomes: HashMap<NodeId, NodeOutcome>,
29+
pub input_slots: HashMap<InputSlotKey, Value>,
30+
pub flow_input: Option<Value>,
31+
pub current_node: Option<NodeId>,
32+
pub frame_stack: Vec<FrameId>,
33+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//! Runtime execution trace model.
2+
//!
3+
//! This model captures node frames, argument resolution, control-flow edges,
4+
//! and final outcomes for one execution run.
5+
6+
use std::time::Instant;
7+
8+
/// Relationship between two execution frames.
9+
#[derive(Debug, Clone)]
10+
pub enum EdgeKind {
11+
/// Sequential flow transition via `next_node_id`.
12+
Next,
13+
/// Eager argument child execution.
14+
EagerCall { arg_index: usize },
15+
/// Lazy runtime callback child execution.
16+
RuntimeCall { label: Option<String> },
17+
}
18+
19+
/// Argument classification for tracing.
20+
#[derive(Debug, Clone)]
21+
pub enum ArgKind {
22+
Literal,
23+
Reference {
24+
reference: ReferenceKind,
25+
hit: bool,
26+
},
27+
Thunk {
28+
node_id: i64,
29+
eager: bool,
30+
executed: bool,
31+
},
32+
}
33+
34+
/// Reference source kind for argument tracing.
35+
#[derive(Debug, Clone)]
36+
pub enum ReferenceKind {
37+
Result {
38+
node_id: i64,
39+
},
40+
InputType {
41+
node_id: i64,
42+
input_index: i64,
43+
parameter_index: i64,
44+
},
45+
FlowInput,
46+
Empty,
47+
}
48+
49+
/// One traced argument on a frame.
50+
#[derive(Debug, Clone)]
51+
pub struct ArgTrace {
52+
pub index: usize,
53+
pub kind: ArgKind,
54+
pub preview: String,
55+
}
56+
57+
/// Final outcome of a frame.
58+
#[derive(Debug, Clone)]
59+
pub enum Outcome {
60+
Success { value_preview: String },
61+
Failure { error_preview: String },
62+
Return { value_preview: String },
63+
Respond { value_preview: String },
64+
Stop,
65+
}
66+
67+
/// One executed node invocation.
68+
#[derive(Debug, Clone)]
69+
pub struct ExecFrame {
70+
pub frame_id: u64,
71+
pub node_id: i64,
72+
pub function_name: String,
73+
pub args: Vec<ArgTrace>,
74+
pub outcome: Option<Outcome>,
75+
pub start: Instant,
76+
pub end: Option<Instant>,
77+
pub children: Vec<(EdgeKind, u64)>,
78+
}
79+
80+
/// Trace data for a full execution.
81+
#[derive(Debug, Clone)]
82+
pub struct TraceRun {
83+
pub frames: Vec<ExecFrame>,
84+
pub root: u64,
85+
}

0 commit comments

Comments
 (0)