Last active
December 14, 2017 07:13
-
-
Save Floofies/73cb092675bbf9c54b884eecbb79f338 to your computer and use it in GitHub Desktop.
An asynchronous cooperative multitasking kernel.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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