feat(core): queueMacroTask (#8904)

This commit is contained in:
Eduardo Speroni
2020-11-12 00:52:29 -03:00
committed by GitHub
parent c1f231d88e
commit e3dc89fbfc
6 changed files with 91 additions and 17 deletions

View File

@ -1,12 +1,18 @@
import { queueMacrotask } from '../utils/macrotask-scheduler';
import { FPSCallback } from '../fps-meter/fps-native'; import { FPSCallback } from '../fps-meter/fps-native';
import { getTimeInFrameBase } from './animation-native'; import { getTimeInFrameBase } from './animation-native';
import { Trace } from 'trace';
export interface FrameRequestCallback { export interface FrameRequestCallback {
(time: number): void; (time: number): void;
} }
type AnimationFrameCallbacks = { [key: string]: FrameRequestCallback };
let animationId = 0; let animationId = 0;
let nextFrameAnimationCallbacks: { [key: string]: FrameRequestCallback } = {}; let currentFrameAnimationCallbacks: AnimationFrameCallbacks = {}; // requests that were scheduled in this frame and must be called ASAP
let currentFrameScheduled = false;
let nextFrameAnimationCallbacks: AnimationFrameCallbacks = {}; // requests there were scheduled in another request and must be called in the next frame
let shouldStop = true; let shouldStop = true;
let inAnimationFrame = false; let inAnimationFrame = false;
let fpsCallback: FPSCallback; let fpsCallback: FPSCallback;
@ -23,33 +29,59 @@ function ensureNative() {
fpsCallback = new FPSCallback(doFrame); fpsCallback = new FPSCallback(doFrame);
} }
function callAnimationCallbacks(thisFrameCbs: AnimationFrameCallbacks, frameTime: number): void {
inAnimationFrame = true;
for (const animationId in thisFrameCbs) {
if (thisFrameCbs[animationId]) {
try {
thisFrameCbs[animationId](frameTime);
} catch (err) {
const msg = err ? err.stack || err : err;
Trace.write(`Error in requestAnimationFrame: ${msg}`, Trace.categories.Error, Trace.messageType.error);
}
}
}
inAnimationFrame = false;
}
function doCurrentFrame() {
// if we're not getting accurate frame times
// set last frame time as the current time
if (!fpsCallback || !fpsCallback.running) {
lastFrameTime = getTimeInFrameBase();
}
currentFrameScheduled = false;
const thisFrameCbs = currentFrameAnimationCallbacks;
currentFrameAnimationCallbacks = {};
callAnimationCallbacks(thisFrameCbs, lastFrameTime);
}
function doFrame(currentTimeMillis: number) { function doFrame(currentTimeMillis: number) {
lastFrameTime = currentTimeMillis; lastFrameTime = currentTimeMillis;
shouldStop = true; shouldStop = true;
const thisFrameCbs = nextFrameAnimationCallbacks; const thisFrameCbs = nextFrameAnimationCallbacks;
nextFrameAnimationCallbacks = {}; nextFrameAnimationCallbacks = {};
inAnimationFrame = true; callAnimationCallbacks(thisFrameCbs, lastFrameTime);
for (const animationId in thisFrameCbs) {
if (thisFrameCbs[animationId]) {
thisFrameCbs[animationId](lastFrameTime);
}
}
inAnimationFrame = false;
if (shouldStop) { if (shouldStop) {
fpsCallback.stop(); // TODO: check performance without stopping to allow consistent frame times fpsCallback.stop(); // TODO: check performance without stopping to allow consistent frame times
} }
} }
export function requestAnimationFrame(cb: FrameRequestCallback): number { function ensureCurrentFrameScheduled() {
if (!inAnimationFrame) { if (!currentFrameScheduled) {
inAnimationFrame = true; currentFrameScheduled = true;
zonedCallback(cb)(getTimeInFrameBase()); // TODO: store and use lastFrameTime queueMacrotask(doCurrentFrame);
inAnimationFrame = false; }
}
return getNewId(); export function requestAnimationFrame(cb: FrameRequestCallback): number {
const animId = getNewId();
if (!inAnimationFrame) {
ensureCurrentFrameScheduled();
currentFrameAnimationCallbacks[animId] = zonedCallback(cb) as FrameRequestCallback;
return animId;
} }
ensureNative(); ensureNative();
const animId = getNewId();
nextFrameAnimationCallbacks[animId] = zonedCallback(cb) as FrameRequestCallback; nextFrameAnimationCallbacks[animId] = zonedCallback(cb) as FrameRequestCallback;
shouldStop = false; shouldStop = false;
fpsCallback.start(); fpsCallback.start();
@ -57,6 +89,7 @@ export function requestAnimationFrame(cb: FrameRequestCallback): number {
return animId; return animId;
} }
export function cancelAnimationFrame(id: number) { export function cancelAnimationFrame(id: number): void {
delete currentFrameAnimationCallbacks[id];
delete nextFrameAnimationCallbacks[id]; delete nextFrameAnimationCallbacks[id];
} }

View File

@ -110,7 +110,7 @@ export * from './trace';
export * from './ui'; export * from './ui';
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'; 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';
import { ClassInfo, getClass, getBaseClasses, getClassInfo, isBoolean, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isUndefined, toUIString, verifyCallback } from './utils/types'; import { ClassInfo, getClass, getBaseClasses, getClassInfo, isBoolean, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isUndefined, toUIString, verifyCallback } from './utils/types';
export const Utils = { export const Utils = {
@ -124,6 +124,7 @@ export const Utils = {
mainThreadify, mainThreadify,
isMainThread, isMainThread,
dispatchToMainThread, dispatchToMainThread,
queueMacrotask,
releaseNativeObject, releaseNativeObject,
getModuleName, getModuleName,

View File

@ -1,6 +1,7 @@
import { dip, px } from '../ui/core/view'; import { dip, px } from '../ui/core/view';
export * from './mainthread-helper'; export * from './mainthread-helper';
export * from './macrotask-scheduler';
export { Source } from './debug'; export { Source } from './debug';
export * from './native-helper'; export * from './native-helper';
@ -192,6 +193,12 @@ export function GC();
*/ */
export function releaseNativeObject(object: any /*java.lang.Object | NSObject*/); export function releaseNativeObject(object: any /*java.lang.Object | NSObject*/);
/**
* Queues the passed function to be ran in a macroTask
* @param task the function to execute as a macroTask
*/
export function queueMacrotask(task: () => void): void;
/** /**
* Checks if the current thread is the main thread. Directly calls the passed function * Checks if the current thread is the main thread. Directly calls the passed function
* if it is, or dispatches it to the main thread otherwise. * if it is, or dispatches it to the main thread otherwise.

View File

@ -0,0 +1,5 @@
/**
* Queues the passed function to be ran in a macroTask
* @param task the function to execute as a macroTask
*/
export function queueMacrotask(task: () => void): void;

View File

@ -0,0 +1,27 @@
import { Trace } from 'trace';
import { dispatchToMainThread } from './mainthread-helper';
let scheduled = false;
let macroTaskQueue: Array<() => void> = [];
function drainMacrotaskQueue() {
const currentQueue = macroTaskQueue;
macroTaskQueue = [];
scheduled = false;
currentQueue.forEach((task) => {
try {
task();
} catch (err) {
const msg = err ? err.stack || err : err;
Trace.write(`Error in macroTask: ${msg}`, Trace.categories.Error, Trace.messageType.error);
}
});
}
export function queueMacrotask(task: () => void): void {
macroTaskQueue.push(task);
if (!scheduled) {
scheduled = true;
dispatchToMainThread(drainMacrotaskQueue);
}
}

View File

@ -5,6 +5,7 @@ import * as layout from './layout-helper';
export { layout }; export { layout };
export * from './mainthread-helper'; export * from './mainthread-helper';
export * from './macrotask-scheduler';
export const RESOURCE_PREFIX = 'res://'; export const RESOURCE_PREFIX = 'res://';
export const FILE_PREFIX = 'file:///'; export const FILE_PREFIX = 'file:///';