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(_) = ext.tick(ctx) {}
277 }
278 }
279
280 pub fn run_until_complete(
281 &mut self,
282 ctx: &mut JSContext,
283 timeout_ms: Option<u64>,
284 ) -> Result<EventLoopResult, String> {
285 let start = Instant::now();
286 let timeout = timeout_ms.map(Duration::from_millis);
287 let mut iterations = 0u64;
288
289 let mut macrotasks_executed = 0usize;
290 let mut timers_fired = 0usize;
291 let mut animation_callbacks_executed = 0usize;
292
293 loop {
294 iterations += 1;
295 if iterations > self.max_iterations {
296 return Err(format!(
297 "Event loop iteration limit exceeded ({})",
298 self.max_iterations
299 ));
300 }
301
302 if let Some(timeout_dur) = &timeout {
303 if start.elapsed() > *timeout_dur {
304 return Ok(EventLoopResult {
305 completed: false,
306 macrotasks_executed,
307 timers_fired,
308 animation_callbacks_executed,
309 macrotasks_remaining: self.macrotask_queue.len(),
310 timers_remaining: self.timer_heap.len(),
311 animation_callbacks_remaining: self.animation_queue.len(),
312 });
313 }
314 }
315
316 self.run_microtasks(ctx);
317
318 self.tick_extensions(ctx);
319
320 timers_fired += self.fire_ready_timers();
321
322 let animation_callbacks: Vec<AnimationCallback> =
323 self.animation_queue.drain(..).collect();
324 for anim_cb in animation_callbacks {
325 let timestamp = Instant::now().elapsed().as_nanos() as f64;
326 let timestamp_val = JSValue::new_float(timestamp / 1_000_000.0);
327
328 if anim_cb.callback.is_function() {
329 let _ = self.execute_callback(ctx, anim_cb.callback, &[timestamp_val]);
330 animation_callbacks_executed += 1;
331 }
332 }
333
334 if let Some(macrotask) = self.macrotask_queue.pop_front() {
335 let timer_id = macrotask.timer_id;
336 let is_interval = macrotask.is_interval;
337 let interval = macrotask.interval;
338
339 if macrotask.callback.is_function() {
340 match self.execute_callback(ctx, macrotask.callback, ¯otask.args) {
341 Ok(_result) => {}
342 Err(e) => {
343 eprintln!("[EventLoop] Macrotask error: {}", e);
344 }
345 }
346 macrotasks_executed += 1;
347 }
348
349 if is_interval {
350 if let (Some(id), Some(interval_dur)) = (timer_id, interval) {
351 if self.active_timers.contains_key(&id) {
352 if let Some(stored_timer) = self.interval_timers.get(&id).cloned() {
353 let new_timer = Timer {
354 id,
355 callback: stored_timer.callback,
356 args: stored_timer.args,
357 deadline: Instant::now() + interval_dur,
358 is_interval: true,
359 interval: Some(interval_dur),
360 };
361 self.timer_heap.push(new_timer);
362 }
363 }
364 }
365 }
366
367 self.run_microtasks(ctx);
368 continue;
369 }
370
371 let has_microtasks = !ctx.microtask_is_empty();
372 let has_macrotasks = !self.macrotask_queue.is_empty();
373 let has_timers = !self.timer_heap.is_empty();
374 let has_animation = !self.animation_queue.is_empty();
375 let has_io = self.extensions.iter().any(|e| e.has_pending());
376
377 if !has_microtasks && !has_macrotasks && !has_timers && !has_animation && !has_io {
378 return Ok(EventLoopResult {
379 completed: true,
380 macrotasks_executed,
381 timers_fired,
382 animation_callbacks_executed,
383 macrotasks_remaining: 0,
384 timers_remaining: 0,
385 animation_callbacks_remaining: 0,
386 });
387 }
388
389 let exit_early = if has_microtasks || has_macrotasks || has_animation {
390 false
391 } else if has_timers {
392 if let Some(next_timer) = self.timer_heap.peek() {
393 let wait = next_timer
394 .deadline
395 .saturating_duration_since(Instant::now());
396 wait > Duration::from_millis(100)
397 } else {
398 true
399 }
400 } else if has_io {
401 false
402 } else {
403 true
404 };
405
406 if exit_early {
407 if has_io {
408 self.tick_extensions(ctx);
409 if !self.macrotask_queue.is_empty() {
410 continue;
411 }
412 }
413 return Ok(EventLoopResult {
414 completed: !has_io,
415 macrotasks_executed,
416 timers_fired,
417 animation_callbacks_executed,
418 macrotasks_remaining: self.macrotask_queue.len(),
419 timers_remaining: self.timer_heap.len(),
420 animation_callbacks_remaining: self.animation_queue.len(),
421 });
422 }
423 }
424 }
425
426 pub fn tick(&mut self, ctx: &mut JSContext) -> Result<EventLoopResult, String> {
427 let mut macrotasks_executed = 0usize;
428 let mut timers_fired = 0usize;
429 let mut animation_callbacks_executed = 0usize;
430
431 self.run_microtasks(ctx);
432
433 self.tick_extensions(ctx);
434
435 timers_fired += self.fire_ready_timers();
436
437 let animation_callbacks: Vec<AnimationCallback> = self.animation_queue.drain(..).collect();
438 for anim_cb in animation_callbacks {
439 let timestamp = Instant::now().elapsed().as_nanos() as f64;
440 let timestamp_val = JSValue::new_float(timestamp / 1_000_000.0);
441
442 if anim_cb.callback.is_function() {
443 let _ = self.execute_callback(ctx, anim_cb.callback, &[timestamp_val]);
444 animation_callbacks_executed += 1;
445 }
446 }
447
448 if let Some(macrotask) = self.macrotask_queue.pop_front() {
449 if macrotask.callback.is_function() {
450 let _ = self.execute_callback(ctx, macrotask.callback, ¯otask.args);
451 macrotasks_executed += 1;
452 }
453
454 self.run_microtasks(ctx);
455 }
456
457 Ok(EventLoopResult {
458 completed: !self.has_pending_tasks()
459 && ctx.microtask_is_empty()
460 && !self.extensions.iter().any(|e| e.has_pending()),
461 macrotasks_executed,
462 timers_fired,
463 animation_callbacks_executed,
464 macrotasks_remaining: self.macrotask_queue.len(),
465 timers_remaining: self.timer_heap.len(),
466 animation_callbacks_remaining: self.animation_queue.len(),
467 })
468 }
469
470 pub fn advance_time(
471 &mut self,
472 ctx: &mut JSContext,
473 _duration_ms: u64,
474 ) -> Result<EventLoopResult, String> {
475 let mut timers_fired = 0usize;
476
477 while let Some(timer) = self.timer_heap.pop() {
478 if !self.active_timers.contains_key(&timer.id) {
479 continue;
480 }
481
482 timers_fired += 1;
483
484 self.macrotask_queue.push_back(Macrotask {
485 callback: timer.callback.clone(),
486 args: timer.args.clone(),
487 timer_id: Some(timer.id),
488 is_interval: timer.is_interval,
489 interval: timer.interval,
490 });
491
492 if timer.is_interval {
493 if let Some(interval) = timer.interval {
494 let mut new_timer = timer;
495 new_timer.deadline = Instant::now() + interval;
496 self.timer_heap.push(new_timer);
497 }
498 } else {
499 self.active_timers.remove(&timer.id);
500 }
501 }
502
503 let result = self.run_until_complete(ctx, None)?;
504
505 Ok(EventLoopResult {
506 timers_fired,
507 ..result
508 })
509 }
510}
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515
516 #[test]
517 fn test_event_loop_creation() {
518 let el = EventLoop::new();
519 assert!(!el.has_pending_tasks());
520 assert_eq!(el.macrotask_count(), 0);
521 assert_eq!(el.timer_count(), 0);
522 }
523
524 #[test]
525 fn test_schedule_macrotask() {
526 let mut el = EventLoop::new();
527 el.schedule_macrotask(JSValue::undefined(), vec![]);
528 assert!(el.has_pending_tasks());
529 assert_eq!(el.macrotask_count(), 1);
530 }
531
532 #[test]
533 fn test_schedule_timer() {
534 let mut el = EventLoop::new();
535 let id = el.schedule_timer(JSValue::undefined(), vec![], 100, false);
536 assert_eq!(id, 1);
537 assert!(el.has_pending_tasks());
538 assert_eq!(el.timer_count(), 1);
539 assert!(el.is_timer_active(id));
540 }
541
542 #[test]
543 fn test_clear_timer() {
544 let mut el = EventLoop::new();
545 let id = el.schedule_timer(JSValue::undefined(), vec![], 100, false);
546 assert!(el.is_timer_active(id));
547 el.clear_timer(id);
548 assert!(!el.is_timer_active(id));
549 }
550
551 #[test]
552 fn test_schedule_animation_callback() {
553 let mut el = EventLoop::new();
554 let id = el.schedule_animation_callback(JSValue::undefined());
555 assert_eq!(id, 1);
556 assert_eq!(el.animation_callback_count(), 1);
557 }
558
559 #[test]
560 fn test_cancel_animation_callback() {
561 let mut el = EventLoop::new();
562 let id = el.schedule_animation_callback(JSValue::undefined());
563 el.cancel_animation_callback(id);
564 assert_eq!(el.animation_callback_count(), 0);
565 }
566
567 #[test]
568 fn test_timer_ordering() {
569 let mut el = EventLoop::new();
570
571 let _id1 = el.schedule_timer(JSValue::undefined(), vec![], 300, false);
572 let id2 = el.schedule_timer(JSValue::undefined(), vec![], 100, false);
573 let _id3 = el.schedule_timer(JSValue::undefined(), vec![], 200, false);
574
575 let top = el.timer_heap.peek().unwrap();
576 assert_eq!(top.id, id2);
577 }
578}