UNPKG

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