devalang_wasm/engine/events/
mod.rs

1/// Event system for Devalang - "on" and "emit" statements
2/// Allows reactive programming and event-driven audio generation
3use crate::language::syntax::ast::{Statement, Value};
4use std::collections::HashMap;
5
6/// Event handler - function called when event is emitted
7#[derive(Debug, Clone)]
8pub struct EventHandler {
9    pub event_name: String,
10    pub body: Vec<Statement>,
11    pub once: bool, // If true, handler runs only once
12    /// Optional args provided on `on` declaration, e.g. [Number(4), String("once")]
13    pub args: Option<Vec<Value>>,
14}
15
16/// Event payload - data associated with an emitted event
17#[derive(Debug, Clone)]
18pub struct EventPayload {
19    pub event_name: String,
20    pub data: HashMap<String, Value>,
21    pub timestamp: f32, // When the event was emitted
22}
23
24/// Event registry - manages event handlers and emitted events
25#[derive(Debug, Clone, Default)]
26pub struct EventRegistry {
27    handlers: HashMap<String, Vec<EventHandler>>,
28    emitted_events: Vec<EventPayload>,
29    executed_once: HashMap<String, usize>, // Track which handlers have executed (for "once")
30}
31
32impl EventRegistry {
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Register an event handler
38    pub fn register_handler(&mut self, handler: EventHandler) {
39        let event_name = handler.event_name.clone();
40        self.handlers
41            .entry(event_name)
42            .or_insert_with(Vec::new)
43            .push(handler);
44    }
45
46    /// Emit an event with optional data
47    pub fn emit(&mut self, event_name: String, data: HashMap<String, Value>, timestamp: f32) {
48        let payload = EventPayload {
49            event_name,
50            data,
51            timestamp,
52        };
53        self.emitted_events.push(payload);
54    }
55
56    /// Get handlers for a specific event
57    pub fn get_handlers(&self, event_name: &str) -> Vec<EventHandler> {
58        self.handlers.get(event_name).cloned().unwrap_or_default()
59    }
60
61    /// Get all handlers matching a pattern (supports wildcards)
62    pub fn get_handlers_matching(&self, pattern: &str) -> Vec<EventHandler> {
63        let mut matching = Vec::new();
64
65        for (event_name, handlers) in &self.handlers {
66            if pattern_matches(pattern, event_name) {
67                matching.extend(handlers.clone());
68            }
69        }
70
71        matching
72    }
73
74    /// Check if a "once" handler should be executed
75    pub fn should_execute_once(&mut self, event_name: &str, handler_index: usize) -> bool {
76        let key = format!("{}:{}", event_name, handler_index);
77
78        if self.executed_once.contains_key(&key) {
79            return false;
80        }
81
82        self.executed_once.insert(key, 1);
83        true
84    }
85
86    /// Get all emitted events
87    pub fn get_emitted_events(&self) -> &[EventPayload] {
88        &self.emitted_events
89    }
90
91    /// Get emitted events for a specific event name
92    pub fn get_events_by_name(&self, event_name: &str) -> Vec<EventPayload> {
93        self.emitted_events
94            .iter()
95            .filter(|e| e.event_name == event_name)
96            .cloned()
97            .collect()
98    }
99
100    /// Clear all emitted events (useful for new playback runs)
101    pub fn clear_events(&mut self) {
102        self.emitted_events.clear();
103        self.executed_once.clear();
104    }
105
106    /// Get number of handlers for an event
107    pub fn handler_count(&self, event_name: &str) -> usize {
108        self.handlers.get(event_name).map(|h| h.len()).unwrap_or(0)
109    }
110
111    /// Remove all handlers for an event
112    pub fn remove_handlers(&mut self, event_name: &str) {
113        self.handlers.remove(event_name);
114    }
115}
116
117/// Check if a pattern matches an event name
118/// Supports wildcards: * (any characters), ? (single character)
119fn pattern_matches(pattern: &str, event_name: &str) -> bool {
120    if pattern == "*" {
121        return true;
122    }
123
124    if !pattern.contains('*') && !pattern.contains('?') {
125        return pattern == event_name;
126    }
127
128    // Convert pattern to regex-like matching
129    let mut pattern_chars = pattern.chars().peekable();
130    let mut name_chars = event_name.chars().peekable();
131
132    while pattern_chars.peek().is_some() || name_chars.peek().is_some() {
133        match pattern_chars.peek() {
134            Some('*') => {
135                pattern_chars.next();
136
137                // If * is at the end, match everything remaining
138                if pattern_chars.peek().is_none() {
139                    return true;
140                }
141
142                // Try to match remaining pattern with remaining name
143                let remaining_pattern: String = pattern_chars.clone().collect();
144                while name_chars.peek().is_some() {
145                    let remaining_name: String = name_chars.clone().collect();
146                    if pattern_matches(&remaining_pattern, &remaining_name) {
147                        return true;
148                    }
149                    name_chars.next();
150                }
151                return false;
152            }
153            Some('?') => {
154                pattern_chars.next();
155                if name_chars.next().is_none() {
156                    return false;
157                }
158            }
159            Some(p) => {
160                let p = *p;
161                pattern_chars.next();
162                match name_chars.next() {
163                    Some(n) if n == p => continue,
164                    _ => return false,
165                }
166            }
167            None => {
168                return name_chars.peek().is_none();
169            }
170        }
171    }
172
173    true
174}
175
176/// Built-in event types
177pub mod builtin_events {
178    pub const BEAT: &str = "beat";
179    pub const BAR: &str = "bar";
180    pub const START: &str = "start";
181    pub const END: &str = "end";
182    pub const TEMPO_CHANGE: &str = "tempo.change";
183    pub const NOTE_ON: &str = "note.on";
184    pub const NOTE_OFF: &str = "note.off";
185}
186
187#[cfg(test)]
188#[path = "test_events.rs"]
189mod tests;