1use crate::runtime::context::JSContext;
2use crate::runtime::extension::MacroTaskExtension;
3use crate::value::JSValue;
4use std::cmp::Ordering;
5use std::collections::BinaryHeap;
6use std::collections::HashMap;
7use std::collections::VecDeque;
8use std::time::{Duration, Instant};
9
10pub type TimerId = u32;
11
12pub type AnimationCallbackId = u32;
13
14pub struct Macrotask {
15 pub callback: JSValue,
16
17 pub args: Vec<JSValue>,
18
19 pub timer_id: Option<TimerId>,
20
21 pub is_interval: bool,
22
23 pub interval: Option<Duration>,
24}
25
26#[derive(Clone)]
27pub struct Timer {
28 pub id: TimerId,
29
30 pub callback: JSValue,
31
32 pub args: Vec<JSValue>,
33
34 pub deadline: Instant,
35
36 pub is_interval: bool,
37
38 pub interval: Option<Duration>,
39}
40
41impl Ord for Timer {
42 fn cmp(&self, other: &Self) -> Ordering {
43 other.deadline.cmp(&self.deadline)
44 }
45}
46
47impl PartialOrd for Timer {
48 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
49 Some(self.cmp(other))
50 }
51}
52
53impl Eq for Timer {}
54
55impl PartialEq for Timer {
56 fn eq(&self, other: &Self) -> bool {
57 self.id == other.id
58 }
59}
60
61pub struct AnimationCallback {
62 pub id: AnimationCallbackId,
63
64 pub callback: JSValue,
65}
66
67#[derive(Debug, Clone)]
68pub struct EventLoopResult {
69 pub completed: bool,
70
71 pub macrotasks_executed: usize,
72
73 pub timers_fired: usize,
74
75 pub animation_callbacks_executed: usize,
76
77 pub macrotasks_remaining: usize,
78
79 pub timers_remaining: usize,
80
81 pub animation_callbacks_remaining: usize,
82}
83
84pub struct EventLoop {
85 macrotask_queue: VecDeque<Macrotask>,
86
87 timer_heap: BinaryHeap<Timer>,
88
89 active_timers: HashMap<TimerId, ()>,
90
91 interval_timers: HashMap<TimerId, Timer>,
92
93 animation_queue: VecDeque<AnimationCallback>,
94
95 next_timer_id: TimerId,
96
97 next_animation_id: AnimationCallbackId,
98
99 max_iterations: u64,
100
101 pub extensions: Vec<Box<dyn MacroTaskExtension>>,
102}
103
104impl Default for EventLoop {
105 fn default() -> Self {
106 Self::new()
107 }
108}
109
110impl EventLoop {
111 pub fn new() -> Self {
112 EventLoop {
113 macrotask_queue: VecDeque::new(),
114 timer_heap: BinaryHeap::new(),
115 active_timers: HashMap::new(),
116 interval_timers: HashMap::new(),
117 animation_queue: VecDeque::new(),
118 next_timer_id: 1,
119 next_animation_id: 1,
120 max_iterations: 1_000_000,
121 extensions: Vec::new(),
122 }
123 }
124
125 pub fn set_max_iterations(&mut self, max: u64) {
126 self.max_iterations = max;
127 }
128
129 pub fn schedule_timer(
130 &mut self,
131 callback: JSValue,
132 args: Vec<JSValue>,
133 delay_ms: u64,
134 is_interval: bool,
135 ) -> TimerId {
136 let id = self.next_timer_id;
137 self.next_timer_id += 1;
138
139 let deadline = Instant::now() + Duration::from_millis(delay_ms);
140 let interval = if is_interval {
141 Some(Duration::from_millis(delay_ms))
142 } else {
143 None
144 };
145
146 let timer = Timer {
147 id,
148 callback,
149 args,
150 deadline,
151 is_interval,
152 interval,
153 };
154
155 self.timer_heap.push(timer.clone());
156 self.active_timers.insert(id, ());
157
158 if is_interval {
159 self.interval_timers.insert(id, timer);
160 }
161
162 id
163 }
164
165 pub fn clear_timer(&mut self, timer_id: TimerId) {
166 self.active_timers.remove(&timer_id);
167 self.interval_timers.remove(&timer_id);
168 }
169
170 pub fn is_timer_active(&self, timer_id: TimerId) -> bool {
171 self.active_timers.contains_key(&timer_id)
172 }
173
174 pub fn schedule_macrotask(&mut self, callback: JSValue, args: Vec<JSValue>) {
175 self.macrotask_queue.push_back(Macrotask {
176 callback,
177 args,
178 timer_id: None,
179 is_interval: false,
180 interval: None,
181 });
182 }
183
184 pub fn schedule_animation_callback(&mut self, callback: JSValue) -> AnimationCallbackId {
185 let id = self.next_animation_id;
186 self.next_animation_id += 1;
187
188 self.animation_queue
189 .push_back(AnimationCallback { id, callback });
190
191 id
192 }
193
194 pub fn cancel_animation_callback(&mut self, id: AnimationCallbackId) {
195 self.animation_queue.retain(|cb| cb.id != id);
196 }
197
198 pub fn has_pending_tasks(&self) -> bool {
199 !self.macrotask_queue.is_empty()
200 || !self.timer_heap.is_empty()
201 || !self.animation_queue.is_empty()
202 }
203
204 pub fn macrotask_count(&self) -> usize {
205 self.macrotask_queue.len()
206 }
207
208 pub fn timer_count(&self) -> usize {
209 self.timer_heap.len()
210 }
211
212 pub fn animation_callback_count(&self) -> usize {
213 self.animation_queue.len()
214 }
215
216 fn fire_ready_timers(&mut self) -> usize {
217 let mut fired = 0;
218 let now = Instant::now();
219
220 while let Some(timer) = self.timer_heap.pop() {
221 if timer.deadline > now {
222 self.timer_heap.push(timer);
223 break;
224 }
225
226 if !self.active_timers.contains_key(&timer.id) {
227 continue;
228 }
229
230 fired += 1;
231
232 self.macrotask_queue.push_back(Macrotask {
233 callback: timer.callback.clone(),
234 args: timer.args.clone(),
235 timer_id: Some(timer.id),
236 is_interval: timer.is_interval,
237 interval: timer.interval,
238 });
239
240 if !timer.is_interval {
241 self.active_timers.remove(&timer.id);
242 }
243 }
244
245 fired
246 }
247
248 fn execute_callback(
249 &self,
250 ctx: &mut JSContext,
251 callback: JSValue,
252 args: &[JSValue],
253 ) -> Result<JSValue, String> {
254 if !callback.is_function() {
255 return Ok(JSValue::undefined());
256 }
257
258 let vm_ptr = ctx
259 .get_register_vm_ptr()
260 .ok_or("No VM associated with context")?;
261 let vm = unsafe { &mut *(vm_ptr as *mut crate::runtime::vm::VM) };
262 vm.call_function(ctx, callback, args)
263 }
264
265 fn run_microtasks(&self, ctx: &mut JSContext) {
266 let vm_ptr = match ctx.get_register_vm_ptr() {
267 Some(ptr) => ptr,
268 None => return,
269 };
270 let vm = unsafe { &mut *(vm_ptr as *mut crate::runtime::vm::VM) };
271 crate::builtins::promise::run_microtasks_with_vm(ctx, vm);
272 }
273
274 fn tick_extensions(&mut self, ctx: &mut JSContext) {
275 for ext in &mut self.extensions {
276 if let Err(e) = ext.tick(ctx) {
277 eprintln!("[EventLoop] extension error: {e}");
278 }
279 }
280 }
281
282 pub fn run_until_complete(
283 &mut self,
284 ctx: &mut JSContext,
285 timeout_ms: Option<u64>,
286 ) -> Result<EventLoopResult, String> {
287 let start = Instant::now();
288 let timeout = timeout_ms.map(Duration::from_millis);
289 let mut iterations = 0u64;
290
291 let mut macrotasks_executed = 0usize;
292 let mut timers_fired = 0usize;
293 let mut animation_callbacks_executed = 0usize;
294
295 loop {
296 iterations += 1;
297 if iterations > self.max_iterations {
298 return Err(format!(
299 "Event loop iteration limit exceeded ({})",
300 self.max_iterations
301 ));
302 }
303
304 if let Some(timeout_dur) = &timeout {
305 if start.elapsed() > *timeout_dur {
306 return Ok(EventLoopResult {
307 completed: false,
308 macrotasks_executed,
309 timers_fired,
310 animation_callbacks_executed,
311 macrotasks_remaining: self.macrotask_queue.len(),
312 timers_remaining: self.timer_heap.len(),
313 animation_callbacks_remaining: self.animation_queue.len(),
314 });
315 }
316 }
317
318 self.run_microtasks(ctx);
319
320 self.tick_extensions(ctx);
321
322 timers_fired += self.fire_ready_timers();
323
324 let animation_callbacks: Vec<AnimationCallback> =
325 self.animation_queue.drain(..).collect();
326 for anim_cb in animation_callbacks {
327 let timestamp = Instant::now().elapsed().as_nanos() as f64;
328 let timestamp_val = JSValue::new_float(timestamp / 1_000_000.0);
329
330 if anim_cb.callback.is_function() {
331 let _ = self.execute_callback(ctx, anim_cb.callback, &[timestamp_val]);
332 animation_callbacks_executed += 1;
333 }
334 }
335
336 if let Some(macrotask) = self.macrotask_queue.pop_front() {
337 let timer_id = macrotask.timer_id;
338 let is_interval = macrotask.is_interval;
339 let interval = macrotask.interval;
340
341 if macrotask.callback.is_function() {
342 match self.execute_callback(ctx, macrotask.callback, ¯otask.args) {
343 Ok(_result) => {}
344 Err(e) => {
345 eprintln!("[EventLoop] Macrotask error: {}", e);
346 }
347 }
348 macrotasks_executed += 1;
349 }
350
351 if is_interval {
352 if let (Some(id), Some(interval_dur)) = (timer_id, interval) {
353 if self.active_timers.contains_key(&id) {
354 if let Some(stored_timer) = self.interval_timers.get(&id).cloned() {
355 let new_timer = Timer {
356 id,
357 callback: stored_timer.callback,
358 args: stored_timer.args,
359 deadline: Instant::now() + interval_dur,
360 is_interval: true,
361 interval: Some(interval_dur),
362 };
363 self.timer_heap.push(new_timer);
364 }
365 }
366 }
367 }
368
369 self.run_microtasks(ctx);
370 continue;
371 }
372
373 let has_microtasks = !ctx.microtask_is_empty();
374 let has_macrotasks = !self.macrotask_queue.is_empty();
375 let has_timers = !self.timer_heap.is_empty();
376 let has_animation = !self.animation_queue.is_empty();
377 let has_io = self.extensions.iter().any(|e| e.has_pending());
378
379 if !has_microtasks && !has_macrotasks && !has_timers && !has_animation && !has_io {
380 return Ok(EventLoopResult {
381 completed: true,
382 macrotasks_executed,
383 timers_fired,
384 animation_callbacks_executed,
385 macrotasks_remaining: 0,
386 timers_remaining: 0,
387 animation_callbacks_remaining: 0,
388 });
389 }
390
391 let exit_early = if has_microtasks || has_macrotasks || has_animation {
392 false
393 } else if has_timers {
394 if let Some(next_timer) = self.timer_heap.peek() {
395 let wait = next_timer
396 .deadline
397 .saturating_duration_since(Instant::now());
398 wait > Duration::from_millis(100)
399 } else {
400 true
401 }
402 } else if has_io {
403 false
404 } else {
405 true
406 };
407
408 if exit_early {
409 if has_io {
410 self.tick_extensions(ctx);
411 if !self.macrotask_queue.is_empty() {
412 continue;
413 }
414 }
415 return Ok(EventLoopResult {
416 completed: !has_io,
417 macrotasks_executed,
418 timers_fired,
419 animation_callbacks_executed,
420 macrotasks_remaining: self.macrotask_queue.len(),
421 timers_remaining: self.timer_heap.len(),
422 animation_callbacks_remaining: self.animation_queue.len(),
423 });
424 }
425 }
426 }
427
428 pub fn tick(&mut self, ctx: &mut JSContext) -> Result<EventLoopResult, String> {
429 let mut macrotasks_executed = 0usize;
430 let mut timers_fired = 0usize;
431 let mut animation_callbacks_executed = 0usize;
432
433 self.run_microtasks(ctx);
434
435 self.tick_extensions(ctx);
436
437 timers_fired += self.fire_ready_timers();
438
439 let animation_callbacks: Vec<AnimationCallback> = self.animation_queue.drain(..).collect();
440 for anim_cb in animation_callbacks {
441 let timestamp = Instant::now().elapsed().as_nanos() as f64;
442 let timestamp_val = JSValue::new_float(timestamp / 1_000_000.0);
443
444 if anim_cb.callback.is_function() {
445 let _ = self.execute_callback(ctx, anim_cb.callback, &[timestamp_val]);
446 animation_callbacks_executed += 1;
447 }
448 }
449
450 if let Some(macrotask) = self.macrotask_queue.pop_front() {
451 if macrotask.callback.is_function() {
452 let _ = self.execute_callback(ctx, macrotask.callback, ¯otask.args);
453 macrotasks_executed += 1;
454 }
455
456 self.run_microtasks(ctx);
457 }
458
459 Ok(EventLoopResult {
460 completed: !self.has_pending_tasks()
461 && ctx.microtask_is_empty()
462 && !self.extensions.iter().any(|e| e.has_pending()),
463 macrotasks_executed,
464 timers_fired,
465 animation_callbacks_executed,
466 macrotasks_remaining: self.macrotask_queue.len(),
467 timers_remaining: self.timer_heap.len(),
468 animation_callbacks_remaining: self.animation_queue.len(),
469 })
470 }
471
472 pub fn advance_time(
473 &mut self,
474 ctx: &mut JSContext,
475 _duration_ms: u64,
476 ) -> Result<EventLoopResult, String> {
477 let mut timers_fired = 0usize;
478
479 while let Some(timer) = self.timer_heap.pop() {
480 if !self.active_timers.contains_key(&timer.id) {
481 continue;
482 }
483
484 timers_fired += 1;
485
486 self.macrotask_queue.push_back(Macrotask {
487 callback: timer.callback.clone(),
488 args: timer.args.clone(),
489 timer_id: Some(timer.id),
490 is_interval: timer.is_interval,
491 interval: timer.interval,
492 });
493
494 if timer.is_interval {
495 if let Some(interval) = timer.interval {
496 let mut new_timer = timer;
497 new_timer.deadline = Instant::now() + interval;
498 self.timer_heap.push(new_timer);
499 }
500 } else {
501 self.active_timers.remove(&timer.id);
502 }
503 }
504
505 let result = self.run_until_complete(ctx, None)?;
506
507 Ok(EventLoopResult {
508 timers_fired,
509 ..result
510 })
511 }
512}
513
514#[cfg(test)]
515mod tests {
516 use super::*;
517
518 #[test]
519 fn test_event_loop_creation() {
520 let el = EventLoop::new();
521 assert!(!el.has_pending_tasks());
522 assert_eq!(el.macrotask_count(), 0);
523 assert_eq!(el.timer_count(), 0);
524 }
525
526 #[test]
527 fn test_schedule_macrotask() {
528 let mut el = EventLoop::new();
529 el.schedule_macrotask(JSValue::undefined(), vec![]);
530 assert!(el.has_pending_tasks());
531 assert_eq!(el.macrotask_count(), 1);
532 }
533
534 #[test]
535 fn test_schedule_timer() {
536 let mut el = EventLoop::new();
537 let id = el.schedule_timer(JSValue::undefined(), vec![], 100, false);
538 assert_eq!(id, 1);
539 assert!(el.has_pending_tasks());
540 assert_eq!(el.timer_count(), 1);
541 assert!(el.is_timer_active(id));
542 }
543
544 #[test]
545 fn test_clear_timer() {
546 let mut el = EventLoop::new();
547 let id = el.schedule_timer(JSValue::undefined(), vec![], 100, false);
548 assert!(el.is_timer_active(id));
549 el.clear_timer(id);
550 assert!(!el.is_timer_active(id));
551 }
552
553 #[test]
554 fn test_schedule_animation_callback() {
555 let mut el = EventLoop::new();
556 let id = el.schedule_animation_callback(JSValue::undefined());
557 assert_eq!(id, 1);
558 assert_eq!(el.animation_callback_count(), 1);
559 }
560
561 #[test]
562 fn test_cancel_animation_callback() {
563 let mut el = EventLoop::new();
564 let id = el.schedule_animation_callback(JSValue::undefined());
565 el.cancel_animation_callback(id);
566 assert_eq!(el.animation_callback_count(), 0);
567 }
568
569 #[test]
570 fn test_timer_ordering() {
571 let mut el = EventLoop::new();
572
573 let _id1 = el.schedule_timer(JSValue::undefined(), vec![], 300, false);
574 let id2 = el.schedule_timer(JSValue::undefined(), vec![], 100, false);
575 let _id3 = el.schedule_timer(JSValue::undefined(), vec![], 200, false);
576
577 let top = el.timer_heap.peek().unwrap();
578 assert_eq!(top.id, id2);
579 }
580}