This commit contains the first working concept of Monkeydo.
This commit is contained in:
Victor Westerlund 2021-10-06 12:38:36 +02:00
parent b6ab338e07
commit 19fa7ed3af
5 changed files with 123 additions and 43 deletions

View file

@ -1,4 +1,4 @@
import { default as MonkeyWorker } from "./worker/MonkeyWorker.mjs"; import { default as MonkeyWorker } from "./worker/TaskManager.mjs";
export default class Monkeydo extends MonkeyWorker { export default class Monkeydo extends MonkeyWorker {
constructor(manifest = false) { constructor(manifest = false) {
@ -6,22 +6,25 @@ export default class Monkeydo extends MonkeyWorker {
this.monkeydo = { this.monkeydo = {
version: "0.1", version: "0.1",
debugLevel: 0, debugLevel: 0,
// Flag if debugging is enabled, regardless of level
get debug() { get debug() {
return this.debugLevel > 0 ? true : false; return this.debugLevel > 0 ? true : false;
}, },
set debug(flag = 1) { // Set debug level. Non-verbose debugging by default
this.debugLevel = flag; set debug(level = 1) {
this.debugLevel = level;
} }
}; };
Object.seal(this.monkeydo); Object.seal(this.monkeydo);
// Monkeydo manifest parsed with load()
this.manifest = { this.manifest = {
header: null, header: null,
body: null body: null
}; };
if(!window.Worker) { if(!window.Worker) {
this.except("JavaScript Workers aren't supported by your browser"); throw new Error("JavaScript Workers aren't supported by your browser");
} }
if(manifest) { if(manifest) {
@ -29,17 +32,17 @@ export default class Monkeydo extends MonkeyWorker {
} }
} }
debug(attachment = "ATTACHMENT_EMPTY") { debug(attachment = "DEBUG_EMPTY") {
if(this.monkeydo.debug) { if(this.monkeydo.debug) {
console.warn("-- Monkeydo debug -->",attachment); console.warn("-- Monkeydo debug -->",attachment);
return; return;
} }
} }
// Load a Monkeydo manifest from JSON via string or URL
async load(manifest) { async load(manifest) {
const errorPrefix = "MANIFEST_IMPORT_FAILED: "; const errorPrefix = "MANIFEST_IMPORT_FAILED: ";
let data; let data;
// Monkeydo can only load a JSON string or URL to a JSON file
if(typeof manifest !== "string") { if(typeof manifest !== "string") {
this.debug(manifest); this.debug(manifest);
throw new TypeError(errorPrefix + "Expected JSON or URL"); throw new TypeError(errorPrefix + "Expected JSON or URL");
@ -70,6 +73,7 @@ export default class Monkeydo extends MonkeyWorker {
} }
} }
// Make sure the parsed JSON is a valid Monkeydo manifest
if(!data.hasOwnProperty("header") || !data.hasOwnProperty("body")) { if(!data.hasOwnProperty("header") || !data.hasOwnProperty("body")) {
this.debug(data); this.debug(data);
throw new Error(errorPrefix + "Expected 'header' and 'body' properties in object"); throw new Error(errorPrefix + "Expected 'header' and 'body' properties in object");
@ -80,12 +84,21 @@ export default class Monkeydo extends MonkeyWorker {
return true; return true;
} }
do() { // Execute tasks from Monkeydo manifest
async do() {
const errorPrefix = "DO_FAILED: "; const errorPrefix = "DO_FAILED: ";
// Abort if the manifest object doesn't contain any header data
if(!this.manifest.header) { if(!this.manifest.header) {
this.debug(this.manifest.header); this.debug(this.manifest.header);
throw new Error(errorPrefix + `Expected header object from contructed property`); throw new Error(errorPrefix + `Expected header object from contructed property`);
} }
this.giveManifest();
// Hand over the loaded manifest to the MonkeyWorker task manager
const monkey = this.giveManifest();
monkey.then(() => this.play())
.catch(error => {
this.debug(error);
throw new Error(errorPrefix + "Failed to post manifest to worker thread");
});
} }
} }

64
worker/Monkey.js Normal file
View file

@ -0,0 +1,64 @@
// Task scheduler and iterator of Monkeydo manifests
class Monkey {
constructor(manifest) {
this.data = manifest.body;
this.dataLength = this.data.length - 1;
this.i = 0;
this.queue = {
task: null,
next: null
}
Object.seal(this.queue);
}
run(data) {
this.i++;
postMessage(data);
}
queueNext() {
const data = this.data[this.i];
this.queue.task = setTimeout(() => this.run(data.do),data.wait);
// Schedule next task if it's not the last
if(this.i >= this.dataLength) {
this.i = 0;
return false;
}
this.queue.next = setTimeout(() => this.queueNext(),data.wait);
}
interrupt() {
clearTimeout(this.queue.task);
clearTimeout(this.queue.next);
this.queue.task = null;
this.queue.next = null;
}
}
// Global event handler for this worker
onmessage = (message) => {
const type = message.data[0] ? message.data[0] : null;
const data = message.data[1];
switch(type) {
case "GIVE_MANIFEST":
try {
this.monkey = new Monkey(data);
postMessage("OK");
}
catch(error) {
postMessage(["MANIFEST_ERROR",error]);
}
break;
case "PLAYING":
this.monkey.queueNext();
break;
default: return; // No op
}
}

View file

@ -1,30 +0,0 @@
// Spawn a dedicated worker for scheduling events from manifest
export default class MonkeyWorker {
constructor() {
// Get location of this file
this.ready = false;
let location = new URL(import.meta.url);
location = location.pathname.replace("MonkeyWorker.mjs","");
// Spawn worker from file relative to this file
this.worker = new Worker(location + "Sequencer.js");
this.worker.addEventListener("message",message => this.message(message));
}
play() {
this.worker.postMessage(["PLAYSTATE",true]);
}
pause() {
this.worker.postMessage(["PLAYSTATE",false]);
}
giveManifest() {
this.worker.postMessage(["GIVE_MANIFEST",this.manifest]);
}
message(message) {
console.log(message);
}
}

View file

@ -1,5 +0,0 @@
postMessage("MONKEDO_THREAD_SPAWNED");
onmessage = (message) => {
console.log("Message received",message);
}

38
worker/TaskManager.mjs Normal file
View file

@ -0,0 +1,38 @@
// Task manager for Monkeydo dedicated workers
export default class TaskManager {
constructor() {
// Get path of this file
this.ready = false;
let location = new URL(import.meta.url);
location = location.pathname.replace("TaskManager.mjs",""); // Get parent directory
// Spawn a dedicated worker for scheduling events from manifest
this.worker = new Worker(location + "Monkey.js");
}
play() {
this.worker.postMessage(["PLAYING",true]);
this.worker.addEventListener("message",message => eval(message.data));
}
pause() {
this.worker.postMessage(["PLAYING",false]);
}
// Pass manifest to worker and await response
async giveManifest() {
this.worker.postMessage(["GIVE_MANIFEST",this.manifest]);
// Wait for the worker to install the manifest
const ack = await new Promise((resolve,reject) => {
this.worker.addEventListener("message",message => {
if(message.data !== "OK") {
reject(message.data);
}
resolve();
});
});
return ack;
}
}