UNPKG

hertzscript-dispatcher

Version:

Executes preemptible JavaScript coroutines which conform to the HertzScript specification.

219 lines 8.6 kB
const performance = require('perf_hooks').performance; const Executor = require("./lib/Executor.js"); const HzFunctor = require("./lib/HzFunctor.js"); const TokenLib = require("./lib/TokenLib.js"); const DetourLib = require("./lib/DetourLib.js"); const UserLib = require("./lib/UserLib.js"); const RunQueue = require("./lib/RunQueue.js"); function Dispatcher(tokenLib = null, cQuantum = 300, tQuantum = 0) { // Instruction Token Library if (tokenLib !== null) this.tokenLib = tokenLib; else this.tokenLib = new TokenLib(); // Instruction token executor this.executor = new Executor(); // Functor detour library this.detourLib = new DetourLib(Dispatcher, this.tokenLib); // Userland Library this.userLib = new UserLib(this.tokenLib, this.detourLib); // Default cycle time-slice length this.cQuantum = cQuantum; // Set to "true" when the Dispatcher is running this.running = false; // The ControlBlock queue this.queue = new RunQueue(this.tokenLib, tQuantum); this.lastError = this.tokenLib.symbols.nullSym; this.lastReturn = this.tokenLib.symbols.nullSym; this.metrics = { // Last Cycle time makeflight: 0, // Total running time makespan: 0 }; } Dispatcher.lib = { Executor, RunQueue, HzFunctor, TokenLib, DetourLib, UserLib }; // Set the time slice quantum of the RunQueue Dispatcher.prototype.threadQuantum = function (tQuantum) { this.queue.quantum = tQuantum; }; // Set the time slice quantum of the Dispatcher cycle Dispatcher.prototype.cycleQuantum = function (cQuantum) { this.cQuantum = cQuantum; }; // Add a hzFunctor to the active ControlBlock, or create a new ControlBlock for it Dispatcher.prototype.enqueue = function (functor, thisArg = null, args = null, isTailCall = false) { if ((typeof functor) !== "function") throw new TypeError("Given value is not a function!"); this.queue.enqueue(new HzFunctor(this.tokenLib.symbols, functor, thisArg, args, isTailCall)); }; // Add a hzFunctor to a new ControlBlock Dispatcher.prototype.spawn = function (functor, thisArg = null, args = null) { if ((typeof functor) !== "function") throw new TypeError("Given value is not a function!"); this.queue.spawn(new HzFunctor(this.tokenLib.symbols, functor, thisArg, args)); }; // Imports an HzModule Dispatcher.prototype.import = function (hzModule) { this.spawn(hzModule(this.userLib)); }; // Imports an HzFunctor or HzModule and executes it in run-to-completion mode Dispatcher.prototype.exec = function (functor, thisArg = null, args = null, throwUp = false) { if (this.tokenLib.symbols.tokenSym in functor) this.spawn(functor, thisArgs, args); else this.import(functor); return this.runComplete(throwUp); }; // Pop and terminate a Functor from the stack Dispatcher.prototype.killLast = function () { this.queue.killLast(); }; // Pop and terminate all Functors in the stack Dispatcher.prototype.killAll = function () { this.queue.killAll(); }; // Processes a kernelized instruction Dispatcher.prototype.processToken = function (executor, tokenLib, queue, block, token, wasTailCall) { if (token.type in executor) return executor[token.type](tokenLib, queue, block, token, wasTailCall); else return executor.illegalToken(); }; Dispatcher.processToken = Dispatcher.prototype.processToken; // Prepare the yielded state of an hzFunctor for processState Dispatcher.prototype.coerceState = function (tokenLib, detourLib, hzFunctor, state) { if (hzFunctor.type === "iterator") hzFunctor.args = []; if (hzFunctor.type === "generator") { // Detour an iterator state = detourLib.hookIterator(state); } else if (hzFunctor.type === "unknown") { // State is not an Hztoken, so wrap it in one if ((typeof state) === "undefined") state = tokenLib.tokens.return; else state = tokenLib.tokens.returnValue.set([state]); } else { if (!(tokenLib.symbols.tokenSym in hzFunctor.image)) { // State is not an HzToken, so wrap it in one state = tokenLib.tokens.returnValue.set([state]); } else if (!tokenLib.isKernelized(state.value)) { // State is not an HzToken, so wrap it in one if ((typeof state.value) === "undefined") state = tokenLib.tokens.return; else state = tokenLib.tokens.returnValue.set([state.value]); } else if (!tokenLib.isKernelized(state)) { // State.value is an HzToken, so unwrap it state = state.value; } } if (hzFunctor.type === "constructor" && ( state === tokenLib.tokens.return || (state === tokenLib.tokens.returnValue && (typeof state.arg) === "undefined") )) { // State is from a constructor, so wrap it in an HzToken state = tokenLib.tokens.returnValue.set([hzFunctor.thisArg]); } // Return the HzToken return state; }; Dispatcher.coerceState = Dispatcher.prototype.coerceState; Dispatcher.prototype.dispatch = function (executor, tokenLib, detourLib, queue, block, throwUp) { const hzFunctor = block.getCurrent(); try { // Advances execution of the hzFunctor and saves the yielded state if (block.lastError !== tokenLib.symbols.nullSym) { // Uncaught error was seen before, so throw it into the hzFunctor var state = hzFunctor.throwIntoFunctor(block.lastError); block.lastError = tokenLib.symbols.nullSym; } else { // A value was returned or yielded before, so invoke the hzFunctor with it var state = block.lastReturn !== tokenLib.symbols.nullSym ? hzFunctor.callFunctor(block.lastReturn) : hzFunctor.callFunctor(); block.lastReturn = tokenLib.symbols.nullSym; } // Prepare the yielded state for processing. // Will wrap naked values in an HzToken. state = Dispatcher.coerceState(tokenLib, detourLib, hzFunctor, state); if (state === tokenLib.tokens.remit || state === tokenLib.tokens.remitValue) { return state; } // Process the HzToken Dispatcher.processToken(executor, tokenLib, queue, block, state, hzFunctor.isTailCall); } catch (error) { // Uncaught error, so terminate the hzFunctor console.error(error); queue.killLast(); block.lastError = error; if (block.stack.length === 0 && throwUp) if (throwUp) throw error; } // Update ControlBlock metrics block.metrics.makeflight = this.metrics.makeflight; block.metrics.makespan += block.metrics.makeflight; }; Dispatcher.dispatch = Dispatcher.prototype.dispatch; // Runs a single execution cycle Dispatcher.prototype.cycle = function (cQuantum = null, throwUp = false) { const complete = cQuantum === false; if (cQuantum === null) cQuantum = this.cQuantum; const cEnd = performance.now() + cQuantum; cycle: while (complete || performance.now() < cEnd) { const cStart = performance.now(); if (!this.running || this.queue.blocks.length === 0) { // Dispatcher is not running or there are no ControlBlocks this.stop(); return this.lastReturn; } // Get the next runnable ControlBlock const block = this.queue.getNext(); if (block === null) { // No runnable ControlBlocks this.stop(); return this.lastReturn; } try { // Advance execution of the last Functor in the virtual stack var state = Dispatcher.dispatch(this.executor, this.tokenLib, this.detourLib, this.queue, block, throwUp); this.lastReturn = block.lastReturn; } catch (error) { // Uncaught error this.lastError = error; if (throwUp) { this.stop(); throw error; } } // Update dispactcher metrics this.metrics.makeflight = performance.now() - cStart; this.metrics.makespan += this.metrics.makeflight; if (state === this.tokenLib.tokens.remit || state === this.tokenLib.tokens.remitValue) { // Remit HzToken seen, so stop the cycle and return it this.stop(); return state; } } return this.lastReturn; }; // Synchronous mode, runs for the duration of the quantum in milliseconds Dispatcher.prototype.runSync = function (cQuantum = null, throwUp = false) { this.running = true; return this.cycle(cQuantum, throwUp); }; // Run-to-completion mode, runs runSync continuously until all programs have exited Dispatcher.prototype.runComplete = function (throwUp = false) { return this.runSync(false, throwUp); }; // Asynchronous mode, runs runSync on an interval in the asynchrnous event loop Dispatcher.prototype.runAsync = function (interval = 30, cQuantum = null, throwUp = false) { if (this.running) return; return new Promise((resolve) => { const asyncRunner = () => { const state = this.runSync(cQuantum, throwUp); if (!this.running) return resolve(state); setTimeout(asyncRunner, interval); }; setTimeout(asyncRunner, interval); }); }; // Stops execution Dispatcher.prototype.stop = function () { this.queue.blockIndex = 0; this.queue.activeBlock = null; this.running = false; }; module.exports = Dispatcher;