Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/lib/libeventloop.js
4150 views
1
/**
2
* @license
3
* Copyright 2010 The Emscripten Authors
4
* SPDX-License-Identifier: MIT
5
*/
6
7
// Implementation of functions from emscripten/eventloop.h.
8
9
LibraryJSEventLoop = {
10
emscripten_unwind_to_js_event_loop: () => {
11
throw 'unwind';
12
},
13
14
$safeSetTimeout__deps: ['$callUserCallback'],
15
$safeSetTimeout__docs: '/** @param {number=} timeout */',
16
$safeSetTimeout: (func, timeout) => {
17
{{{ runtimeKeepalivePush() }}}
18
return setTimeout(() => {
19
{{{ runtimeKeepalivePop() }}}
20
callUserCallback(func);
21
}, timeout);
22
},
23
24
// Just like setImmediate but returns an i32 that can be passed back
25
// to wasm rather than a JS object.
26
$setImmediateWrapped: (func) => {
27
setImmediateWrapped.mapping ||= [];
28
var id = setImmediateWrapped.mapping.length;
29
setImmediateWrapped.mapping[id] = setImmediate(() => {
30
setImmediateWrapped.mapping[id] = undefined;
31
func();
32
});
33
return id;
34
},
35
36
$safeRequestAnimationFrame__deps: ['$MainLoop'],
37
$safeRequestAnimationFrame: (func) => {
38
{{{ runtimeKeepalivePush() }}}
39
return MainLoop.requestAnimationFrame(() => {
40
{{{ runtimeKeepalivePop() }}}
41
callUserCallback(func);
42
});
43
},
44
45
// Just like clearImmediate but takes an i32 rather than an object.
46
$clearImmediateWrapped: (id) => {
47
#if ASSERTIONS
48
assert(id);
49
assert(setImmediateWrapped.mapping[id]);
50
#endif
51
clearImmediate(setImmediateWrapped.mapping[id]);
52
setImmediateWrapped.mapping[id] = undefined;
53
},
54
55
$emSetImmediate__deps: ['$setImmediateWrapped', '$clearImmediateWrapped', '$emClearImmediate'],
56
$emSetImmediate__postset: `
57
if (typeof setImmediate != "undefined") {
58
emSetImmediate = setImmediateWrapped;
59
emClearImmediate = clearImmediateWrapped;
60
} else if (typeof addEventListener == "function") {
61
var __setImmediate_id_counter = 0;
62
var __setImmediate_queue = [];
63
var __setImmediate_message_id = "_si";
64
/** @param {Event} e */
65
var __setImmediate_cb = (e) => {
66
if (e.data === __setImmediate_message_id) {
67
e.stopPropagation();
68
__setImmediate_queue.shift()();
69
++__setImmediate_id_counter;
70
}
71
}
72
addEventListener("message", __setImmediate_cb, true);
73
emSetImmediate = (func) => {
74
postMessage(__setImmediate_message_id, "*");
75
return __setImmediate_id_counter + __setImmediate_queue.push(func) - 1;
76
}
77
emClearImmediate = /**@type{function(number=)}*/((id) => {
78
var index = id - __setImmediate_id_counter;
79
// must preserve the order and count of elements in the queue, so replace the pending callback with an empty function
80
if (index >= 0 && index < __setImmediate_queue.length) __setImmediate_queue[index] = () => {};
81
})
82
}`,
83
$emSetImmediate: undefined,
84
85
$emClearImmediate_deps: ['$emSetImmediate'],
86
$emClearImmediate: undefined,
87
88
emscripten_set_immediate__deps: ['$emSetImmediate', '$callUserCallback'],
89
emscripten_set_immediate: (cb, userData) => {
90
{{{ runtimeKeepalivePush(); }}}
91
return emSetImmediate(() => {
92
{{{ runtimeKeepalivePop(); }}}
93
callUserCallback(() => {{{ makeDynCall('vp', 'cb') }}}(userData));
94
});
95
},
96
97
emscripten_clear_immediate__deps: ['$emClearImmediate'],
98
emscripten_clear_immediate: (id) => {
99
{{{ runtimeKeepalivePop(); }}}
100
emClearImmediate(id);
101
},
102
103
emscripten_set_immediate_loop__deps: ['$emSetImmediate', '$callUserCallback'],
104
emscripten_set_immediate_loop: (cb, userData) => {
105
function tick() {
106
callUserCallback(() => {
107
if ({{{ makeDynCall('ip', 'cb') }}}(userData)) {
108
emSetImmediate(tick);
109
} else {
110
{{{ runtimeKeepalivePop(); }}}
111
}
112
});
113
}
114
{{{ runtimeKeepalivePush(); }}}
115
emSetImmediate(tick);
116
},
117
118
emscripten_set_timeout__deps: ['$safeSetTimeout'],
119
emscripten_set_timeout: (cb, msecs, userData) =>
120
safeSetTimeout(() => {{{ makeDynCall('vp', 'cb') }}}(userData), msecs),
121
122
#if AUDIO_WORKLET
123
// Use a wrapper function here since simply aliasing `clearTimeout` would
124
// cause the module to fail to load in the audio worklet context.
125
emscripten_clear_timeout: (id) => clearTimeout(id),
126
#else
127
emscripten_clear_timeout: 'clearTimeout',
128
#endif
129
130
emscripten_set_timeout_loop__deps: ['$callUserCallback', 'emscripten_get_now'],
131
emscripten_set_timeout_loop: (cb, msecs, userData) => {
132
function tick() {
133
var t = _emscripten_get_now();
134
var n = t + msecs;
135
{{{ runtimeKeepalivePop() }}}
136
callUserCallback(() => {
137
if ({{{ makeDynCall('idp', 'cb') }}}(t, userData)) {
138
{{{ runtimeKeepalivePush() }}}
139
// Save a little bit of code space: modern browsers should treat
140
// negative setTimeout as timeout of 0
141
// (https://stackoverflow.com/questions/8430966/is-calling-settimeout-with-a-negative-delay-ok)
142
var remaining = n - _emscripten_get_now();
143
#if ENVIRONMENT_MAY_BE_NODE
144
// Recent revsions of node, however, give TimeoutNegativeWarning
145
remaining = Math.max(0, remaining);
146
#endif
147
setTimeout(tick, remaining);
148
}
149
});
150
}
151
{{{ runtimeKeepalivePush() }}}
152
return setTimeout(tick, 0);
153
},
154
155
emscripten_set_interval__deps: ['$callUserCallback'],
156
emscripten_set_interval: (cb, msecs, userData) => {
157
{{{ runtimeKeepalivePush() }}}
158
return setInterval(() => {
159
callUserCallback(() => {{{ makeDynCall('vp', 'cb') }}}(userData));
160
}, msecs);
161
},
162
163
emscripten_clear_interval: (id) => {
164
{{{ runtimeKeepalivePop() }}}
165
clearInterval(id);
166
},
167
168
emscripten_async_call__deps: ['$safeSetTimeout', '$safeRequestAnimationFrame'],
169
emscripten_async_call: (func, arg, millis) => {
170
var wrapper = () => {{{ makeDynCall('vp', 'func') }}}(arg);
171
172
if (millis >= 0
173
#if ENVIRONMENT_MAY_BE_NODE
174
// node does not support requestAnimationFrame
175
|| ENVIRONMENT_IS_NODE
176
#endif
177
) {
178
safeSetTimeout(wrapper, millis);
179
} else {
180
safeRequestAnimationFrame(wrapper);
181
}
182
},
183
184
$registerPostMainLoop: (f) => {
185
// Does nothing unless $MainLoop is included/used.
186
typeof MainLoop != 'undefined' && MainLoop.postMainLoop.push(f);
187
},
188
189
$registerPreMainLoop: (f) => {
190
// Does nothing unless $MainLoop is included/used.
191
typeof MainLoop != 'undefined' && MainLoop.preMainLoop.push(f);
192
},
193
194
$MainLoop__internal: true,
195
$MainLoop__deps: ['$setMainLoop', '$callUserCallback', 'emscripten_set_main_loop_timing'],
196
$MainLoop__postset: `
197
Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame;
198
Module['pauseMainLoop'] = MainLoop.pause;
199
Module['resumeMainLoop'] = MainLoop.resume;
200
MainLoop.init();`,
201
$MainLoop: {
202
running: false,
203
scheduler: null,
204
method: '',
205
// Each main loop is numbered with a ID in sequence order. Only one main
206
// loop can run at a time. This variable stores the ordinal number of the
207
// main loop that is currently allowed to run. All previous main loops
208
// will quit themselves. This is incremented whenever a new main loop is
209
// created.
210
currentlyRunningMainloop: 0,
211
// The main loop tick function that will be called at each iteration.
212
func: null,
213
// The argument that will be passed to the main loop. (of type void*)
214
arg: 0,
215
timingMode: 0,
216
timingValue: 0,
217
currentFrameNumber: 0,
218
queue: [],
219
preMainLoop: [],
220
postMainLoop: [],
221
222
pause() {
223
MainLoop.scheduler = null;
224
// Incrementing this signals the previous main loop that it's now become old, and it must return.
225
MainLoop.currentlyRunningMainloop++;
226
},
227
228
resume() {
229
MainLoop.currentlyRunningMainloop++;
230
var timingMode = MainLoop.timingMode;
231
var timingValue = MainLoop.timingValue;
232
var func = MainLoop.func;
233
MainLoop.func = null;
234
// do not set timing and call scheduler, we will do it on the next lines
235
setMainLoop(func, 0, false, MainLoop.arg, true);
236
_emscripten_set_main_loop_timing(timingMode, timingValue);
237
MainLoop.scheduler();
238
},
239
240
updateStatus() {
241
#if expectToReceiveOnModule('setStatus')
242
if (Module['setStatus']) {
243
var message = Module['statusMessage'] || 'Please wait...';
244
var remaining = MainLoop.remainingBlockers ?? 0;
245
var expected = MainLoop.expectedBlockers ?? 0;
246
if (remaining) {
247
if (remaining < expected) {
248
Module['setStatus'](`{message} ({expected - remaining}/{expected})`);
249
} else {
250
Module['setStatus'](message);
251
}
252
} else {
253
Module['setStatus']('');
254
}
255
}
256
#endif
257
},
258
259
init() {
260
#if expectToReceiveOnModule('preMainLoop')
261
Module['preMainLoop'] && MainLoop.preMainLoop.push(Module['preMainLoop']);
262
#endif
263
#if expectToReceiveOnModule('postMainLoop')
264
Module['postMainLoop'] && MainLoop.postMainLoop.push(Module['postMainLoop']);
265
#endif
266
},
267
268
runIter(func) {
269
if (ABORT) return;
270
for (var pre of MainLoop.preMainLoop) {
271
if (pre() === false) {
272
return; // |return false| skips a frame
273
}
274
}
275
callUserCallback(func);
276
for (var post of MainLoop.postMainLoop) {
277
post();
278
}
279
#if STACK_OVERFLOW_CHECK
280
checkStackCookie();
281
#endif
282
},
283
284
nextRAF: 0,
285
286
fakeRequestAnimationFrame(func) {
287
// try to keep 60fps between calls to here
288
var now = Date.now();
289
if (MainLoop.nextRAF === 0) {
290
MainLoop.nextRAF = now + 1000/60;
291
} else {
292
while (now + 2 >= MainLoop.nextRAF) { // fudge a little, to avoid timer jitter causing us to do lots of delay:0
293
MainLoop.nextRAF += 1000/60;
294
}
295
}
296
var delay = Math.max(MainLoop.nextRAF - now, 0);
297
setTimeout(func, delay);
298
},
299
300
requestAnimationFrame(func) {
301
if (typeof requestAnimationFrame == 'function') {
302
requestAnimationFrame(func);
303
} else {
304
MainLoop.fakeRequestAnimationFrame(func);
305
}
306
},
307
},
308
309
emscripten_get_main_loop_timing__deps: ['$MainLoop'],
310
emscripten_get_main_loop_timing: (mode, value) => {
311
if (mode) {{{ makeSetValue('mode', 0, 'MainLoop.timingMode', 'i32') }}};
312
if (value) {{{ makeSetValue('value', 0, 'MainLoop.timingValue', 'i32') }}};
313
},
314
315
emscripten_set_main_loop_timing__deps: ['$MainLoop'],
316
emscripten_set_main_loop_timing: (mode, value) => {
317
MainLoop.timingMode = mode;
318
MainLoop.timingValue = value;
319
320
if (!MainLoop.func) {
321
#if ASSERTIONS
322
err('emscripten_set_main_loop_timing: Cannot set timing mode for main loop since a main loop does not exist! Call emscripten_set_main_loop first to set one up.');
323
#endif
324
return 1; // Return non-zero on failure, can't set timing mode when there is no main loop.
325
}
326
327
if (!MainLoop.running) {
328
{{{ runtimeKeepalivePush() }}}
329
MainLoop.running = true;
330
}
331
if (mode == {{{ cDefs.EM_TIMING_SETTIMEOUT }}}) {
332
MainLoop.scheduler = function MainLoop_scheduler_setTimeout() {
333
var timeUntilNextTick = Math.max(0, MainLoop.tickStartTime + value - _emscripten_get_now())|0;
334
setTimeout(MainLoop.runner, timeUntilNextTick); // doing this each time means that on exception, we stop
335
};
336
MainLoop.method = 'timeout';
337
} else if (mode == {{{ cDefs.EM_TIMING_RAF }}}) {
338
MainLoop.scheduler = function MainLoop_scheduler_rAF() {
339
MainLoop.requestAnimationFrame(MainLoop.runner);
340
};
341
MainLoop.method = 'rAF';
342
} else if (mode == {{{ cDefs.EM_TIMING_SETIMMEDIATE}}}) {
343
if (typeof MainLoop.setImmediate == 'undefined') {
344
if (typeof setImmediate == 'undefined') {
345
// Emulate setImmediate. (note: not a complete polyfill, we don't emulate clearImmediate() to keep code size to minimum, since not needed)
346
var setImmediates = [];
347
var emscriptenMainLoopMessageId = 'setimmediate';
348
/** @param {Event} event */
349
var MainLoop_setImmediate_messageHandler = (event) => {
350
// When called in current thread or Worker, the main loop ID is structured slightly different to accommodate for --proxy-to-worker runtime listening to Worker events,
351
// so check for both cases.
352
if (event.data === emscriptenMainLoopMessageId || event.data.target === emscriptenMainLoopMessageId) {
353
event.stopPropagation();
354
setImmediates.shift()();
355
}
356
};
357
addEventListener("message", MainLoop_setImmediate_messageHandler, true);
358
MainLoop.setImmediate = /** @type{function(function(): ?, ...?): number} */((func) => {
359
setImmediates.push(func);
360
if (ENVIRONMENT_IS_WORKER) {
361
Module['setImmediates'] ??= [];
362
Module['setImmediates'].push(func);
363
postMessage({target: emscriptenMainLoopMessageId}); // In --proxy-to-worker, route the message via proxyClient.js
364
} else postMessage(emscriptenMainLoopMessageId, "*"); // On the main thread, can just send the message to itself.
365
});
366
} else {
367
MainLoop.setImmediate = setImmediate;
368
}
369
}
370
MainLoop.scheduler = function MainLoop_scheduler_setImmediate() {
371
MainLoop.setImmediate(MainLoop.runner);
372
};
373
MainLoop.method = 'immediate';
374
}
375
return 0;
376
},
377
378
emscripten_set_main_loop__deps: ['$setMainLoop'],
379
emscripten_set_main_loop: (func, fps, simulateInfiniteLoop) => {
380
var iterFunc = {{{ makeDynCall('v', 'func') }}};
381
setMainLoop(iterFunc, fps, simulateInfiniteLoop);
382
},
383
384
$setMainLoop__internal: true,
385
$setMainLoop__deps: [
386
'$MainLoop',
387
'emscripten_set_main_loop_timing', 'emscripten_get_now',
388
#if !MINIMAL_RUNTIME
389
'$maybeExit',
390
#endif
391
],
392
$setMainLoop__docs: `
393
/**
394
* @param {number=} arg
395
* @param {boolean=} noSetTiming
396
*/`,
397
$setMainLoop: (iterFunc, fps, simulateInfiniteLoop, arg, noSetTiming) => {
398
#if ASSERTIONS
399
assert(!MainLoop.func, 'emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.');
400
#endif
401
MainLoop.func = iterFunc;
402
MainLoop.arg = arg;
403
404
var thisMainLoopId = MainLoop.currentlyRunningMainloop;
405
function checkIsRunning() {
406
if (thisMainLoopId < MainLoop.currentlyRunningMainloop) {
407
#if RUNTIME_DEBUG
408
dbg('main loop exiting');
409
#endif
410
{{{ runtimeKeepalivePop() }}}
411
#if !MINIMAL_RUNTIME
412
maybeExit();
413
#endif
414
return false;
415
}
416
return true;
417
}
418
419
// We create the loop runner here but it is not actually running until
420
// _emscripten_set_main_loop_timing is called (which might happen a
421
// later time). This member signifies that the current runner has not
422
// yet been started so that we can call runtimeKeepalivePush when it
423
// gets it timing set for the first time.
424
MainLoop.running = false;
425
MainLoop.runner = function MainLoop_runner() {
426
if (ABORT) return;
427
if (MainLoop.queue.length > 0) {
428
var start = Date.now();
429
var blocker = MainLoop.queue.shift();
430
blocker.func(blocker.arg);
431
if (MainLoop.remainingBlockers) {
432
var remaining = MainLoop.remainingBlockers;
433
var next = remaining%1 == 0 ? remaining-1 : Math.floor(remaining);
434
if (blocker.counted) {
435
MainLoop.remainingBlockers = next;
436
} else {
437
// not counted, but move the progress along a tiny bit
438
next = next + 0.5; // do not steal all the next one's progress
439
MainLoop.remainingBlockers = (8*remaining + next)/9;
440
}
441
}
442
#if RUNTIME_DEBUG
443
dbg(`main loop blocker "${blocker.name}" took '${Date.now() - start} ms`); //, left: ' + MainLoop.remainingBlockers);
444
#endif
445
MainLoop.updateStatus();
446
447
// catches pause/resume main loop from blocker execution
448
if (!checkIsRunning()) return;
449
450
setTimeout(MainLoop.runner, 0);
451
return;
452
}
453
454
// catch pauses from non-main loop sources
455
if (!checkIsRunning()) return;
456
457
// Implement very basic swap interval control
458
MainLoop.currentFrameNumber = MainLoop.currentFrameNumber + 1 | 0;
459
if (MainLoop.timingMode == {{{ cDefs.EM_TIMING_RAF }}} && MainLoop.timingValue > 1 && MainLoop.currentFrameNumber % MainLoop.timingValue != 0) {
460
// Not the scheduled time to render this frame - skip.
461
MainLoop.scheduler();
462
return;
463
} else if (MainLoop.timingMode == {{{ cDefs.EM_TIMING_SETTIMEOUT }}}) {
464
MainLoop.tickStartTime = _emscripten_get_now();
465
}
466
467
#if ASSERTIONS
468
if (MainLoop.method === 'timeout' && Module['ctx']) {
469
warnOnce('Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!');
470
MainLoop.method = ''; // just warn once per call to set main loop
471
}
472
#endif
473
474
MainLoop.runIter(iterFunc);
475
476
// catch pauses from the main loop itself
477
if (!checkIsRunning()) return;
478
479
MainLoop.scheduler();
480
}
481
482
if (!noSetTiming) {
483
if (fps > 0) {
484
_emscripten_set_main_loop_timing({{{ cDefs.EM_TIMING_SETTIMEOUT }}}, 1000.0 / fps);
485
} else {
486
// Do rAF by rendering each frame (no decimating)
487
_emscripten_set_main_loop_timing({{{ cDefs.EM_TIMING_RAF }}}, 1);
488
}
489
490
MainLoop.scheduler();
491
}
492
493
if (simulateInfiniteLoop) {
494
throw 'unwind';
495
}
496
},
497
498
emscripten_set_main_loop_arg__deps: ['$setMainLoop'],
499
emscripten_set_main_loop_arg: (func, arg, fps, simulateInfiniteLoop) => {
500
var iterFunc = () => {{{ makeDynCall('vp', 'func') }}}(arg);
501
setMainLoop(iterFunc, fps, simulateInfiniteLoop, arg);
502
},
503
504
emscripten_cancel_main_loop__deps: ['$MainLoop'],
505
emscripten_cancel_main_loop: () => {
506
MainLoop.pause();
507
MainLoop.func = null;
508
},
509
510
emscripten_pause_main_loop__deps: ['$MainLoop'],
511
emscripten_pause_main_loop: () => MainLoop.pause(),
512
513
emscripten_resume_main_loop__deps: ['$MainLoop'],
514
emscripten_resume_main_loop: () => MainLoop.resume(),
515
516
_emscripten_push_main_loop_blocker__deps: ['$MainLoop'],
517
_emscripten_push_main_loop_blocker: (func, arg, name) => {
518
MainLoop.queue.push({ func: () => {
519
{{{ makeDynCall('vp', 'func') }}}(arg);
520
}, name: UTF8ToString(name), counted: true });
521
MainLoop.updateStatus();
522
},
523
524
_emscripten_push_uncounted_main_loop_blocker__deps: ['$MainLoop'],
525
_emscripten_push_uncounted_main_loop_blocker: (func, arg, name) => {
526
MainLoop.queue.push({ func: () => {
527
{{{ makeDynCall('vp', 'func') }}}(arg);
528
}, name: UTF8ToString(name), counted: false });
529
MainLoop.updateStatus();
530
},
531
532
emscripten_set_main_loop_expected_blockers__deps: ['$MainLoop'],
533
emscripten_set_main_loop_expected_blockers: (num) => {
534
MainLoop.expectedBlockers = num;
535
MainLoop.remainingBlockers = num;
536
MainLoop.updateStatus();
537
},
538
539
};
540
541
addToLibrary(LibraryJSEventLoop);
542
543