diff --git a/Monkeydo.mjs b/Monkeydo.mjs index 7790905..c0f5002 100644 --- a/Monkeydo.mjs +++ b/Monkeydo.mjs @@ -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 { constructor(manifest = false) { @@ -6,22 +6,25 @@ export default class Monkeydo extends MonkeyWorker { this.monkeydo = { version: "0.1", debugLevel: 0, + // Flag if debugging is enabled, regardless of level get debug() { return this.debugLevel > 0 ? true : false; }, - set debug(flag = 1) { - this.debugLevel = flag; + // Set debug level. Non-verbose debugging by default + set debug(level = 1) { + this.debugLevel = level; } }; Object.seal(this.monkeydo); + // Monkeydo manifest parsed with load() this.manifest = { header: null, body: null }; 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) { @@ -29,17 +32,17 @@ export default class Monkeydo extends MonkeyWorker { } } - debug(attachment = "ATTACHMENT_EMPTY") { + debug(attachment = "DEBUG_EMPTY") { if(this.monkeydo.debug) { console.warn("-- Monkeydo debug -->",attachment); return; } } + // Load a Monkeydo manifest from JSON via string or URL async load(manifest) { const errorPrefix = "MANIFEST_IMPORT_FAILED: "; let data; - // Monkeydo can only load a JSON string or URL to a JSON file if(typeof manifest !== "string") { this.debug(manifest); 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")) { this.debug(data); throw new Error(errorPrefix + "Expected 'header' and 'body' properties in object"); @@ -80,12 +84,21 @@ export default class Monkeydo extends MonkeyWorker { return true; } - do() { + // Execute tasks from Monkeydo manifest + async do() { const errorPrefix = "DO_FAILED: "; + // Abort if the manifest object doesn't contain any header data if(!this.manifest.header) { this.debug(this.manifest.header); 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"); + }); } } \ No newline at end of file diff --git a/worker/Monkey.js b/worker/Monkey.js new file mode 100644 index 0000000..eb0a2ff --- /dev/null +++ b/worker/Monkey.js @@ -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 + } +} \ No newline at end of file diff --git a/worker/MonkeyWorker.mjs b/worker/MonkeyWorker.mjs deleted file mode 100644 index d2560ce..0000000 --- a/worker/MonkeyWorker.mjs +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/worker/Sequencer.js b/worker/Sequencer.js deleted file mode 100644 index 4cab0b9..0000000 --- a/worker/Sequencer.js +++ /dev/null @@ -1,5 +0,0 @@ -postMessage("MONKEDO_THREAD_SPAWNED"); - -onmessage = (message) => { - console.log("Message received",message); -} \ No newline at end of file diff --git a/worker/TaskManager.mjs b/worker/TaskManager.mjs new file mode 100644 index 0000000..c126685 --- /dev/null +++ b/worker/TaskManager.mjs @@ -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; + } +} \ No newline at end of file