diff --git a/Monkeydo.mjs b/Monkeydo.mjs index 4975232..34ea401 100644 --- a/Monkeydo.mjs +++ b/Monkeydo.mjs @@ -10,16 +10,32 @@ export default class Monkeydo extends MonkeyMaster { Object.assign(this.methods,methods); } + // Execute a task do(task) { - console.log("TASK",task); + if(!task[1] in this.methods) return; + const args = task.splice(0,2); + this.methods[task[1]](...args); } - async loop(times = null) { - times = times < 0 ? null : times; - return await this.setFlag("loop",times); + async debug(state = true) { + return await this.setFlag("debug",state); } + // Loop playback X times or negative number for infinite + async loop(times = 255) { + if(typeof times !== "number") { + times = parseInt(times); + } + // Clamp number to 8 bit max + times = Math.min(Math.max(times,0),255); + return await this.setFlag("playing",times); + } + + // Load Monkeydo manifest async load(manifest) { - + if(typeof manifest === "object") { + manifest = JSON.stringify(manifest); + } + return await this.loadManifest(manifest); } } \ No newline at end of file diff --git a/monkey/Monkey.js b/monkey/Monkey.js index f7efb0e..04389de 100644 --- a/monkey/Monkey.js +++ b/monkey/Monkey.js @@ -4,10 +4,44 @@ importScripts("https://unpkg.com/comlink/dist/umd/comlink.js"); class Monkey { constructor() { - this.manifest = {}; - - // Runtime flags this.flags = new Uint8ClampedArray(2); + this.tasks = []; + // Runtime task queue + this.queue = { + thisTask: null, + nextTask: null + } + } + + // Task scheduler + next() { + if(this.flags[0] === 0) return; + const task = this.tasks[this.i]; + + // Run task after delay + this.queue.thisTask = setTimeout(() => { + // Dispatch task to main thread + this.postMessage(["TASK",task]); + this.i++; + },task[0]); + + // Loop until flag is 0 or infinite if 255 + if(this.i === this.tasks.length) { + this.i = 0; + if(flags[1] === 255) return; + flags[1]--; + } + + // Queue the next task + this.queue.nextTask = setTimeout(() => this.next(),task[0]); + } + + abort() { + clearTimeout(this.queue.thisTask); + clearTimeout(this.queue.nextTask); + this.queue.thisTask = null; + this.queue.nextTask = null; + this.flags[1] = 0; // Playing: false } // Set or get a runtime flag @@ -15,15 +49,28 @@ class Monkey { return value ? this.flags[index] = value : this.flags[index]; } + // Fetch and install manifest from URL + async fetchManifest(url) { + const manifest = await fetch(url); + const json = await manifest.json(); + return await this.loadManifest(json); + } + + // Install a Monkeydo manifest async loadManifest(manifest) { - try { - const data = JSON.parse(manifest); - this.manifest = data; - this.flags[0] = 1; - } - catch { - const url = new URL(manifest); - } + return await new Promise((resolve,reject) => { + if(typeof manifest !== "object") { + try { + manifest = JSON.parse(manifest); + } + catch { + reject("Failed to load manifest"); + } + } + this.tasks = manifest.tasks; + this.flags[0] = 1; // Manifest loaded: true + resolve(); + }); } } diff --git a/monkey/MonkeyMaster.mjs b/monkey/MonkeyMaster.mjs index e75dfcc..e086f12 100644 --- a/monkey/MonkeyMaster.mjs +++ b/monkey/MonkeyMaster.mjs @@ -7,8 +7,21 @@ export default class MonkeyMaster { this.comlink = null; this.ready = false; - this.flagQueue = []; - this.init(); + // Tasks will be queued here on runtime if the worker isn't ready + this.queue = { + _flags: [], + set flag(flag) { + this._flags.push(flag); + }, + // Attempt to send all queued flags + sendAllFlags: () => { + // Copy flags and clear queue + const flags = [...this.queue._flags]; + this.queue._flags = []; + + flags.forEach(flag => this.setFlag(flag)); + } + }; } // Import worker relative to this module @@ -16,6 +29,7 @@ export default class MonkeyMaster { const name = "Monkey.js"; const url = new URL(import.meta.url); + // Replace pathname of this file with worker const path = url.pathname.split("/"); path[path.length - 1] = name; @@ -27,24 +41,31 @@ export default class MonkeyMaster { // Spawn and wrap dedicated worker with Comlink const worker = new Worker(this.getWorkerPath()); const Monkey = Comlink.wrap(worker); - this.comlink = await new Monkey(); + + // Wait for comlink to initialize proxy and send queued flags + return await new Promise((resolve,reject) => { + if(!this.comlink) reject("Failed to open proxy to worker"); + + this.ready = true; + this.queue.sendAllFlags(); + resolve(); + }); } // Return a flag array index by name flagStringToIndex(flag) { const flags = [ "MANIFEST_LOADED", - "PLAYING", - "LOOP" + "PLAYING" ]; + // Translate string to index if(typeof flag === "string" || flag < 0) { const key = flags.indexOf(flag.toUpperCase()); - if(key < 0) { - return false; - } + if(key < 0) return; } + // Check key is in bounds if(flag < 0 || flags > flags.length - 1) { throw new Error(`Array key '${flag}' out of range`); @@ -57,11 +78,30 @@ export default class MonkeyMaster { return await this.comlink.flag(key); } + // Set or queue worker runtime flag async setFlag(flag,value) { const key = this.flagStringToIndex(flag); - const update = await this.comlink.flag(0,12); + if(!this.ready) { + this.queue.flag = [key,value]; + return; + } + + // Tell worker to update flag by key + const update = await this.comlink.flag(key,value); if(!update) { - this.flagQueue.push([key,value]); + this.queue.flag = [key,value]; + } + return update; + } + + async loadManifest(manifest) { + if(!this.ready) await this.init(); + try { + const url = new URL(manifest); + this.comlink.fetchManifest(url.toString()); + } + catch { + this.comlink.loadManifest(manifest); } return true; }