use concinnity_core::ecs::asset_id::AssetId;
use concinnity_core::ecs::{
ExecutionTrace, FrameTime, TraceEvent, TracePaths, TraceRequest, World,
};
use crate::editor::behavior::path::Path;
use crate::editor::behavior::pulse::{self, NodePulse};
use crate::editor::behavior::trace;
use concinnity_cook::authoring::world::entry_handle;
use crate::editor::hook::EditorHook;
use crate::editor::panels::registry::PanelKey;
use crate::editor::sim;
impl EditorHook {
pub(in crate::editor::hook) fn drive_trace(&mut self, world: &mut World) {
if !self.open[PanelKey::Behavior] && !self.open[PanelKey::Variables] {
world.remove_resource::<TraceRequest>();
self.clear_trace();
return;
}
if self.sim.state == sim::SimState::Stopped {
self.clear_trace();
}
self.publish_trace_request(world);
self.ingest_trace(world);
}
fn clear_trace(&mut self) {
self.behavior.pulses.clear();
self.live_vars.clear();
self.live_locals.clear();
}
fn open_behavior_name(&self) -> String {
self.behavior_entry()
.and_then(|i| entry_handle(&self.entries, i))
.unwrap_or_default()
}
fn open_behavior_paths(&self, world: &World) -> Vec<Path> {
let Some(id) = trace::id_of(&self.open_behavior_name()) else {
return Vec::new();
};
world
.resource::<TracePaths>()
.and_then(|t| t.0.iter().find(|(b, _)| *b == id))
.map(|(_, paths)| paths.iter().map(|p| trace::to_path(p)).collect())
.unwrap_or_default()
}
fn publish_trace_request(&mut self, world: &mut World) {
let entity = self
.selection
.active()
.and_then(|h| self.handle_name(h))
.and_then(|name| trace::id_of(&name))
.and_then(|id| world.entity_of(id))
.map(|e| e.to_bits());
let breakpoints = self.resolve_breakpoints(world);
world.insert_resource(TraceRequest {
entity,
breakpoints,
});
}
fn resolve_breakpoints(&self, world: &World) -> Vec<TraceEvent> {
let Some(table) = world.resource::<TracePaths>() else {
return Vec::new();
};
self.behavior
.breakpoints
.iter()
.filter_map(|(name, path)| {
let id = trace::id_of(name)?;
let (_, paths) = table.0.iter().find(|(b, _)| *b == id)?;
let node = paths.iter().position(|p| trace::matches(p, path))?;
Some(TraceEvent {
behavior: id,
node: node as u32,
})
})
.collect()
}
fn ingest_trace(&mut self, world: &mut World) {
let dt = world
.resource::<FrameTime>()
.copied()
.unwrap_or_default()
.dt;
self.age_pulses(dt);
let Some(t) = world.resource::<ExecutionTrace>() else {
return;
};
if t.frame == self.trace_seen {
return;
}
let frame = t.frame;
let events = t.events.clone();
let vars = t.vars.clone();
let locals = t.locals.clone();
let hit = t.hit;
self.trace_seen = frame;
let open_id = trace::id_of(&self.open_behavior_name());
let open_paths = self.open_behavior_paths(world);
for event in &events {
if Some(event.behavior) != open_id {
continue;
}
let Some(path) = open_paths.get(event.node as usize) else {
continue;
};
match self
.behavior
.pulses
.iter_mut()
.find(|p| p.node == event.node)
{
Some(p) => p.age = 0.0,
None => self.behavior.pulses.push(NodePulse {
node: event.node,
path: path.clone(),
age: 0.0,
}),
}
}
self.live_vars = vars
.into_iter()
.map(|(name, val)| {
let (ty, text) = trace::text(val);
(name, ty.to_string(), text)
})
.collect();
self.live_locals = locals
.into_iter()
.filter(|(behavior, _, _)| Some(*behavior) == open_id)
.map(|(_, name, val)| {
let (ty, text) = trace::text(val);
(name, ty.to_string(), text)
})
.collect();
if let Some(hit) = hit {
self.land_on_hit(hit, open_id, &open_paths, world);
}
}
fn land_on_hit(
&mut self,
hit: TraceEvent,
open_id: Option<AssetId>,
open_paths: &[Path],
world: &mut World,
) {
self.sim.pause();
if Some(hit.behavior) != open_id {
return;
}
let Some(path) = open_paths.get(hit.node as usize) else {
return;
};
if let Some(row) = self.behavior_rows().iter().position(|r| &r.path == path) {
self.select_behavior_row(row, world);
self.ensure_behavior_visible();
}
}
fn age_pulses(&mut self, dt: f32) {
self.behavior.pulses.retain_mut(|p| {
p.age += dt.max(0.0);
p.age < pulse::PULSE_SECS
});
}
pub(in crate::editor::hook) fn toggle_behavior_breakpoint(&mut self, path: &Path) {
let name = self.open_behavior_name();
if name.is_empty() {
return;
}
self.behavior.toggle_breakpoint(name, path);
}
}