@dark-engine/core
Version:
The lightweight and powerful UI rendering engine without dependencies and written in TypeScript (Browser, Node.js, Android, iOS, Windows, Linux, macOS)
303 lines (302 loc) • 7.76 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.scheduler = void 0;
const utils_1 = require('../utils');
const constants_1 = require('../constants');
const workloop_1 = require('../workloop');
const emitter_1 = require('../emitter');
const platform_1 = require('../platform');
class MessageChannel extends emitter_1.EventEmitter {
port1 = null;
port2 = null;
constructor() {
super();
this.port1 = new MessagePort(this);
this.port2 = new MessagePort(this);
}
}
class MessagePort {
channel;
offs = [];
constructor(channel) {
this.channel = channel;
}
on(event, callback) {
const off = this.channel.on(event, callback);
this.offs.push(off);
}
postMessage(value) {
platform_1.platform.spawn(() => {
this.channel.emit('message', value);
});
}
unref() {
this.offs.forEach(x => x());
this.offs = [];
}
}
class Scheduler {
queue = {
[constants_1.TaskPriority.HIGH]: [],
[constants_1.TaskPriority.NORMAL]: [],
[constants_1.TaskPriority.LOW]: [],
};
deadline = 0;
lastId = 0;
task = null;
scheduledCallback = null;
isMessageLoopRunning = false;
channel = null;
port = null;
constructor() {
this.channel = new MessageChannel();
this.port = this.channel.port2;
this.channel.port1.on('message', this.performWorkUntilDeadline.bind(this));
}
reset() {
this.deadline = 0;
this.task = null;
this.scheduledCallback = null;
this.isMessageLoopRunning = false;
}
shouldYield() {
return (0, utils_1.getTime)() >= this.deadline;
}
schedule(callback, options) {
const task = createTask(callback, options);
this.lastId = task.getId();
this.put(task);
this.execute();
}
getLastId() {
return this.lastId;
}
detectIsTransition() {
return this.task.getIsTransition();
}
hasNewTask() {
const { high, normal, low } = this.getQueues();
return high.length + normal.length + low.length > 0;
}
retain(fn) {
const { high, normal, low } = this.getQueues();
const tasks = [...high, ...normal, ...low];
const { hasHostUpdate, hasChildUpdate } = collectFlags(this.task, tasks);
if (hasHostUpdate || hasChildUpdate) {
const hasExact = detectHasExact(this.task, tasks);
if (hasExact) {
this.complete(this.task, true);
} else {
this.defer(this.task);
}
this.task.markAsObsolete();
} else {
this.task.setOnRestore(fn);
this.defer(this.task);
}
}
complete(task, isCanceled) {
task.complete(isCanceled);
}
put(task) {
const queue = this.queue[task.getPriority()];
if (task.getIsTransition()) {
const base = task.base();
const tasks = queue.filter(x => x.base() !== base);
queue.splice(0, queue.length, ...tasks);
}
queue.push(task);
}
pick(queue) {
if (queue.length === 0) return false;
this.task = queue.shift();
this.run(this.task);
return true;
}
run(task) {
try {
task.run();
task.getForceAsync() ? this.requestCallbackAsync(workloop_1.workLoop) : this.requestCallback(workloop_1.workLoop);
} catch (something) {
if ((0, utils_1.detectIsPromise)(something)) {
something.catch(utils_1.dummyFn).finally(() => {
this.run(task);
});
} else {
throw something;
}
}
}
execute() {
const isBusy = (0, workloop_1.detectIsBusy)();
if (!isBusy && !this.isMessageLoopRunning) {
const { high, normal, low } = this.getQueues();
this.pick(high) || this.pick(normal) || this.pick(low);
}
}
requestCallbackAsync(callback) {
this.scheduledCallback = callback;
if (!this.isMessageLoopRunning) {
this.isMessageLoopRunning = true;
this.port.postMessage(null);
}
}
requestCallback(callback) {
const something = callback(false);
if ((0, utils_1.detectIsPromise)(something)) {
something.catch(utils_1.dummyFn).finally(() => {
this.requestCallback(callback);
});
} else {
this.task = null;
this.execute();
}
}
performWorkUntilDeadline() {
if (this.scheduledCallback) {
this.deadline = (0, utils_1.getTime)() + constants_1.YIELD_INTERVAL;
const something = this.scheduledCallback(true);
if ((0, utils_1.detectIsPromise)(something)) {
something.catch(utils_1.dummyFn).finally(() => {
this.port.postMessage(null);
});
} else if (something) {
this.port.postMessage(null);
} else {
this.complete(this.task, false);
this.reset();
this.execute();
}
} else {
this.isMessageLoopRunning = false;
}
}
defer(task) {
const { low } = this.getQueues();
low.unshift(task);
}
getQueues() {
const high = this.queue[constants_1.TaskPriority.HIGH];
const normal = this.queue[constants_1.TaskPriority.NORMAL];
const low = this.queue[constants_1.TaskPriority.LOW];
return {
high,
normal,
low,
};
}
}
class Task {
__id;
priority;
forceAsync = false;
isTransition = false;
isObsolete = false;
callback = null;
createLoc = null;
onRestore = null;
onTransitionEnd = null;
static nextTaskId = 0;
constructor(callback, priority, forceAsync) {
this.__id = ++Task.nextTaskId;
this.callback = callback;
this.priority = priority;
this.forceAsync = forceAsync;
}
getId() {
return this.__id;
}
getPriority() {
return this.priority;
}
getForceAsync() {
return this.forceAsync;
}
setIsTransition(x) {
this.isTransition = x;
}
getIsTransition() {
return this.isTransition;
}
run() {
this.isObsolete = false;
this.callback(this.onRestore);
this.onRestore = null;
}
complete(isCanceled) {
this.isTransition &&
!this.isObsolete &&
(0, utils_1.detectIsFunction)(this.onTransitionEnd) &&
this.onTransitionEnd(loc => (isCanceled ? this.createBase(loc) === this.base() : false));
}
markAsObsolete() {
this.isObsolete = true;
}
getIsObsolete() {
return this.isObsolete;
}
setOnRestore(fn) {
this.onRestore = fn;
}
setCreateLoc(fn) {
this.createLoc = fn;
}
createBase(loc) {
const [base] = loc.split(constants_1.HOOK_DELIMETER);
return base;
}
base() {
return this.createBase(this.loc());
}
loc() {
return this.createLoc();
}
setOnTransitionEnd(fn) {
this.onTransitionEnd = fn;
}
}
function collectFlags(task, tasks) {
const base = task.base();
let hasTopUpdate = false;
let hasHostUpdate = false;
let hasChildUpdate = false;
for (let i = 0; i < tasks.length; i++) {
const task = tasks[i];
const $base = task.base();
if ($base.length < base.length && base.indexOf($base) === 0) {
hasTopUpdate = true;
} else if ($base === base) {
hasHostUpdate = true;
} else if ($base.length > base.length && $base.indexOf(base) === 0) {
hasChildUpdate = true;
}
}
return {
hasTopUpdate,
hasHostUpdate,
hasChildUpdate,
};
}
function detectHasExact(task, tasks) {
const $loc = task.loc();
const hasExact = tasks.some(x => x.loc() === $loc);
return hasExact;
}
function createTask(callback, options) {
const {
priority = constants_1.TaskPriority.NORMAL,
forceAsync = false,
isTransition = false,
loc,
onTransitionEnd,
} = options;
const task = new Task(callback, priority, forceAsync);
task.setIsTransition(isTransition);
task.setOnTransitionEnd(onTransitionEnd);
task.setCreateLoc(loc || rootLoc);
return task;
}
const rootLoc = () => '>';
const scheduler = new Scheduler();
exports.scheduler = scheduler;
//# sourceMappingURL=scheduler.js.map