Skip to content

Commit e3dc89f

Browse files
authored
feat(core): queueMacroTask (#8904)
1 parent c1f231d commit e3dc89f

6 files changed

Lines changed: 89 additions & 15 deletions

File tree

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1+
import { queueMacrotask } from '../utils/macrotask-scheduler';
12
import { FPSCallback } from '../fps-meter/fps-native';
23
import { getTimeInFrameBase } from './animation-native';
4+
import { Trace } from 'trace';
35

46
export interface FrameRequestCallback {
57
(time: number): void;
68
}
79

10+
type AnimationFrameCallbacks = { [key: string]: FrameRequestCallback };
11+
812
let animationId = 0;
9-
let nextFrameAnimationCallbacks: { [key: string]: FrameRequestCallback } = {};
13+
let currentFrameAnimationCallbacks: AnimationFrameCallbacks = {}; // requests that were scheduled in this frame and must be called ASAP
14+
let currentFrameScheduled = false;
15+
let nextFrameAnimationCallbacks: AnimationFrameCallbacks = {}; // requests there were scheduled in another request and must be called in the next frame
1016
let shouldStop = true;
1117
let inAnimationFrame = false;
1218
let fpsCallback: FPSCallback;
@@ -23,40 +29,67 @@ function ensureNative() {
2329
fpsCallback = new FPSCallback(doFrame);
2430
}
2531

26-
function doFrame(currentTimeMillis: number) {
27-
lastFrameTime = currentTimeMillis;
28-
shouldStop = true;
29-
const thisFrameCbs = nextFrameAnimationCallbacks;
30-
nextFrameAnimationCallbacks = {};
32+
function callAnimationCallbacks(thisFrameCbs: AnimationFrameCallbacks, frameTime: number): void {
3133
inAnimationFrame = true;
3234
for (const animationId in thisFrameCbs) {
3335
if (thisFrameCbs[animationId]) {
34-
thisFrameCbs[animationId](lastFrameTime);
36+
try {
37+
thisFrameCbs[animationId](frameTime);
38+
} catch (err) {
39+
const msg = err ? err.stack || err : err;
40+
Trace.write(`Error in requestAnimationFrame: ${msg}`, Trace.categories.Error, Trace.messageType.error);
41+
}
3542
}
3643
}
3744
inAnimationFrame = false;
45+
}
46+
47+
function doCurrentFrame() {
48+
// if we're not getting accurate frame times
49+
// set last frame time as the current time
50+
if (!fpsCallback || !fpsCallback.running) {
51+
lastFrameTime = getTimeInFrameBase();
52+
}
53+
currentFrameScheduled = false;
54+
const thisFrameCbs = currentFrameAnimationCallbacks;
55+
currentFrameAnimationCallbacks = {};
56+
callAnimationCallbacks(thisFrameCbs, lastFrameTime);
57+
}
58+
59+
function doFrame(currentTimeMillis: number) {
60+
lastFrameTime = currentTimeMillis;
61+
shouldStop = true;
62+
const thisFrameCbs = nextFrameAnimationCallbacks;
63+
nextFrameAnimationCallbacks = {};
64+
callAnimationCallbacks(thisFrameCbs, lastFrameTime);
3865
if (shouldStop) {
3966
fpsCallback.stop(); // TODO: check performance without stopping to allow consistent frame times
4067
}
4168
}
4269

70+
function ensureCurrentFrameScheduled() {
71+
if (!currentFrameScheduled) {
72+
currentFrameScheduled = true;
73+
queueMacrotask(doCurrentFrame);
74+
}
75+
}
76+
4377
export function requestAnimationFrame(cb: FrameRequestCallback): number {
78+
const animId = getNewId();
4479
if (!inAnimationFrame) {
45-
inAnimationFrame = true;
46-
zonedCallback(cb)(getTimeInFrameBase()); // TODO: store and use lastFrameTime
47-
inAnimationFrame = false;
48-
49-
return getNewId();
80+
ensureCurrentFrameScheduled();
81+
currentFrameAnimationCallbacks[animId] = zonedCallback(cb) as FrameRequestCallback;
82+
return animId;
5083
}
5184
ensureNative();
52-
const animId = getNewId();
5385
nextFrameAnimationCallbacks[animId] = zonedCallback(cb) as FrameRequestCallback;
5486
shouldStop = false;
5587
fpsCallback.start();
5688

5789
return animId;
5890
}
5991

60-
export function cancelAnimationFrame(id: number) {
92+
export function cancelAnimationFrame(id: number): void {
93+
delete currentFrameAnimationCallbacks[id];
6194
delete nextFrameAnimationCallbacks[id];
6295
}

‎packages/core/index.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export * from './trace';
110110

111111
export * from './ui';
112112

113-
import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, RESOURCE_PREFIX, FILE_PREFIX } from './utils';
113+
import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, queueMacrotask, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, RESOURCE_PREFIX, FILE_PREFIX } from './utils';
114114
import { ClassInfo, getClass, getBaseClasses, getClassInfo, isBoolean, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isUndefined, toUIString, verifyCallback } from './utils/types';
115115

116116
export const Utils = {
@@ -124,6 +124,7 @@ export const Utils = {
124124
mainThreadify,
125125
isMainThread,
126126
dispatchToMainThread,
127+
queueMacrotask,
127128
releaseNativeObject,
128129

129130
getModuleName,

‎packages/core/utils/index.d.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { dip, px } from '../ui/core/view';
22

33
export * from './mainthread-helper';
4+
export * from './macrotask-scheduler';
45
export { Source } from './debug';
56

67
export * from './native-helper';
@@ -192,6 +193,12 @@ export function GC();
192193
*/
193194
export function releaseNativeObject(object: any /*java.lang.Object | NSObject*/);
194195

196+
/**
197+
* Queues the passed function to be ran in a macroTask
198+
* @param task the function to execute as a macroTask
199+
*/
200+
export function queueMacrotask(task: () => void): void;
201+
195202
/**
196203
* Checks if the current thread is the main thread. Directly calls the passed function
197204
* if it is, or dispatches it to the main thread otherwise.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* Queues the passed function to be ran in a macroTask
3+
* @param task the function to execute as a macroTask
4+
*/
5+
export function queueMacrotask(task: () => void): void;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Trace } from 'trace';
2+
import { dispatchToMainThread } from './mainthread-helper';
3+
4+
let scheduled = false;
5+
let macroTaskQueue: Array<() => void> = [];
6+
7+
function drainMacrotaskQueue() {
8+
const currentQueue = macroTaskQueue;
9+
macroTaskQueue = [];
10+
scheduled = false;
11+
currentQueue.forEach((task) => {
12+
try {
13+
task();
14+
} catch (err) {
15+
const msg = err ? err.stack || err : err;
16+
Trace.write(`Error in macroTask: ${msg}`, Trace.categories.Error, Trace.messageType.error);
17+
}
18+
});
19+
}
20+
21+
export function queueMacrotask(task: () => void): void {
22+
macroTaskQueue.push(task);
23+
if (!scheduled) {
24+
scheduled = true;
25+
dispatchToMainThread(drainMacrotaskQueue);
26+
}
27+
}

‎packages/core/utils/utils-common.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as layout from './layout-helper';
55

66
export { layout };
77
export * from './mainthread-helper';
8+
export * from './macrotask-scheduler';
89

910
export const RESOURCE_PREFIX = 'res://';
1011
export const FILE_PREFIX = 'file:///';

0 commit comments

Comments
 (0)