1
2use glam::{Vec2, Vec4};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum EventParamType {
14 Void,
15 Bool,
16 Int,
17 UInt,
18 Float,
19 Double,
20 String,
21 Vec2,
22 Vec3,
23 Vec4,
24 Quaternion,
25 EntityId,
26 AssetRef,
27 Color,
28 Bytes,
29 Json,
30 Custom,
31}
32
33impl EventParamType {
34 pub fn label(&self) -> &'static str {
35 match self {
36 Self::Void => "void",
37 Self::Bool => "bool",
38 Self::Int => "int",
39 Self::UInt => "uint",
40 Self::Float => "float",
41 Self::Double => "double",
42 Self::String => "string",
43 Self::Vec2 => "vec2",
44 Self::Vec3 => "vec3",
45 Self::Vec4 => "vec4",
46 Self::Quaternion => "quat",
47 Self::EntityId => "EntityId",
48 Self::AssetRef => "AssetRef",
49 Self::Color => "Color",
50 Self::Bytes => "bytes",
51 Self::Json => "JSON",
52 Self::Custom => "Custom",
53 }
54 }
55
56 pub fn size_bytes(&self) -> usize {
57 match self {
58 Self::Void => 0,
59 Self::Bool => 1,
60 Self::Int | Self::UInt | Self::Float => 4,
61 Self::Double | Self::EntityId => 8,
62 Self::Vec2 => 8,
63 Self::Vec3 => 12,
64 Self::Vec4 | Self::Quaternion | Self::Color => 16,
65 Self::String | Self::Bytes | Self::Json | Self::Custom | Self::AssetRef => 32,
66 }
67 }
68
69 pub fn port_color(&self) -> Vec4 {
70 match self {
71 Self::Void => Vec4::new(0.6, 0.6, 0.6, 1.0),
72 Self::Bool => Vec4::new(0.87, 0.4, 0.4, 1.0),
73 Self::Int | Self::UInt => Vec4::new(0.4, 0.8, 0.4, 1.0),
74 Self::Float | Self::Double => Vec4::new(0.4, 0.7, 0.87, 1.0),
75 Self::String | Self::Json => Vec4::new(0.87, 0.7, 0.4, 1.0),
76 Self::Vec2 | Self::Vec3 | Self::Vec4 => Vec4::new(0.6, 0.4, 0.87, 1.0),
77 Self::Quaternion => Vec4::new(0.7, 0.4, 0.87, 1.0),
78 Self::EntityId => Vec4::new(0.87, 0.87, 0.4, 1.0),
79 Self::Color => Vec4::new(0.87, 0.5, 0.3, 1.0),
80 _ => Vec4::new(0.5, 0.5, 0.5, 1.0),
81 }
82 }
83}
84
85#[derive(Debug, Clone)]
90pub struct EventParam {
91 pub name: String,
92 pub param_type: EventParamType,
93 pub description: String,
94 pub optional: bool,
95 pub default_value: String,
96}
97
98impl EventParam {
99 pub fn new(name: &str, param_type: EventParamType) -> Self {
100 Self {
101 name: name.to_string(),
102 param_type,
103 description: String::new(),
104 optional: false,
105 default_value: String::new(),
106 }
107 }
108 pub fn optional(mut self) -> Self { self.optional = true; self }
109 pub fn with_default(mut self, def: &str) -> Self { self.default_value = def.to_string(); self }
110 pub fn with_description(mut self, desc: &str) -> Self { self.description = desc.to_string(); self }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
114pub enum EventPriority { Lowest = 0, Low = 25, Normal = 50, High = 75, Critical = 100, Immediate = 255 }
115
116impl EventPriority {
117 pub fn label(&self) -> &'static str {
118 match self {
119 Self::Lowest => "Lowest",
120 Self::Low => "Low",
121 Self::Normal => "Normal",
122 Self::High => "High",
123 Self::Critical => "Critical",
124 Self::Immediate => "Immediate",
125 }
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq)]
130pub enum EventDispatchMode {
131 Immediate,
132 Queued,
133 Deferred,
134 LateTick,
135 FixedUpdate,
136 ThreadSafe,
137 Broadcast,
138}
139
140impl EventDispatchMode {
141 pub fn label(&self) -> &'static str {
142 match self {
143 Self::Immediate => "Immediate",
144 Self::Queued => "Queued",
145 Self::Deferred => "Deferred",
146 Self::LateTick => "Late Tick",
147 Self::FixedUpdate => "Fixed Update",
148 Self::ThreadSafe => "Thread-Safe",
149 Self::Broadcast => "Broadcast",
150 }
151 }
152 pub fn description(&self) -> &'static str {
153 match self {
154 Self::Immediate => "Dispatched and handled in the same call frame",
155 Self::Queued => "Added to a queue, processed next tick",
156 Self::Deferred => "Processed after current frame",
157 Self::LateTick => "Processed at the end of the update loop",
158 Self::FixedUpdate => "Processed during fixed-rate update",
159 Self::ThreadSafe => "Can be raised from any thread",
160 Self::Broadcast => "Sent to all listening systems globally",
161 }
162 }
163}
164
165#[derive(Debug, Clone)]
166pub struct EventDefinition {
167 pub id: u64,
168 pub name: String,
169 pub namespace: String,
170 pub params: Vec<EventParam>,
171 pub priority: EventPriority,
172 pub dispatch_mode: EventDispatchMode,
173 pub category: String,
174 pub description: String,
175 pub cancelable: bool,
176 pub persistent: bool,
177 pub one_shot: bool,
178 pub max_listeners: Option<u32>,
179 pub ttl_ms: Option<u32>,
180 pub tags: Vec<String>,
181 pub deprecated: bool,
182 pub version: u32,
183}
184
185impl EventDefinition {
186 pub fn new(id: u64, name: &str) -> Self {
187 Self {
188 id,
189 name: name.to_string(),
190 namespace: "game".to_string(),
191 params: Vec::new(),
192 priority: EventPriority::Normal,
193 dispatch_mode: EventDispatchMode::Queued,
194 category: "General".to_string(),
195 description: String::new(),
196 cancelable: false,
197 persistent: false,
198 one_shot: false,
199 max_listeners: None,
200 ttl_ms: None,
201 tags: Vec::new(),
202 deprecated: false,
203 version: 1,
204 }
205 }
206 pub fn with_param(mut self, param: EventParam) -> Self { self.params.push(param); self }
207 pub fn with_priority(mut self, priority: EventPriority) -> Self { self.priority = priority; self }
208 pub fn with_mode(mut self, mode: EventDispatchMode) -> Self { self.dispatch_mode = mode; self }
209 pub fn cancelable(mut self) -> Self { self.cancelable = true; self }
210 pub fn persistent(mut self) -> Self { self.persistent = true; self }
211 pub fn with_category(mut self, cat: &str) -> Self { self.category = cat.to_string(); self }
212 pub fn full_name(&self) -> String { format!("{}.{}", self.namespace, self.name) }
213 pub fn payload_size_bytes(&self) -> usize { self.params.iter().map(|p| p.param_type.size_bytes()).sum() }
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
221pub enum ListenerKind {
222 System,
223 Component,
224 Script,
225 UI,
226 Animation,
227 Audio,
228 Network,
229 AI,
230 Physics,
231 VFX,
232 Custom,
233}
234
235impl ListenerKind {
236 pub fn label(&self) -> &'static str {
237 match self {
238 Self::System => "System",
239 Self::Component => "Component",
240 Self::Script => "Script",
241 Self::UI => "UI",
242 Self::Animation => "Animation",
243 Self::Audio => "Audio",
244 Self::Network => "Network",
245 Self::AI => "AI",
246 Self::Physics => "Physics",
247 Self::VFX => "VFX",
248 Self::Custom => "Custom",
249 }
250 }
251 pub fn color(&self) -> Vec4 {
252 match self {
253 Self::System => Vec4::new(0.8, 0.8, 0.3, 1.0),
254 Self::Component => Vec4::new(0.3, 0.7, 0.9, 1.0),
255 Self::Script => Vec4::new(0.5, 0.9, 0.5, 1.0),
256 Self::UI => Vec4::new(0.9, 0.5, 0.8, 1.0),
257 Self::Animation => Vec4::new(0.9, 0.7, 0.3, 1.0),
258 Self::Audio => Vec4::new(0.5, 0.8, 0.9, 1.0),
259 Self::Network => Vec4::new(0.9, 0.3, 0.3, 1.0),
260 Self::AI => Vec4::new(0.7, 0.4, 0.9, 1.0),
261 Self::Physics => Vec4::new(0.4, 0.9, 0.7, 1.0),
262 Self::VFX => Vec4::new(0.9, 0.6, 0.2, 1.0),
263 Self::Custom => Vec4::new(0.6, 0.6, 0.6, 1.0),
264 }
265 }
266}
267
268#[derive(Debug, Clone)]
269pub struct EventListener {
270 pub id: u64,
271 pub name: String,
272 pub kind: ListenerKind,
273 pub event_id: u64,
274 pub priority: i32,
275 pub consume_event: bool,
276 pub filter_expression: Option<String>,
277 pub handler_code: String,
278 pub enabled: bool,
279 pub call_count: u64,
280 pub total_time_us: f64,
281 pub last_triggered_ms: Option<f64>,
282 pub max_calls_per_frame: Option<u32>,
283 pub cooldown_ms: Option<u32>,
284}
285
286impl EventListener {
287 pub fn new(id: u64, name: &str, event_id: u64, kind: ListenerKind) -> Self {
288 Self {
289 id,
290 name: name.to_string(),
291 kind,
292 event_id,
293 priority: 0,
294 consume_event: false,
295 filter_expression: None,
296 handler_code: String::new(),
297 enabled: true,
298 call_count: 0,
299 total_time_us: 0.0,
300 last_triggered_ms: None,
301 max_calls_per_frame: None,
302 cooldown_ms: None,
303 }
304 }
305 pub fn avg_time_us(&self) -> f64 {
306 if self.call_count == 0 { 0.0 } else { self.total_time_us / self.call_count as f64 }
307 }
308}
309
310#[derive(Debug, Clone)]
315pub struct EventPublisher {
316 pub id: u64,
317 pub name: String,
318 pub kind: ListenerKind,
319 pub publishes: Vec<u64>,
320 pub dispatch_count: u64,
321 pub last_dispatch_ms: Option<f64>,
322 pub enabled: bool,
323}
324
325impl EventPublisher {
326 pub fn new(id: u64, name: &str, kind: ListenerKind) -> Self {
327 Self {
328 id,
329 name: name.to_string(),
330 kind,
331 publishes: Vec::new(),
332 dispatch_count: 0,
333 last_dispatch_ms: None,
334 enabled: true,
335 }
336 }
337}
338
339#[derive(Debug, Clone)]
344pub struct EventChannel {
345 pub id: u64,
346 pub publisher_id: u64,
347 pub listener_id: u64,
348 pub event_id: u64,
349 pub transform_code: Option<String>,
350 pub active: bool,
351 pub total_dispatches: u64,
352 pub dropped_count: u64,
353}
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
360pub enum EventNodeKind {
361 EventDefinition,
362 Publisher,
363 Listener,
364 Filter,
365 Transform,
366 Splitter,
367 Merger,
368 Throttle,
369 Debounce,
370 Buffer,
371 Delay,
372 Map,
373 Reduce,
374 Fork,
375 Conditional,
376 Logger,
377 Counter,
378 Timer,
379 Gate,
380 LatchSet,
381 LatchReset,
382 Comment,
383}
384
385impl EventNodeKind {
386 pub fn label(&self) -> &'static str {
387 match self {
388 Self::EventDefinition => "Event",
389 Self::Publisher => "Publisher",
390 Self::Listener => "Listener",
391 Self::Filter => "Filter",
392 Self::Transform => "Transform",
393 Self::Splitter => "Splitter",
394 Self::Merger => "Merger",
395 Self::Throttle => "Throttle",
396 Self::Debounce => "Debounce",
397 Self::Buffer => "Buffer",
398 Self::Delay => "Delay",
399 Self::Map => "Map",
400 Self::Reduce => "Reduce",
401 Self::Fork => "Fork",
402 Self::Conditional => "Conditional",
403 Self::Logger => "Logger",
404 Self::Counter => "Counter",
405 Self::Timer => "Timer",
406 Self::Gate => "Gate",
407 Self::LatchSet => "Latch (Set)",
408 Self::LatchReset => "Latch (Reset)",
409 Self::Comment => "Comment",
410 }
411 }
412 pub fn header_color(&self) -> Vec4 {
413 match self {
414 Self::EventDefinition => Vec4::new(0.2, 0.4, 0.7, 1.0),
415 Self::Publisher => Vec4::new(0.7, 0.4, 0.2, 1.0),
416 Self::Listener => Vec4::new(0.2, 0.7, 0.4, 1.0),
417 Self::Filter | Self::Conditional | Self::Gate => Vec4::new(0.6, 0.2, 0.6, 1.0),
418 Self::Throttle | Self::Debounce | Self::Delay => Vec4::new(0.6, 0.6, 0.2, 1.0),
419 Self::Map | Self::Transform | Self::Reduce => Vec4::new(0.2, 0.6, 0.6, 1.0),
420 Self::Logger | Self::Counter => Vec4::new(0.4, 0.4, 0.4, 1.0),
421 _ => Vec4::new(0.3, 0.3, 0.3, 1.0),
422 }
423 }
424}
425
426static mut EVENT_NODE_ID: u64 = 1;
427fn next_event_node_id() -> u64 {
428 unsafe { let id = EVENT_NODE_ID; EVENT_NODE_ID += 1; id }
429}
430
431#[derive(Debug, Clone)]
432pub struct EventGraphNode {
433 pub id: u64,
434 pub kind: EventNodeKind,
435 pub position: Vec2,
436 pub size: Vec2,
437 pub label: String,
438 pub linked_id: Option<u64>,
439 pub params: HashMap<String, String>,
440 pub enabled: bool,
441 pub collapsed: bool,
442 pub comment: String,
443 pub dispatch_count: u64,
444 pub error_message: Option<String>,
445}
446
447impl EventGraphNode {
448 pub fn new(kind: EventNodeKind, position: Vec2) -> Self {
449 Self {
450 id: next_event_node_id(),
451 kind,
452 position,
453 size: Vec2::new(180.0, 80.0),
454 label: kind.label().to_string(),
455 linked_id: None,
456 params: HashMap::new(),
457 enabled: true,
458 collapsed: false,
459 comment: String::new(),
460 dispatch_count: 0,
461 error_message: None,
462 }
463 }
464 pub fn contains(&self, pt: Vec2) -> bool {
465 pt.x >= self.position.x && pt.x <= self.position.x + self.size.x
466 && pt.y >= self.position.y && pt.y <= self.position.y + self.size.y
467 }
468 pub fn set_param(&mut self, key: &str, value: &str) {
469 self.params.insert(key.to_string(), value.to_string());
470 }
471}
472
473#[derive(Debug, Clone)]
474pub struct EventGraphEdge {
475 pub id: u64,
476 pub from_node: u64,
477 pub from_port: u32,
478 pub to_node: u64,
479 pub to_port: u32,
480 pub event_id: Option<u64>,
481 pub active: bool,
482 pub dispatch_count: u64,
483}
484
485static mut EVENT_GRAPH_ID: u64 = 1;
490fn next_eg_id() -> u64 { unsafe { let id = EVENT_GRAPH_ID; EVENT_GRAPH_ID += 1; id } }
491
492#[derive(Debug, Clone)]
493pub struct EventGraph {
494 pub name: String,
495 pub nodes: Vec<EventGraphNode>,
496 pub edges: Vec<EventGraphEdge>,
497 pub next_edge_id: u64,
498}
499
500impl EventGraph {
501 pub fn new(name: &str) -> Self {
502 Self { name: name.to_string(), nodes: Vec::new(), edges: Vec::new(), next_edge_id: 1 }
503 }
504
505 pub fn add_node(&mut self, node: EventGraphNode) -> u64 {
506 let id = node.id;
507 self.nodes.push(node);
508 id
509 }
510
511 pub fn connect(&mut self, from: u64, from_port: u32, to: u64, to_port: u32) -> u64 {
512 let id = self.next_edge_id;
513 self.next_edge_id += 1;
514 self.edges.push(EventGraphEdge { id, from_node: from, from_port, to_node: to, to_port, event_id: None, active: true, dispatch_count: 0 });
515 id
516 }
517
518 pub fn find_node(&self, id: u64) -> Option<&EventGraphNode> { self.nodes.iter().find(|n| n.id == id) }
519 pub fn find_node_mut(&mut self, id: u64) -> Option<&mut EventGraphNode> { self.nodes.iter_mut().find(|n| n.id == id) }
520
521 pub fn build_player_combat_graph() -> Self {
522 let mut g = Self::new("PlayerCombat");
523 let fire_event = g.add_node(EventGraphNode::new(EventNodeKind::EventDefinition, Vec2::new(20.0, 20.0)));
524 if let Some(n) = g.find_node_mut(fire_event) { n.label = "OnPlayerFire".to_string(); }
525 let hit_event = g.add_node(EventGraphNode::new(EventNodeKind::EventDefinition, Vec2::new(20.0, 140.0)));
526 if let Some(n) = g.find_node_mut(hit_event) { n.label = "OnHitDetected".to_string(); }
527 let death_event = g.add_node(EventGraphNode::new(EventNodeKind::EventDefinition, Vec2::new(20.0, 260.0)));
528 if let Some(n) = g.find_node_mut(death_event) { n.label = "OnPlayerDeath".to_string(); }
529
530 let fire_pub = g.add_node(EventGraphNode::new(EventNodeKind::Publisher, Vec2::new(260.0, 20.0)));
531 if let Some(n) = g.find_node_mut(fire_pub) { n.label = "WeaponSystem".to_string(); }
532 let hit_pub = g.add_node(EventGraphNode::new(EventNodeKind::Publisher, Vec2::new(260.0, 140.0)));
533 if let Some(n) = g.find_node_mut(hit_pub) { n.label = "HitDetectionSystem".to_string(); }
534
535 let throttle = g.add_node({ let mut n = EventGraphNode::new(EventNodeKind::Throttle, Vec2::new(480.0, 20.0)); n.set_param("rate_hz", "10"); n });
536 let filter = g.add_node({ let mut n = EventGraphNode::new(EventNodeKind::Filter, Vec2::new(480.0, 140.0)); n.set_param("condition", "damage > 0"); n });
537 let splitter = g.add_node(EventGraphNode::new(EventNodeKind::Splitter, Vec2::new(700.0, 140.0)));
538
539 let audio_listener = g.add_node(EventGraphNode::new(EventNodeKind::Listener, Vec2::new(900.0, 80.0)));
540 if let Some(n) = g.find_node_mut(audio_listener) { n.label = "AudioSystem".to_string(); }
541 let vfx_listener = g.add_node(EventGraphNode::new(EventNodeKind::Listener, Vec2::new(900.0, 180.0)));
542 if let Some(n) = g.find_node_mut(vfx_listener) { n.label = "VFXSystem".to_string(); }
543 let ui_listener = g.add_node(EventGraphNode::new(EventNodeKind::Listener, Vec2::new(900.0, 280.0)));
544 if let Some(n) = g.find_node_mut(ui_listener) { n.label = "UIHUDSystem".to_string(); }
545 let logger = g.add_node(EventGraphNode::new(EventNodeKind::Logger, Vec2::new(900.0, 380.0)));
546
547 g.connect(fire_pub, 0, throttle, 0);
548 g.connect(throttle, 0, audio_listener, 0);
549 g.connect(hit_pub, 0, filter, 0);
550 g.connect(filter, 0, splitter, 0);
551 g.connect(splitter, 0, audio_listener, 0);
552 g.connect(splitter, 1, vfx_listener, 0);
553 g.connect(splitter, 2, ui_listener, 0);
554 g.connect(splitter, 3, logger, 0);
555 g
556 }
557}
558
559#[derive(Debug, Clone)]
564pub struct EventTraceEntry {
565 pub timestamp_ms: f64,
566 pub frame: u64,
567 pub event_name: String,
568 pub event_id: u64,
569 pub dispatch_mode: EventDispatchMode,
570 pub priority: EventPriority,
571 pub publisher: String,
572 pub listener_count: u32,
573 pub cancelled: bool,
574 pub consumed: bool,
575 pub dispatch_time_us: f32,
576 pub payload_bytes: usize,
577}
578
579#[derive(Debug, Clone, Default)]
580pub struct EventTrace {
581 pub entries: Vec<EventTraceEntry>,
582 pub recording: bool,
583 pub max_entries: usize,
584 pub filter_event: Option<String>,
585 pub filter_publisher: Option<String>,
586 pub paused: bool,
587}
588
589impl EventTrace {
590 pub fn new(max: usize) -> Self { Self { max_entries: max, ..Default::default() } }
591 pub fn push(&mut self, entry: EventTraceEntry) {
592 if !self.recording || self.paused { return; }
593 if let Some(ref f) = self.filter_event {
594 if !entry.event_name.contains(f.as_str()) { return; }
595 }
596 self.entries.push(entry);
597 if self.entries.len() > self.max_entries { self.entries.remove(0); }
598 }
599 pub fn clear(&mut self) { self.entries.clear(); }
600 pub fn total_events(&self) -> usize { self.entries.len() }
601 pub fn events_per_second(&self) -> f64 {
602 if self.entries.len() < 2 { return 0.0; }
603 let dt = self.entries.last().unwrap().timestamp_ms - self.entries.first().unwrap().timestamp_ms;
604 if dt <= 0.0 { return 0.0; }
605 self.entries.len() as f64 / (dt / 1000.0)
606 }
607 pub fn most_frequent(&self, n: usize) -> Vec<(&str, usize)> {
608 let mut counts: HashMap<&str, usize> = HashMap::new();
609 for e in &self.entries { *counts.entry(e.event_name.as_str()).or_insert(0) += 1; }
610 let mut sorted: Vec<(&str, usize)> = counts.into_iter().collect();
611 sorted.sort_by(|a, b| b.1.cmp(&a.1));
612 sorted.into_iter().take(n).collect()
613 }
614 pub fn avg_dispatch_time_us(&self) -> f32 {
615 if self.entries.is_empty() { return 0.0; }
616 self.entries.iter().map(|e| e.dispatch_time_us).sum::<f32>() / self.entries.len() as f32
617 }
618 pub fn cancelled_count(&self) -> usize { self.entries.iter().filter(|e| e.cancelled).count() }
619}
620
621#[derive(Debug, Clone)]
626pub struct QueuedEvent {
627 pub event_id: u64,
628 pub event_name: String,
629 pub queued_at_ms: f64,
630 pub scheduled_frame: u64,
631 pub priority: EventPriority,
632 pub publisher: String,
633 pub payload_preview: String,
634 pub age_ms: f32,
635}
636
637#[derive(Debug, Clone, Default)]
638pub struct EventQueueVisualizer {
639 pub queued: Vec<QueuedEvent>,
640 pub processed_this_frame: u32,
641 pub dropped_this_frame: u32,
642 pub max_queue_depth_seen: usize,
643 pub avg_queue_depth: f32,
644 pub frame: u64,
645}
646
647impl EventQueueVisualizer {
648 pub fn simulate_frame(&mut self, current_time_ms: f64) {
649 self.frame += 1;
650 let seed: u64 = self.frame.wrapping_mul(6364136223846793005);
652 let rng = ((seed >> 33) as f32) / (u32::MAX as f32);
653
654 if rng > 0.3 {
655 self.queued.push(QueuedEvent {
656 event_id: self.frame,
657 event_name: ["OnPlayerMove", "OnHit", "OnFire", "OnCollision", "OnTick"][((rng * 5.0) as usize).min(4)].to_string(),
658 queued_at_ms: current_time_ms,
659 scheduled_frame: self.frame,
660 priority: EventPriority::Normal,
661 publisher: "GameSystem".to_string(),
662 payload_preview: "{ ... }".to_string(),
663 age_ms: 0.0,
664 });
665 }
666
667 let to_process = (self.queued.len() as f32 * 0.8) as usize;
668 self.processed_this_frame = to_process as u32;
669 self.queued = self.queued.split_off(to_process.min(self.queued.len()));
670
671 if self.queued.len() > self.max_queue_depth_seen {
672 self.max_queue_depth_seen = self.queued.len();
673 }
674 self.avg_queue_depth = self.avg_queue_depth * 0.95 + self.queued.len() as f32 * 0.05;
675 }
676}
677
678#[derive(Debug, Clone, Default)]
683pub struct EventLibrary {
684 pub events: Vec<EventDefinition>,
685 pub next_id: u64,
686}
687
688impl EventLibrary {
689 pub fn with_defaults() -> Self {
690 let mut lib = Self { events: Vec::new(), next_id: 1 };
691 lib.add(EventDefinition::new(lib.next_id, "OnGameStart").with_category("Game"));
692 lib.next_id += 1;
693 lib.add(EventDefinition::new(lib.next_id, "OnGameEnd").with_category("Game")
694 .with_param(EventParam::new("reason", EventParamType::String)));
695 lib.next_id += 1;
696 lib.add(EventDefinition::new(lib.next_id, "OnLevelLoaded").with_category("Level")
697 .with_param(EventParam::new("scene_name", EventParamType::String)));
698 lib.next_id += 1;
699 lib.add(EventDefinition::new(lib.next_id, "OnPlayerSpawned").with_category("Player")
700 .with_param(EventParam::new("entity_id", EventParamType::EntityId))
701 .with_param(EventParam::new("position", EventParamType::Vec3)));
702 lib.next_id += 1;
703 lib.add(EventDefinition::new(lib.next_id, "OnPlayerDied").with_category("Player")
704 .with_param(EventParam::new("entity_id", EventParamType::EntityId))
705 .with_param(EventParam::new("killer_id", EventParamType::EntityId).optional())
706 .with_priority(EventPriority::High).cancelable());
707 lib.next_id += 1;
708 lib.add(EventDefinition::new(lib.next_id, "OnDamageDealt").with_category("Combat")
709 .with_param(EventParam::new("target", EventParamType::EntityId))
710 .with_param(EventParam::new("attacker", EventParamType::EntityId))
711 .with_param(EventParam::new("amount", EventParamType::Float))
712 .with_param(EventParam::new("type", EventParamType::String))
713 .cancelable());
714 lib.next_id += 1;
715 lib.add(EventDefinition::new(lib.next_id, "OnItemPickedUp").with_category("Inventory")
716 .with_param(EventParam::new("entity_id", EventParamType::EntityId))
717 .with_param(EventParam::new("item_id", EventParamType::AssetRef))
718 .with_param(EventParam::new("count", EventParamType::Int)));
719 lib.next_id += 1;
720 lib.add(EventDefinition::new(lib.next_id, "OnQuestCompleted").with_category("Quest")
721 .with_param(EventParam::new("quest_id", EventParamType::String))
722 .with_priority(EventPriority::High));
723 lib.next_id += 1;
724 lib.add(EventDefinition::new(lib.next_id, "OnDialogueStarted").with_category("Dialogue")
725 .with_param(EventParam::new("npc_id", EventParamType::EntityId))
726 .with_param(EventParam::new("dialogue_id", EventParamType::String)));
727 lib.next_id += 1;
728 lib.add(EventDefinition::new(lib.next_id, "OnAchievementUnlocked").with_category("Progression")
729 .with_param(EventParam::new("achievement_id", EventParamType::String))
730 .with_priority(EventPriority::High).persistent());
731 lib.next_id += 1;
732 lib.add(EventDefinition::new(lib.next_id, "OnUIButtonClicked").with_category("UI")
733 .with_param(EventParam::new("button_id", EventParamType::String))
734 .with_mode(EventDispatchMode::Immediate));
735 lib.next_id += 1;
736 lib.add(EventDefinition::new(lib.next_id, "OnSettingsChanged").with_category("System")
737 .with_param(EventParam::new("category", EventParamType::String))
738 .with_mode(EventDispatchMode::Broadcast));
739 lib.next_id += 1;
740 lib.add(EventDefinition::new(lib.next_id, "OnNetworkDisconnect").with_category("Network")
741 .with_param(EventParam::new("player_id", EventParamType::EntityId))
742 .with_param(EventParam::new("reason", EventParamType::String))
743 .with_priority(EventPriority::Critical));
744 lib.next_id += 1;
745 lib.add(EventDefinition::new(lib.next_id, "OnPhysicsCollision").with_category("Physics")
746 .with_param(EventParam::new("entity_a", EventParamType::EntityId))
747 .with_param(EventParam::new("entity_b", EventParamType::EntityId))
748 .with_param(EventParam::new("contact_point", EventParamType::Vec3))
749 .with_param(EventParam::new("impulse", EventParamType::Float))
750 .with_mode(EventDispatchMode::FixedUpdate));
751 lib.next_id += 1;
752 lib.add(EventDefinition::new(lib.next_id, "OnAnimationEvent").with_category("Animation")
753 .with_param(EventParam::new("entity_id", EventParamType::EntityId))
754 .with_param(EventParam::new("event_name", EventParamType::String))
755 .with_param(EventParam::new("frame", EventParamType::Int)));
756 lib.next_id += 1;
757 lib
758 }
759
760 pub fn add(&mut self, def: EventDefinition) { self.events.push(def); }
761 pub fn find_by_name(&self, name: &str) -> Option<&EventDefinition> { self.events.iter().find(|e| e.name == name) }
762 pub fn find_by_id(&self, id: u64) -> Option<&EventDefinition> { self.events.iter().find(|e| e.id == id) }
763 pub fn by_category(&self) -> HashMap<&str, Vec<&EventDefinition>> {
764 let mut map: HashMap<&str, Vec<&EventDefinition>> = HashMap::new();
765 for e in &self.events { map.entry(e.category.as_str()).or_default().push(e); }
766 map
767 }
768 pub fn search(&self, query: &str) -> Vec<&EventDefinition> {
769 let q = query.to_lowercase();
770 self.events.iter().filter(|e| e.name.to_lowercase().contains(&q) || e.category.to_lowercase().contains(&q)).collect()
771 }
772 pub fn total_payload_size_bytes(&self) -> usize { self.events.iter().map(|e| e.payload_size_bytes()).sum() }
773}
774
775#[derive(Debug, Clone, Copy, PartialEq)]
780pub enum EventEditorPanel {
781 EventLibrary,
782 Graph,
783 Listeners,
784 Publishers,
785 Trace,
786 Queue,
787 Statistics,
788 CodeGen,
789}
790
791impl EventEditorPanel {
792 pub fn label(&self) -> &'static str {
793 match self {
794 Self::EventLibrary => "Events",
795 Self::Graph => "Graph",
796 Self::Listeners => "Listeners",
797 Self::Publishers => "Publishers",
798 Self::Trace => "Trace",
799 Self::Queue => "Queue",
800 Self::Statistics => "Statistics",
801 Self::CodeGen => "Code Gen",
802 }
803 }
804}
805
806#[derive(Debug, Clone, Default)]
811pub struct EventStatistics {
812 pub total_dispatched: u64,
813 pub total_cancelled: u64,
814 pub total_consumed: u64,
815 pub total_dropped: u64,
816 pub per_event_counts: HashMap<String, u64>,
817 pub per_publisher_counts: HashMap<String, u64>,
818 pub per_listener_counts: HashMap<String, u64>,
819 pub peak_events_per_frame: u32,
820 pub avg_events_per_frame: f32,
821 pub avg_dispatch_time_us: f32,
822 pub peak_queue_depth: usize,
823 pub hottest_event: Option<String>,
824}
825
826impl EventStatistics {
827 pub fn record_dispatch(&mut self, event: &str, publisher: &str, cancelled: bool, consumed: bool) {
828 self.total_dispatched += 1;
829 if cancelled { self.total_cancelled += 1; }
830 if consumed { self.total_consumed += 1; }
831 *self.per_event_counts.entry(event.to_string()).or_insert(0) += 1;
832 *self.per_publisher_counts.entry(publisher.to_string()).or_insert(0) += 1;
833 self.hottest_event = self.per_event_counts.iter()
834 .max_by_key(|(_, &v)| v)
835 .map(|(k, _)| k.clone());
836 }
837 pub fn top_events(&self, n: usize) -> Vec<(&String, &u64)> {
838 let mut sorted: Vec<(&String, &u64)> = self.per_event_counts.iter().collect();
839 sorted.sort_by(|a, b| b.1.cmp(a.1));
840 sorted.into_iter().take(n).collect()
841 }
842 pub fn cancel_rate(&self) -> f32 {
843 if self.total_dispatched == 0 { return 0.0; }
844 self.total_cancelled as f32 / self.total_dispatched as f32
845 }
846}
847
848#[derive(Debug, Clone, Copy, PartialEq)]
853pub enum CodeGenLanguage { Rust, Cpp, CSharp, TypeScript, Python, Lua }
854
855impl CodeGenLanguage {
856 pub fn label(&self) -> &'static str {
857 match self {
858 Self::Rust => "Rust", Self::Cpp => "C++", Self::CSharp => "C#",
859 Self::TypeScript => "TypeScript", Self::Python => "Python", Self::Lua => "Lua",
860 }
861 }
862}
863
864fn generate_rust_event_code(lib: &EventLibrary) -> String {
865 let mut lines = vec![
866 "// Auto-generated event types".to_string(),
867 "use std::any::Any;".to_string(),
868 "".to_string(),
869 "pub trait Event: Any { fn name(&self) -> &'static str; }".to_string(),
870 "".to_string(),
871 ];
872 for def in &lib.events {
873 lines.push(format!("/// {}", def.description));
874 lines.push(format!("pub struct {} {{", def.name));
875 for param in &def.params {
876 let rust_type = match param.param_type {
877 EventParamType::Bool => "bool",
878 EventParamType::Int => "i32",
879 EventParamType::UInt => "u32",
880 EventParamType::Float => "f32",
881 EventParamType::Double => "f64",
882 EventParamType::String => "String",
883 EventParamType::Vec2 => "glam::Vec2",
884 EventParamType::Vec3 => "glam::Vec3",
885 EventParamType::Vec4 => "glam::Vec4",
886 EventParamType::Quaternion => "glam::Quat",
887 EventParamType::EntityId => "u64",
888 EventParamType::AssetRef => "String",
889 EventParamType::Color => "glam::Vec4",
890 _ => "Vec<u8>",
891 };
892 let opt = if param.optional { format!("Option<{}>", rust_type) } else { rust_type.to_string() };
893 lines.push(format!(" pub {}: {},", param.name, opt));
894 }
895 lines.push("}".to_string());
896 lines.push(format!("impl Event for {} {{ fn name(&self) -> &'static str {{ \"{}\" }} }}", def.name, def.full_name()));
897 lines.push("".to_string());
898 }
899 lines.join("\n")
900}
901
902fn generate_cpp_event_code(lib: &EventLibrary) -> String {
903 let mut lines = vec![
904 "// Auto-generated event types".to_string(),
905 "#pragma once".to_string(),
906 "#include <string>".to_string(),
907 "#include <glm/glm.hpp>".to_string(),
908 "".to_string(),
909 ];
910 for def in &lib.events {
911 lines.push(format!("struct {} {{", def.name));
912 for param in &def.params {
913 let cpp_type = match param.param_type {
914 EventParamType::Bool => "bool",
915 EventParamType::Int => "int32_t",
916 EventParamType::UInt => "uint32_t",
917 EventParamType::Float => "float",
918 EventParamType::Double => "double",
919 EventParamType::String => "std::string",
920 EventParamType::Vec2 => "glm::vec2",
921 EventParamType::Vec3 => "glm::vec3",
922 EventParamType::Vec4 => "glm::vec4",
923 EventParamType::Quaternion => "glm::quat",
924 EventParamType::EntityId => "uint64_t",
925 _ => "std::vector<uint8_t>",
926 };
927 lines.push(format!(" {} {};", cpp_type, param.name));
928 }
929 lines.push(format!(" static constexpr const char* NAME = \"{}\";", def.full_name()));
930 lines.push("};".to_string());
931 lines.push("".to_string());
932 }
933 lines.join("\n")
934}
935
936#[derive(Debug)]
941pub struct EventSystemEditor {
942 pub library: EventLibrary,
943 pub graphs: Vec<EventGraph>,
944 pub active_graph: usize,
945 pub listeners: Vec<EventListener>,
946 pub publishers: Vec<EventPublisher>,
947 pub channels: Vec<EventChannel>,
948 pub trace: EventTrace,
949 pub queue_vis: EventQueueVisualizer,
950 pub statistics: EventStatistics,
951 pub active_panel: EventEditorPanel,
952 pub selected_event: Option<u64>,
953 pub selected_node: Option<u64>,
954 pub selected_listener: Option<usize>,
955 pub selected_publisher: Option<usize>,
956 pub search_query: String,
957 pub category_filter: Option<String>,
958 pub codegen_language: CodeGenLanguage,
959 pub generated_code: String,
960 pub zoom: f32,
961 pub pan: Vec2,
962 pub simulation_time_ms: f64,
963 pub auto_trace: bool,
964 pub trace_max_entries: usize,
965 pub show_cancelled: bool,
966 pub show_consumed: bool,
967 pub history: Vec<Vec<EventGraphNode>>,
968 pub history_pos: usize,
969}
970
971impl Default for EventSystemEditor {
972 fn default() -> Self {
973 let library = EventLibrary::with_defaults();
974 let graphs = vec![
975 EventGraph::build_player_combat_graph(),
976 EventGraph::new("UIEvents"),
977 EventGraph::new("NetworkEvents"),
978 ];
979 let mut trace = EventTrace::new(2048);
980 trace.recording = true;
981
982 let listeners = vec![
984 EventListener::new(1, "AudioSystem.OnHit", 6, ListenerKind::Audio),
985 EventListener::new(2, "VFXSystem.OnHit", 6, ListenerKind::VFX),
986 EventListener::new(3, "UIHud.OnDamage", 6, ListenerKind::UI),
987 EventListener::new(4, "QuestTracker.OnKill", 5, ListenerKind::System),
988 EventListener::new(5, "AchievementSystem.OnDeath", 5, ListenerKind::System),
989 ];
990
991 let publishers = vec![
992 EventPublisher::new(1, "WeaponSystem", ListenerKind::System),
993 EventPublisher::new(2, "HealthSystem", ListenerKind::System),
994 EventPublisher::new(3, "PlayerController", ListenerKind::Component),
995 EventPublisher::new(4, "PhysicsSystem", ListenerKind::Physics),
996 ];
997
998 Self {
999 library,
1000 graphs,
1001 active_graph: 0,
1002 listeners,
1003 publishers,
1004 channels: Vec::new(),
1005 trace,
1006 queue_vis: EventQueueVisualizer::default(),
1007 statistics: EventStatistics::default(),
1008 active_panel: EventEditorPanel::EventLibrary,
1009 selected_event: None,
1010 selected_node: None,
1011 selected_listener: None,
1012 selected_publisher: None,
1013 search_query: String::new(),
1014 category_filter: None,
1015 codegen_language: CodeGenLanguage::Rust,
1016 generated_code: String::new(),
1017 zoom: 1.0,
1018 pan: Vec2::ZERO,
1019 simulation_time_ms: 0.0,
1020 auto_trace: true,
1021 trace_max_entries: 2048,
1022 show_cancelled: true,
1023 show_consumed: true,
1024 history: Vec::new(),
1025 history_pos: 0,
1026 }
1027 }
1028}
1029
1030impl EventSystemEditor {
1031 pub fn active_graph(&self) -> &EventGraph { &self.graphs[self.active_graph] }
1032 pub fn active_graph_mut(&mut self) -> &mut EventGraph { &mut self.graphs[self.active_graph] }
1033
1034 pub fn simulate_tick(&mut self, dt_ms: f64) {
1035 self.simulation_time_ms += dt_ms;
1036 self.queue_vis.simulate_frame(self.simulation_time_ms);
1037
1038 if self.auto_trace && (self.simulation_time_ms as u64 % 16) == 0 {
1040 let events = ["OnHit", "OnPlayerMove", "OnFire", "OnTick", "OnCollision"];
1041 let idx = (self.simulation_time_ms as usize / 16) % events.len();
1042 self.trace.push(EventTraceEntry {
1043 timestamp_ms: self.simulation_time_ms,
1044 frame: self.queue_vis.frame,
1045 event_name: events[idx].to_string(),
1046 event_id: idx as u64 + 1,
1047 dispatch_mode: EventDispatchMode::Queued,
1048 priority: EventPriority::Normal,
1049 publisher: "GameSystem".to_string(),
1050 listener_count: 2,
1051 cancelled: false,
1052 consumed: false,
1053 dispatch_time_us: 0.5,
1054 payload_bytes: 16,
1055 });
1056 self.statistics.record_dispatch(events[idx], "GameSystem", false, false);
1057 }
1058 }
1059
1060 pub fn generate_code(&mut self) {
1061 self.generated_code = match self.codegen_language {
1062 CodeGenLanguage::Rust => generate_rust_event_code(&self.library),
1063 CodeGenLanguage::Cpp => generate_cpp_event_code(&self.library),
1064 _ => "// Code generation not yet supported for this language".to_string(),
1065 };
1066 }
1067
1068 pub fn filtered_events(&self) -> Vec<&EventDefinition> {
1069 let q = self.search_query.to_lowercase();
1070 self.library.events.iter().filter(|e| {
1071 let cat_ok = self.category_filter.as_ref().map_or(true, |f| &e.category == f);
1072 let text_ok = q.is_empty() || e.name.to_lowercase().contains(&q) || e.category.to_lowercase().contains(&q);
1073 cat_ok && text_ok
1074 }).collect()
1075 }
1076
1077 pub fn listeners_for_event(&self, event_id: u64) -> Vec<&EventListener> {
1078 self.listeners.iter().filter(|l| l.event_id == event_id).collect()
1079 }
1080
1081 pub fn snapshot(&mut self) {
1082 let nodes = self.active_graph().nodes.clone();
1083 self.history.truncate(self.history_pos);
1084 self.history.push(nodes);
1085 self.history_pos = self.history.len();
1086 }
1087
1088 pub fn undo(&mut self) {
1089 if self.history_pos > 1 {
1090 self.history_pos -= 1;
1091 let nodes = self.history[self.history_pos - 1].clone();
1092 self.active_graph_mut().nodes = nodes;
1093 }
1094 }
1095
1096 pub fn redo(&mut self) {
1097 if self.history_pos < self.history.len() {
1098 let nodes = self.history[self.history_pos].clone();
1099 self.active_graph_mut().nodes = nodes;
1100 self.history_pos += 1;
1101 }
1102 }
1103}