runtime/event_loop/
macrotasks.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use std::collections::HashMap;
8use std::fmt;
9use std::fmt::{Debug, Formatter};
10use std::sync::Arc;
11use std::sync::atomic::{AtomicBool, Ordering};
12use std::time::{Duration, Instant};
13
14use ion::{Context, ErrorReport, Function, Object, Value};
15use mozjs::jsapi::JSFunction;
16use mozjs::jsval::JSVal;
17
18pub struct SignalMacrotask {
19	callback: Option<Box<dyn FnOnce()>>,
20	terminate: Arc<AtomicBool>,
21	scheduled: Instant,
22}
23
24impl SignalMacrotask {
25	pub fn new(callback: Box<dyn FnOnce()>, terminate: Arc<AtomicBool>, duration: Duration) -> SignalMacrotask {
26		SignalMacrotask {
27			callback: Some(callback),
28			terminate,
29			scheduled: Instant::now() + duration,
30		}
31	}
32}
33
34impl Debug for SignalMacrotask {
35	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
36		f.debug_struct("SignalMacrotask")
37			.field("callback", &self.callback.as_ref().map(|_| ()))
38			.field("terminate", &self.terminate.as_ref())
39			.field("scheduled", &self.scheduled)
40			.finish()
41	}
42}
43
44#[derive(Debug)]
45pub struct TimerMacrotask {
46	callback: *mut JSFunction,
47	arguments: Box<[JSVal]>,
48	repeat: bool,
49	scheduled: Instant,
50	duration: Duration,
51	nesting: u8,
52}
53
54impl TimerMacrotask {
55	pub fn new(callback: &Function, arguments: Box<[JSVal]>, repeat: bool, duration: Duration) -> TimerMacrotask {
56		TimerMacrotask {
57			callback: callback.get(),
58			arguments,
59			repeat,
60			duration,
61			scheduled: Instant::now(),
62			nesting: 0,
63		}
64	}
65
66	pub fn reset(&mut self) -> bool {
67		if self.repeat {
68			self.scheduled = Instant::now();
69		}
70		self.repeat
71	}
72}
73
74#[derive(Debug)]
75pub struct UserMacrotask {
76	callback: *mut JSFunction,
77	scheduled: Instant,
78}
79
80impl UserMacrotask {
81	pub fn new(callback: &Function) -> UserMacrotask {
82		UserMacrotask {
83			callback: callback.get(),
84			scheduled: Instant::now(),
85		}
86	}
87}
88
89#[derive(Debug)]
90pub enum Macrotask {
91	Signal(SignalMacrotask),
92	Timer(TimerMacrotask),
93	User(UserMacrotask),
94}
95
96#[derive(Debug, Default)]
97pub struct MacrotaskQueue {
98	pub(crate) map: HashMap<u32, Macrotask>,
99	pub(crate) nesting: u8,
100	next: Option<u32>,
101	latest: Option<u32>,
102}
103
104impl Macrotask {
105	pub fn run(&mut self, cx: &Context) -> Result<(), Option<ErrorReport>> {
106		if let Macrotask::Signal(signal) = self {
107			if let Some(callback) = signal.callback.take() {
108				callback();
109			}
110			return Ok(());
111		}
112
113		let (callback, args) = match self {
114			Macrotask::Timer(timer) => (timer.callback, timer.arguments.clone()),
115			Macrotask::User(user) => (user.callback, Box::default()),
116			_ => unreachable!(),
117		};
118
119		let callback = Function::from(cx.root(callback));
120		let args: Vec<_> = args.into_vec().into_iter().map(|value| Value::from(cx.root(value))).collect();
121
122		callback.call(cx, &Object::global(cx), args.as_slice())?;
123		Ok(())
124	}
125
126	pub fn remove(&mut self) -> bool {
127		match self {
128			Macrotask::Timer(timer) => !timer.reset(),
129			_ => true,
130		}
131	}
132
133	fn terminate(&self) -> bool {
134		match self {
135			Macrotask::Signal(signal) => signal.terminate.load(Ordering::SeqCst),
136			_ => false,
137		}
138	}
139
140	fn remaining(&self) -> Duration {
141		match self {
142			Macrotask::Signal(signal) => signal.scheduled - Instant::now(),
143			Macrotask::Timer(timer) => timer.scheduled + timer.duration - Instant::now(),
144			Macrotask::User(user) => user.scheduled - Instant::now(),
145		}
146	}
147}
148
149impl MacrotaskQueue {
150	pub fn run_job(&mut self, cx: &Context) -> Result<(), Option<ErrorReport>> {
151		self.find_next();
152		if let Some(next) = self.next {
153			{
154				let macrotask = self.map.get_mut(&next);
155				if let Some(macrotask) = macrotask {
156					macrotask.run(cx)?;
157				}
158			}
159
160			// The previous reference may be invalidated by running the macrotask.
161			let macrotask = self.map.get_mut(&next);
162			if let Some(macrotask) = macrotask
163				&& macrotask.remove()
164			{
165				self.map.remove(&next);
166			}
167		}
168
169		Ok(())
170	}
171
172	pub fn enqueue(&mut self, mut macrotask: Macrotask, id: Option<u32>) -> u32 {
173		let index = id.unwrap_or_else(|| self.latest.map_or(0, |l| l + 1));
174
175		let next = self.next.and_then(|next| self.map.get(&next));
176		if let Some(next) = next {
177			if macrotask.remaining() < next.remaining() {
178				self.set_next(index, &macrotask);
179			}
180		} else {
181			self.set_next(index, &macrotask);
182		}
183
184		if let Macrotask::Timer(timer) = &mut macrotask {
185			self.nesting += 1;
186			timer.nesting = self.nesting;
187		}
188
189		self.latest = Some(index);
190		self.map.insert(index, macrotask);
191
192		index
193	}
194
195	pub fn remove(&mut self, id: u32) {
196		if self.map.remove(&id).is_some()
197			&& let Some(next) = self.next
198			&& next == id
199		{
200			self.next = None;
201		}
202	}
203
204	pub fn find_next(&mut self) {
205		let mut next: Option<(u32, Duration)> = None;
206
207		self.map.retain(|id, macrotask| {
208			if macrotask.terminate() {
209				return false;
210			}
211
212			let duration = macrotask.remaining();
213			if let Some((_, next_duration)) = next {
214				if duration < next_duration {
215					next = Some((*id, duration));
216				}
217			} else if duration <= Duration::ZERO {
218				next = Some((*id, duration));
219			}
220
221			true
222		});
223
224		self.next = next.map(|(id, _)| id);
225	}
226
227	pub fn set_next(&mut self, index: u32, macrotask: &Macrotask) {
228		if macrotask.remaining() <= Duration::ZERO {
229			self.next = Some(index);
230		}
231	}
232
233	pub fn is_empty(&self) -> bool {
234		self.map.is_empty()
235	}
236}