diff --git a/Monkeydo.mjs b/Monkeydo.mjs index c0f5002..e67236b 100644 --- a/Monkeydo.mjs +++ b/Monkeydo.mjs @@ -1,16 +1,16 @@ -import { default as MonkeyWorker } from "./worker/TaskManager.mjs"; +import { default as MonkeyWorker } from "./worker/MonkeyManager.mjs"; export default class Monkeydo extends MonkeyWorker { - constructor(manifest = false) { - super(); + constructor(methods = {},manifest = false) { + super(methods); this.monkeydo = { - version: "0.1", + version: "0.2.1", debugLevel: 0, // Flag if debugging is enabled, regardless of level get debug() { return this.debugLevel > 0 ? true : false; }, - // Set debug level. Non-verbose debugging by default + // Set debug level. Non-verbose debugging if called without an argument set debug(level = 1) { this.debugLevel = level; } @@ -32,13 +32,23 @@ export default class Monkeydo extends MonkeyWorker { } } - debug(attachment = "DEBUG_EMPTY") { + debug(...attachments) { if(this.monkeydo.debug) { - console.warn("-- Monkeydo debug -->",attachment); + console.warn("-- Monkeydo debug -->",attachments); return; } } + // Loop playback; -1 or false = infinite + loop(times = -1) { + // Typecast boolean to left shifted integer; + if(typeof times === "boolean") { + times = times ? -1 : 0; + } + times = times < 0 ? -1 : times; + this.setFlag("loop",times); + } + // Load a Monkeydo manifest from JSON via string or URL async load(manifest) { const errorPrefix = "MANIFEST_IMPORT_FAILED: "; @@ -95,10 +105,6 @@ export default class Monkeydo extends MonkeyWorker { // 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"); - }); + this.play(); } } \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..791873a --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +
+
+
Monkeydo uses the portable data format JSON to read tasks, making it easy to read by primates and machines alike.
+
+
+{
+ "tasks": [
+ [0,"myJavaSriptMethod","someArgument","anotherArgument"]
+ ]
+}
+
+ |
+
+
|
+
Monkeydo comes as an importable ECMAScript 6 module. In this guide we'll import this directly from a ./modules/ folder, but any web-accesible location will work.
+Monkeydo
from your repo clone or download
+
+import { default } from "./modules/Monkeydo/Monkeydo.mjs";
+
+
+const methods = {
+ myJavaScriptMethod: (foo,bar) => {
+ console.log(foo,bar);
+ }
+}
+
+
+{
+ "tasks": [
+ [0,"myJavaSriptMethod","I see skies of","blue"],
+ [300,"myJavaSriptMethod","red","roses too"],
+ [160,"myJavaSriptMethod","I see them","bloom"],
+ [1200,"myJavaSriptMethod","for","me and you"]
+ ]
+}
+
+ Monkeydo
with your methods and manifest
+
+const monkey = new Monkeydo(methods,manifest);
+monkey.do();
+
+ The example above would be the same as running:
+
+console.log("I see skies","of blue"); // Right away
+console.log("red","roses too"); // 300 milliseconds after the first
+console.log("I see them","bloom"); // 160 milliseconds after that one
+console.log("for","me and you"); // and lastly, 1200 after that
+
diff --git a/worker/Monkey.js b/worker/Monkey.js
index eb0a2ff..9bb7d66 100644
--- a/worker/Monkey.js
+++ b/worker/Monkey.js
@@ -5,7 +5,13 @@ class Monkey {
this.data = manifest.body;
this.dataLength = this.data.length - 1;
- this.i = 0;
+ this.flags = {
+ playing: 0,
+ stacking: 0, // Subsequent calls to play() will build a queue (jQuery-style)
+ loop: 0, // Loop n times; <0 = infinite
+ }
+
+ this.i = 0; // Manifest iterator index
this.queue = {
task: null,
next: null
@@ -13,50 +19,97 @@ class Monkey {
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);
+ // Parse task components and send them to main thread
+ run(task) {
+ this.i++; // Advance index
+ postMessage(["TASK",task]);
}
+ // Interrupt timeout and put monkey to sleep
interrupt() {
clearTimeout(this.queue.task);
clearTimeout(this.queue.next);
this.queue.task = null;
this.queue.next = null;
+ this.flags.playing = 0;
+ }
+
+ play() {
+ // Stack playback as loops if flag is set
+ if(this.flags.playing) {
+ if(this.flags.stacking) {
+ this.flags.loop++;
+ }
+ return;
+ }
+ this.queueNext();
+ }
+
+ // Schedule task for execution by index
+ queueNext() {
+ this.flags.playing = 1;
+ const data = this.data[this.i];
+ const task = {
+ wait: data[0],
+ func: data[1],
+ args: data.slice(2)
+ };
+
+ // Schedule the current task to run after the specified wait time
+ this.queue.task = setTimeout(() => this.run(task),task.wait);
+
+ // We're out of tasks to schedule..
+ if(this.i >= this.dataLength) {
+ this.i = -1;
+ // Exit if we're out of loops
+ if(this.flags.loop === 0) {
+ this.flags.playing = 0;
+ return false;
+ }
+
+ // Decrement loop iterations if not infinite (negative int)
+ if(this.flags.loop > 0) {
+ this.flags.loop = this.flags.loop - 1;
+ }
+ }
+
+ // Run this function again when the scheduled task will fire
+ this.queue.next = setTimeout(() => this.queueNext(),task.wait);
}
}
-// Global event handler for this worker
+// Global message event handler for this worker
onmessage = (message) => {
- const type = message.data[0] ? message.data[0] : null;
+ const type = message.data[0] ? message.data[0] : message.data;
const data = message.data[1];
switch(type) {
+ // Attempt to load manfiest provided by initiator thread
case "GIVE_MANIFEST":
try {
this.monkey = new Monkey(data);
- postMessage("OK");
+ postMessage(["RECEIVED_MANIFEST","OK"]);
}
catch(error) {
- postMessage(["MANIFEST_ERROR",error]);
+ postMessage(["RECEIVED_MANIFEST",error]);
}
break;
- case "PLAYING":
- this.monkey.queueNext();
+ case "SET_PLAYING":
+ if(data === true) {
+ this.monkey.play();
+ return;
+ }
+ this.monkey.interrupt();
+ break;
+
+ case "GET_FLAG":
+ const flag = this.monkey.flags[data];
+ postMessage(parseInt(flag));
+ break;
+
+ case "SET_FLAG":
+ this.monkey.flags[data[0]] = data[1];
break;
default: return; // No op
diff --git a/worker/MonkeyManager.mjs b/worker/MonkeyManager.mjs
new file mode 100644
index 0000000..66d2dd7
--- /dev/null
+++ b/worker/MonkeyManager.mjs
@@ -0,0 +1,79 @@
+// Task manager for Monkeydo dedicated workers
+
+export default class MonkeyManager {
+ constructor(methods) {
+ // Object of scoped methods for this manifest
+ this.methods = {};
+ Object.assign(this.methods,methods);
+
+ // Get path of this file
+ let location = new URL(import.meta.url);
+ location = location.pathname.replace("MonkeyManager.mjs",""); // Get parent directory
+
+ // Spawn a dedicated worker for scheduling events from manifest
+ this.worker = new Worker(location + "Monkey.js");
+ this.worker.addEventListener("message",message => this.message(message));
+ }
+
+ // Get a status flag from the worker
+ async getFlag(flag) {
+ this.worker.postMessage(["GET_FLAG",flag]);
+ const response = await new Promise((resolve) => {
+ this.worker.addEventListener("message",message => resolve(message.data));
+ });
+ this.debug("GET_FLAG",flag,response);
+ return response;
+ }
+
+ // Set a status flag for the worker
+ async setFlag(flag,value = 0) {
+ const flagExists = await this.getFlag(flag);
+ if(flagExists === null) {
+ this.debug(flagExists);
+ throw new Error("Flag does not not exist");
+ }
+ this.worker.postMessage(["SET_FLAG",[flag,value]]);
+ }
+
+ // Call method from object and pass arguments
+ runTask(task) {
+ this.methods[task.func](...task.args);
+ }
+
+ play() {
+ this.worker.postMessage(["SET_PLAYING",true]);
+ }
+
+ pause() {
+ this.worker.postMessage(["SET_PLAYING",false]);
+ }
+
+ // Pass manifest to worker and await response
+ async giveManifest() {
+ this.worker.postMessage(["GIVE_MANIFEST",this.manifest]);
+
+ const status = await new Promise((resolve,reject) => {
+ const ack = this.worker.addEventListener("message",message => {
+ if(message.data[0] !== "RECEIVED_MANIFEST") {
+ return false;
+ }
+
+ if(message.data[1] !== "OK") {
+ reject(message.data);
+ }
+ resolve();
+ });
+ this.worker.removeEventListener("message",ack);
+ });
+ return status;
+ }
+
+ message(message) {
+ const type = message.data[0] ? message.data[0] : message.data;
+ const data = message.data[1];
+ if(type !== "TASK") {
+ return false;
+ }
+ this.runTask(data);
+ }
+}
\ No newline at end of file
diff --git a/worker/TaskManager.mjs b/worker/TaskManager.mjs
deleted file mode 100644
index c126685..0000000
--- a/worker/TaskManager.mjs
+++ /dev/null
@@ -1,38 +0,0 @@
-// 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