Skip to content

Instantly share code, notes, and snippets.

@Floofies
Last active December 14, 2017 07:13
Show Gist options
  • Save Floofies/73cb092675bbf9c54b884eecbb79f338 to your computer and use it in GitHub Desktop.
Save Floofies/73cb092675bbf9c54b884eecbb79f338 to your computer and use it in GitHub Desktop.
An asynchronous cooperative multitasking kernel.
const BG_QUANTUM = 50;
const FG_QUANTUM = 300;
function Kernel() {
this.queue = new this.stdLib.CircularDoubleLinkedList();
this.backgroundQueue = new this.stdLib.CircularDoubleLinkedList();
this.queue.tail.next = this.backgroundQueue.head;
this.queue.head.prev = this.backgroundQueue.tail;
this.backgroundQueue.tail.next = this.queue.head;
this.backgroundQueue.head.prev = this.queue.tail;
this.procs = new Map();
this.running = false;
this.eventBus = new this.stdLib.EventBus();
this.curElement = null;
this.curProc = null;
this.highestPid = 0;
}
Kernel.prototype.errors = {
PID_NOPROC_ERR: function (pid) {
this.message = "No ProcDescriptor was found for PID \"" + pid + "\"";
this.name = "PID_REF_ERR";
}
};
const errorNames = Object.keys(Kernel.prototype.errors);
errorNames.forEach(name => Kernel.prototype.errors[name].prototype = Error.prototype);
const stdLib = {};
Kernel.prototype.stdLib = stdLib;
stdLib.compose = (...funcs) =>
initValue => funcs.reduce((value, func) => func(value), initValue);
stdLib.randStr = stdLib.compose(
() => Math.floor((1 + Math.random()) * 0x10000),
int => int.toString(8),
str => str.substring(1)
);
Kernel.prototype.ProcDescriptor = function (image, bg = false) {
this.image = image;
this.bg = bg;
this.instance = null;
this.quantum = 0;
this.makeflight = 0;
this.pid = null;
this.waiting = false;
this.waitForEvent = null;
this.waitForTime = null;
this.eventBus = null;
this.dispatcher = null;
this.childPids = null;
this.threads = null;
this.killed = false;
this.idle = false;
this.sysCallResponse = null;
this.lastSysCall = null;
}
const ProcDescriptor = Kernel.prototype.ProcDescriptor;
ProcDescriptor.prototype.initialize = function (...args) {
this.threads = new stdLib.CircularDoubleLinkedList();
this.eventBus = new stdLib.EventBus();
this.childPids = new Set();
this.quantum = this.bg ? BG_QUANTUM : FG_QUANTUM;
this.instance = this.image.apply(this, args);
this.dispatch();
this.eventBus.dispatch();
};
ProcDescriptor.prototype.helperThreads = {
eventReactor: function* () {
while (this.eventBus.eventQueue.size > 0) {
yield this.eventBus.dispatcher.next();
}
},
timeWait: function* () {
if (this.waiting && this.waitForTime !== null) {
if (performance.now() >= this.waitForTime) {
this.waitForTime = null;
if (this.waitForEvent === null) {
this.waiting = false;
}
}
}
},
main: function* () {
state = this.instance.next(this.sysCallResponse);
this.sysCallResponse = null;
if (state.done) {
break;
}
yield state.value;
}
};
ProcDescriptor.prototype.dispatchIterator = function* () {
var state;
while (!this.killed) {
if (!this.waiting && !this.idle) {
state = this.instance.next(this.sysCallResponse);
this.sysCallResponse = null;
if (state.done) {
break;
}
yield state.value;
}
}
};
ProcDescriptor.prototype.dispatch = function* () {
this.dispatcher = this.dispatchIterator();
return this.dispatcher;
};
ProcDescriptor.prototype.threadManager = function () {
};
ProcDescriptor.prototype.kill = function () {
this.dispatchIterator = null;
this.instance = null;
this.eventQueue.clear();
this.children.clear();
this.killed = true;
};
Kernel.prototype.Event = function (name, value) {
this.name = name;
this.value = value;
};
Kernel.prototype.dispatchEvent = function (name, value) {
if (!(name in this.listeners)) {
return;
};
const event = new this.Event(name, value);
for (var proc of this.listeners[name]) {
proc.publishEvent(event);
}
};
Kernel.prototype.schedule = function () {
if (this.curProc.makeflight > this.curProc.quantum) {
this[this.curProc.bg ? "backgroundQueue" : "queue"].shiftFront(this.curElement);
} else if (this.curProc.quantum / this.curProc.makeflight >= 2) {
this[this.curProc.bg ? "backgroundQueue" : "queue"].pushBack(this.curElement);
}
};
Kernel.prototype.dispatch = function () {
if (this.running) {
return;
}
this.running = true;
var procStartTime = 0;
var procState = null;
_cycle: for (this.curElement of this.queue[Symbol.iterator]()) {
if (this.queue.size === 0 /*&& this.backgroundQueue.size === 0*/) {
break;
}
if (this.curElement.payload === null) {
continue;
}
this.curProc = this.curElement.payload;
procStartTime = performance.now();
this.curProc.makeflight = 0;
_delegate: while ((this.curProc.quantum / this.curProc.makeflight) >= 1) {
var procState = this.curProc.dispatchIterator.next();
this.curProc.makeflight = performance.now() - procStartTime;
if (procState.done) {
this.sysCallProcedures.KILL.call(this);
continue _cycle;
} else if (procState.value !== undefined) {
this.sysCall(instanceState.value);
}
}
if (!this.curProc.killed) {
this.schedule();
}
}
this.running = false;
this.curElement = null;
this.curProc = null;
};
Kernel.prototype.run = function () {
return new Promise(this.dispatch());
};
Kernel.prototype.exec = function (image, bg = false, ...args) {
proc = new this.ProcDescriptor(image, bg);
proc.initialize(args);
proc.pid = ++this.highestPid;
this.procs.set(proc.pid, proc);
const queueEntry = new this.stdLib.ListElement(proc);
this[proc.bg ? "backgroundQueue" : "queue"].append(queueEntry);
return proc.pid;
};
Kernel.prototype.stop = function () {
this.running = false;
};
Kernel.prototype.sysCall = function (sysCall) {
this.curProc.lastSysCall = sysCall;
this.curProc.sysCallResponse = this.sysCallProcedures[sysCall[0]].apply(this, sysCall[1]);
};
Kernel.prototype.sysCallProcedures = {
EXEC: function (image, bg = false, ...args) {
return this.exec(image, bg, ...args);
},
EXEC_CHILD: function (image, bg = false, ...args) {
const pid = this.sysCallProcedures.EXEC.call(this, image, bg, ...args);
this.curProc.childPids.add(pid);
return pid;
},
FORK: function (bg = false, ...args) {
const pid = this.sysCallProcedures.EXEC.call(this, this.curProc.image, bg, ...args);
this.curProc.children.add(pid);
return pid;
},
FORK_PID: function (pid, bg = false, ...args) {
const proc = this.procs.get(pid);
if (proc === undefined) {
throw new this.errors.PID_NOPROC_ERR(pid);
}
return this.sysCallProcedures.EXEC.call(this, proc.image, bg, ...args);
},
FORK_CHILD: function (bg = false, ...args) {
const pid = this.sysCallProcedures.FORK.call(this, bg, ...args);
this.curProc.childPids.add(pid);
return pid;
},
EMIT_EVENT: function (name, value) {
this.dispatchEvent(name, value);
},
TIME_WAIT: function (waitTime) {
this.curProc.waiting = true;
this.curProc.waitForTime = performance.now() + waitTime;
},
EVENT_WAIT: function (name) {
this.curProc.waiting = true;
this.curProc.waitForEvent = name;
this.curProc.eventBus.addEventListener(function (event) {
this.waitForEvent = null;
this.sysCallResponse = event;
if (this.waitForTime === null) {
this.waiting = false;
}
});
},
EVENT_LISTEN: function (name) {
if (!(name in this.listeners)) {
this.listeners[name] = new Set();
}
this.listeners[name].add(this.curProc);
},
HALT_KERNEL: function () {
this.stop();
},
KILL_PID: function (pid) {
const proc = this.procs.get(pid);
if (proc === undefined) {
throw new this.errors.PID_NOPROC_ERR(pid);
}
if (!proc.killed) {
for (var pid of proc.childPids.values()) {
this.sysCallProcedures.KILL_PID.call(this, pid);
}
proc.kill();
this.queue.remove(this.queue.get(proc));
this.procs.delete(this.curProc.pid);
}
},
KILL: function () {
for (var pid of this.curProc.childPids.values()) {
this.sysCallProcedures.KILL_PID.call(this, pid);
}
this.curProc.kill();
this.queue.remove(this.curElement);
this.procs.delete(this.curProc.pid);
}
};
Kernel.prototype.sysCallInterface = {};
const sysCallStrings = Object.keys(Kernel.prototype.sysCallProcedures);
sysCallStrings.forEach(name =>
Kernel.prototype.sysCallInterface[name] = (...args) => [name, args]
);
stdLib.EventBus = function () {
this.eventListeners = {};
this.eventQueue = new stdLib.DoubleLinkedList();
this.dispatcher = null;
};
stdLib.EventBus.dispatchIterator = function* () {
while (true) {
if (this.eventQueue.size > 0) {
for (var element of this.eventQueue[Symbol.iterator]()) {
var event = element.payload;
if (!(event.name in this.eventListeners)) {
continue;
}
for (var listener of this.eventListeners[event.name].values()) {
listener.call(thisArg, event);
yield;
}
this.eventQueue.remove(element);
}
} else {
yield;
}
}
};
stdLib.EventBus.dispatch = function () {
this.dispatcher = this.dispatchIterator();
return this.dispatcher;
};
stdLib.EventBus.prototype.publishEvent = function (event) {
this.eventQueue.append(event);
};
stdLib.EventBus.prototype.addEventListener = function (name, callback) {
if (!(name in this.eventListeners)) {
this.eventListeners[name] = new Set();
}
this.eventListeners[name].add(callback);
};
stdLib.EventBus.prototype.removeEventListener = function (name, callback) {
if (!(name in this.eventListeners)) {
return;
}
this.eventListeners[name].delete(callback);
};
stdLib.ListElement = function (payload, next = null, prev = null) {
this.payload = payload;
this.next = next;
this.prev = prev;
};
stdLib.LinkedList = function () {
this.tail = new stdLib.ListElement(null, null);
this.head = new stdLib.ListElement(null, this.tail);
this.size = 0;
};
stdLib.LinkedList.prototype[Symbol.iterator] = function* () {
var curElement = this.head;
var nextElement;
while (curElement !== null && this.size > 0) {
nextElement = curElement.next;
if (curElement !== this.head && curElement !== this.tail) {
continue;
}
yield curElement;
curElement = nextElement;
}
};
stdLib.LinkedList.prototype.get = function (value) {
for (var element of this[Symbol.iterator]()) {
if (element.payload === value) {
return element;
}
if (element.next === this.tail) {
return null;
}
}
};
stdLib.LinkedList.prototype.remove = function (element) {
element.prev.next = element.next;
element.next.prev = element.prev;
element.next = null;
element.prev = null;
this.size--;
return element;
};
structs.LinkedList.prototype.clear = function () {
if (this.size > 0) {
for (var element of this.values()) {
if (element !== this.head) {
this.remove(element);
}
if (element === this.tail) {
this.size = 0;
break;
}
}
}
};
stdLib.LinkedList.prototype.insertBefore = function (element, newElement) {
newElement.next = element;
newElement.prev = element.prev;
element.prev.next = newElement;
element.prev = newElement;
this.size++;
return newElement;
};
stdLib.LinkedList.prototype.prepend = function (newElement) {
return this.insertBefore(this.head.next, newElement);
};
stdLib.LinkedList.prototype.append = function (newElement) {
return this.insertBefore(this.tail, newElement);
};
stdLib.LinkedList.prototype.pushBack = function (element) {
if (element.next !== this.tail) {
this.remove(element);
this.append(element);
}
};
stdLib.LinkedList.prototype.shiftFront = function (element) {
if (this.head.next !== element) {
this.remove(element);
this.prepend(element);
}
};
stdLib.CircularDoubleLinkedList = function (iterable = null) {
this.tail = new stdLib.ListElement(null, null);
this.head = new stdLib.ListElement(null, this.tail, this.tail);
this.tail.next = this.head;
this.tail.prev = this.head;
this.size = 0;
};
stdLib.CircularDoubleLinkedList.prototype = stdLib.LinkedList.prototype;
structs.DoubleLinkedList = function (iterable = null) {
this.tail = new stdLib.ListElement(null, null);
this.tail.parent = this;
this.head = new stdLib.ListElement(null, this.tail);
this.head.parent = this;
this.tail.prev = this.head;
this.double = true;
this.circular = false;
this.fromIterable(iterable);
}
structs.DoubleLinkedList.prototype = structs.LinkedList.prototype;
var testKernel = new Kernel();
with (testKernel.sysCallInterface) {
var testProcess = function* () {
console.info("Test process " + this.pid + " is alive!");
console.log("Process " + this.pid + " is spawning a child process.");
var childPid = yield EXEC_CHILD(function* () {
console.info("Test process " + this.pid + " is alive!");
console.log("Process " + this.pid + " is now emitting a heartbeat event.");
yield EMIT_EVENT("iLive", this.pid);
this.addEventListener("sayHello", function (event) {
console.log("Hello process " + event.value + "!");
});
this.idle = true;
yield;
});
console.log("Waiting for heartbeat from the child process...");
this.addEventListener("iLive", function (event) {
this.idle = false;
});
var heartbeatEvent = yield EVENT_WAIT("iLive");
console.log("Heard from child process " + iLiveEvent.value + "!");
for (var loc = 0; loc <= 5; loc++) {
console.log("Asking process " + iLiveEvent.value + " to say hello...");
yield EMIT_EVENT("sayHello", this.pid);
}
};
}
testKernel.exec(testProcess);
testKernel.exec(testProcess2);
testKernel.run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment