blob: 772043b3f8438557322d63661cc63879c876cc18 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
if (typeof __VRT_PLAYBACK_SPEED__ === 'undefined') {
window.__VRT_PLAYBACK_SPEED__ = 1;
}
const nativeRaf = window.requestAnimationFrame;
const nativeSetTimeout = window.setTimeout;
const nativeSetInterval = window.setInterval;
const FIXED_FRAME_TIME = 16;
const MAX_FRAME_TIME = 80;
const TIMELINE_START = 1566458693300;
window.__VRT_TIMELINE_PAUSED__ = true;
let realFrameStartTime = 0;
/** Control timeline loop */
let rafCbs = [];
let frameIdx = 0;
let timelineTime = 0;
function runFrame() {
realFrameStartTime = NativeDate.now();
frameIdx++;
timelineTime += FIXED_FRAME_TIME;
const currentRafCbs = rafCbs;
// Clear before calling the callbacks. raf may be registered in the callback
rafCbs = [];
currentRafCbs.forEach((cb) => {
try {
cb();
}
catch (e) {
// Catch error to avoid following tasks.
__VRT_LOG_ERRORS__(e.toString());
}
});
flushTimeoutHandlers();
flushIntervalHandlers();
}
function timelineLoop() {
if (!__VRT_TIMELINE_PAUSED__) {
runFrame();
}
nativeRaf(timelineLoop);
}
nativeRaf(timelineLoop);
window.requestAnimationFrame = function (cb) {
rafCbs.push(cb);
};
/** Mock setTimeout, setInterval */
let timeoutHandlers = [];
let intervalHandlers = [];
let timeoutId = 1;
let intervalId = 1;
window.setTimeout = function (cb, timeout) {
const elapsedFrame = Math.ceil(Math.max(timeout || 0, 1) / FIXED_FRAME_TIME);
timeoutHandlers.push({
callback: cb,
id: timeoutId,
frame: frameIdx + elapsedFrame
});
return timeoutId++;
}
window.clearTimeout = function (id) {
const idx = timeoutHandlers.findIndex(handler => {
return handler.id === id
});
if (idx >= 0) {
timeoutHandlers.splice(idx, 1);
}
}
function flushTimeoutHandlers() {
// Copy the array. In case setTimeout/clearTimeout is invoked in the callback.
const savedTimeoutHandlers = timeoutHandlers.slice();
for (let i = 0; i < savedTimeoutHandlers.length; i++) {
const handler = savedTimeoutHandlers[i];
if (handler.frame === frameIdx) {
// Need find index again. In case setTimeout/clearTimeout is invoked in the callback.
const idx = timeoutHandlers.indexOf(handler);
timeoutHandlers.splice(idx, 1);
try {
handler.callback();
}
catch (e) {
// Catch error to avoid following tasks.
__VRT_LOG_ERRORS__(e.toString());
}
}
}
}
window.setInterval = function (cb, interval) {
const intervalFrame = Math.ceil(Math.max(interval || 0, 1) / FIXED_FRAME_TIME);
intervalHandlers.push({
callback: cb,
id: intervalId,
intervalFrame,
frame: frameIdx + intervalFrame
});
return intervalId++;
}
window.clearInterval = function (id) {
const idx = intervalHandlers.findIndex(handler => {
return handler.id === id;
});
if (idx >= 0) {
intervalHandlers.splice(idx, 1);
}
}
function flushIntervalHandlers() {
// Copy the array. In case setInterval/clearInterval is invoked in the callback.
const savedIntervalHandlers = intervalHandlers.slice();
for (let i = 0; i < savedIntervalHandlers.length; i++) {
const handler = savedIntervalHandlers[i];
if (handler.frame === frameIdx) {
try {
handler.callback();
}
catch (e) {
// Catch error to avoid following tasks.
__VRT_LOG_ERRORS__(e.toString());
}
handler.frame += handler.intervalFrame;
}
}
}
/** Mock Date */
const NativeDate = window.Date;
const mockNow = function () {
// // Use same time in one frame.
// var realFrameTime = NativeDate.now();
// // Split frame. Add 8ms offset on the second half
// // Avoid infinite loop when some logic determine whether to break the loop based on the execution time.
// // For example https://github.com/apache/echarts/blob/737e23c0054e6b501ecc6f562920cffae953b5c6/src/core/echarts.ts#L537
// var frameDeltaTime = realFrameTime - realFrameStartTime;
var frameDeltaTime = 0;
// Use same time in one frame.
return TIMELINE_START + (timelineTime + frameDeltaTime) * window.__VRT_PLAYBACK_SPEED__;
};
function MockDate(...args) {
if (!args.length) {
return new NativeDate(mockNow());
}
else {
return new NativeDate(...args);
}
}
MockDate.prototype = Object.create(NativeDate.prototype);
Object.setPrototypeOf(MockDate, NativeDate);
MockDate.now = mockNow;
window.Date = MockDate;
// TODO Do we need to mock performance? Or leave some API that can keep real.
export function start() {
window.__VRT_TIMELINE_PAUSED__ = false;
}
export function pause() {
window.__VRT_TIMELINE_PAUSED__ = true;
}
export function resume() {
window.__VRT_TIMELINE_PAUSED__ = false;
}
export { nativeRaf, nativeSetInterval, nativeSetTimeout };