hertzscript-dispatcher
Version:
Executes preemptible JavaScript coroutines which conform to the HertzScript specification.
219 lines • 8.6 kB
JavaScript
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;