Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 47 additions & 14 deletions packages/core/animation-frame/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { queueMacrotask } from '../utils/macrotask-scheduler';
import { FPSCallback } from '../fps-meter/fps-native';
import { getTimeInFrameBase } from './animation-native';
import { Trace } from 'trace';

export interface FrameRequestCallback {
(time: number): void;
}

type AnimationFrameCallbacks = { [key: string]: FrameRequestCallback };

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 inAnimationFrame = false;
let fpsCallback: FPSCallback;
Expand All @@ -23,40 +29,67 @@ function ensureNative() {
fpsCallback = new FPSCallback(doFrame);
}

function doFrame(currentTimeMillis: number) {
lastFrameTime = currentTimeMillis;
shouldStop = true;
const thisFrameCbs = nextFrameAnimationCallbacks;
nextFrameAnimationCallbacks = {};
function callAnimationCallbacks(thisFrameCbs: AnimationFrameCallbacks, frameTime: number): void {
inAnimationFrame = true;
for (const animationId in thisFrameCbs) {
if (thisFrameCbs[animationId]) {
thisFrameCbs[animationId](lastFrameTime);
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) {
lastFrameTime = currentTimeMillis;
shouldStop = true;
const thisFrameCbs = nextFrameAnimationCallbacks;
nextFrameAnimationCallbacks = {};
callAnimationCallbacks(thisFrameCbs, lastFrameTime);
if (shouldStop) {
fpsCallback.stop(); // TODO: check performance without stopping to allow consistent frame times
}
}

function ensureCurrentFrameScheduled() {
if (!currentFrameScheduled) {
currentFrameScheduled = true;
queueMacrotask(doCurrentFrame);
}
}

export function requestAnimationFrame(cb: FrameRequestCallback): number {
const animId = getNewId();
if (!inAnimationFrame) {
inAnimationFrame = true;
zonedCallback(cb)(getTimeInFrameBase()); // TODO: store and use lastFrameTime
inAnimationFrame = false;

return getNewId();
ensureCurrentFrameScheduled();
currentFrameAnimationCallbacks[animId] = zonedCallback(cb) as FrameRequestCallback;
return animId;
}
ensureNative();
const animId = getNewId();
nextFrameAnimationCallbacks[animId] = zonedCallback(cb) as FrameRequestCallback;
shouldStop = false;
fpsCallback.start();

return animId;
}

export function cancelAnimationFrame(id: number) {
export function cancelAnimationFrame(id: number): void {
delete currentFrameAnimationCallbacks[id];
delete nextFrameAnimationCallbacks[id];
}
3 changes: 2 additions & 1 deletion packages/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export * from './trace';

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';

export const Utils = {
Expand All @@ -124,6 +124,7 @@ export const Utils = {
mainThreadify,
isMainThread,
dispatchToMainThread,
queueMacrotask,
releaseNativeObject,

getModuleName,
Expand Down
7 changes: 7 additions & 0 deletions packages/core/utils/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { dip, px } from '../ui/core/view';

export * from './mainthread-helper';
export * from './macrotask-scheduler';
export { Source } from './debug';

export * from './native-helper';
Expand Down Expand Up @@ -192,6 +193,12 @@ export function GC();
*/
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
* if it is, or dispatches it to the main thread otherwise.
Expand Down
5 changes: 5 additions & 0 deletions packages/core/utils/macrotask-scheduler.d.ts
Original file line number Diff line number Diff line change
@@ -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;
27 changes: 27 additions & 0 deletions packages/core/utils/macrotask-scheduler.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
1 change: 1 addition & 0 deletions packages/core/utils/utils-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as layout from './layout-helper';

export { layout };
export * from './mainthread-helper';
export * from './macrotask-scheduler';

export const RESOURCE_PREFIX = 'res://';
export const FILE_PREFIX = 'file:///';
Expand Down