Last active
January 24, 2017 04:14
-
-
Save Trung0246/d647593027516648eb8ee54a609b5ece to your computer and use it in GitHub Desktop.
Coroutine implementation in ES5 Javascript, same like Generator in ES6, pretty heavy(?) to process, as I never test performance before ...
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
| //P.S: really sorry for bad English, not my first language :( | |
| //Main coroutine class | |
| function Coroutine(configs, condition, param, actions, workData) { | |
| /* | |
| Configs object: (Only have one option, really ... useless?) | |
| { | |
| reset: //Boolean, check if Coroutine be able to reset and iterate again, | |
| } | |
| */ | |
| this.configs = configs; | |
| /* | |
| Condition function, actually pretty much same like while loop, return true if want to keep running Coroutine, else false | |
| */ | |
| this.condition = condition; | |
| /* | |
| Parameter, pass value here, can be anything really... | |
| */ | |
| this.param = param; | |
| /* | |
| Array of function that Coroutine will run through, example: | |
| [ | |
| function(param, workData) { //param will pass as first argument on every function in Coroutine, then workData | |
| console.log("Hello") | |
| }, | |
| wait(function(param) { //Basic use of functions below the file, here return false mean wait for nothing, keep continue | |
| return false; | |
| }), | |
| function(param) { | |
| console.log("there"); | |
| }, | |
| function(param) { | |
| console.log("!!! <3"); | |
| }, | |
| //You can guess what Coroutine will log :P : | |
| // > Hello | |
| // > there | |
| // > !!! <3 | |
| ] | |
| */ | |
| this.actions = actions; | |
| /* | |
| This one is the core of Coroutine, it will store many valuable data that Coroutine needed when iterate, do not modify this | |
| unless you know what to do... | |
| */ | |
| this.workData = workData || {}; | |
| this.workData.process = this.workData.process || []; | |
| //If iteration done, this will set to true, like ES6 Javascript | |
| this.done = false; | |
| //Unused, really, I think this may use for return value of Generator like ES6 | |
| this.value = undefined; | |
| return this; | |
| } | |
| //Here are class function | |
| Coroutine.prototype = { | |
| next: function() { //Like ES6 Generator, call this everytime if want to iterate to next step | |
| var tempData; | |
| //Check if coroutine is already done, else push new temporary iterator object of main scope | |
| if (this.workData.process.length <= 0 && this.configs.reset !== true && this.workData.jump) { | |
| return this.stop(); | |
| } else if ((this.configs.reset === true && this.workData.process.length <= 0) || !this.workData.jump) { | |
| //Push first scope to workData.process array | |
| this.workData.process.push({ | |
| location: 0, | |
| condition: this.condition, | |
| actions: this.actions, | |
| }); | |
| //This object will store jump flag | |
| this.workData.jump = {}; | |
| //Set this to false, as Coroutine will start to run | |
| this.done = false; | |
| } | |
| //Check if lastest process is waiting | |
| if (this.workData.process[this.workData.process.length - 1].type === "wait") { | |
| //If so, check if true then wait, else continue | |
| if (this.workData.process[this.workData.process.length - 1].condition(this.param, this.workData)) { | |
| return this.stop(); | |
| } else { | |
| //Pop out wait process | |
| this.workData.process.pop(); | |
| } | |
| } | |
| //Real magic happen in couHelper function, tempData will store what Coroutine will do next, parameter of this are pretty important... | |
| //I can't explain in-depth what really happen here, as my English is limited :\ | |
| tempData = couHelper(this.param, this.workData.process[this.workData.process.length - 1].condition(this.param, this.workData) || (this.workData.process[this.workData.process.length - 1].location < this.workData.process[this.workData.process.length - 1].actions.length && this.workData.process[this.workData.process.length - 1].location !== 0), this.workData); | |
| //Check if tempData is not undefined | |
| if (tempData) { | |
| //Check tempData type | |
| switch(tempData.func) { | |
| case "repeat": { | |
| //Call next iterate | |
| return this.next(); | |
| } | |
| break; | |
| case "wait": { | |
| //Stop Coroutine for waiting | |
| return this.stop(); | |
| } | |
| break; | |
| } | |
| } else { | |
| //Pop out processed loop | |
| this.workData.process.pop(); | |
| //Check if process array length is larger than 0, continue Coroutine | |
| if (this.workData.process.length > 0) { | |
| return this.next(); | |
| } else { | |
| //Stop Iteration, as Coroutine done it's work | |
| this.done = true; | |
| return this.stop(); | |
| } | |
| } | |
| }, | |
| //Reset Coroutine | |
| reset: function() { | |
| this.workData = {}; | |
| this.workData.process = []; | |
| return this.next(); | |
| }, | |
| //Stop Coroutine, please don't call this outside this.next() function | |
| stop: function() { | |
| return this; | |
| }, | |
| }; | |
| //Coroutine helper | |
| function couHelper(param, tempBool, workData) { | |
| //Here we go!! The REAL MAGIC !! | |
| var tempData; | |
| //As I said, my English is limited, so I don't know how to explain what is going on here... | |
| while (tempBool) { | |
| while (workData.process[workData.process.length - 1].location < workData.process[workData.process.length - 1].actions.length) { | |
| if (typeof workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location] === "object") { | |
| switch (workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location].func) { | |
| case "repeat": { | |
| tempData = workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location]; | |
| workData.process[workData.process.length - 1].location += 1; | |
| tempData.location = 0; | |
| workData.process.push(tempData); | |
| return tempData; | |
| } | |
| break; | |
| case "wait": { | |
| if (workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location].condition(param, workData)) { | |
| tempData = workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location]; | |
| workData.process[workData.process.length - 1].location += 1; | |
| workData.process.push(tempData); | |
| return tempData; | |
| } else { | |
| workData.process[workData.process.length - 1].location += 1; | |
| if (workData.process[workData.process.length - 1].location >= workData.process[workData.process.length - 1].actions.length) { | |
| tempData = { | |
| func: "repeat", | |
| }; | |
| return tempData; | |
| } | |
| workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location](param, workData); | |
| } | |
| } | |
| break; | |
| case "jump": { | |
| switch(workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location].type) { | |
| case 0: { | |
| if (!workData.jump[workData.process.length - 1]) { | |
| workData.jump[workData.process.length - 1] = {}; | |
| } | |
| workData.jump[workData.process.length - 1][workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location].value] = workData.process[workData.process.length - 1].location; | |
| } | |
| break; | |
| case 1: { | |
| if (workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location].condition(param, workData)) { | |
| if (typeof workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location].value === "string") { | |
| workData.process[workData.process.length - 1].location = workData.jump[workData.process.length - 1][workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location].value]; | |
| } else { | |
| workData.process[workData.process.length - 1].location = workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location].value; | |
| } | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| break; | |
| case "stop": { | |
| if (workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location].condition(param, workData)) { | |
| switch (workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location].type) { | |
| case 0: { | |
| if (workData.process.length > 1) { | |
| workData.process.pop(); | |
| workData.process[workData.process.length - 1].location -= 1; | |
| } else { | |
| workData.process[0].location = workData.process[workData.process.length - 1].actions.length - 1; | |
| workData.process[0].condition = function () { | |
| return false; | |
| }; | |
| } | |
| } | |
| break; | |
| case 1: { | |
| workData.process[workData.process.length - 1].location = workData.process[workData.process.length - 1].actions.length - 1; | |
| } | |
| break; | |
| case 2: { | |
| for (var deleteCount = workData.process.length - 1; deleteCount >= 1; --deleteCount) { | |
| workData.process.splice(deleteCount, 1); | |
| } | |
| workData.process[0].location = workData.process[workData.process.length - 1].actions.length - 1; | |
| workData.process[0].condition = function () { | |
| return false; | |
| }; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| break; | |
| default: { | |
| throw new Error("Invalid action at " + workData.process[workData.process.length - 1].location); | |
| } | |
| } | |
| } else { | |
| workData.process[workData.process.length - 1].actions[workData.process[workData.process.length - 1].location](param, workData); | |
| } | |
| workData.process[workData.process.length - 1].location += 1; | |
| } | |
| tempBool = workData.process[workData.process.length - 1].condition(param, workData); | |
| workData.process[workData.process.length - 1].location = 0; | |
| } | |
| return; | |
| } | |
| //Here are some basic function | |
| function repeat(condition, actions) { //Repeat like while() loop | |
| return { | |
| func: "repeat", | |
| location: 0, //Please don't touch this | |
| condition: condition, //Condition function, like "while" | |
| actions: actions, //Array of functions | |
| }; | |
| } | |
| function wait(condition) { //Yield | |
| return { | |
| func: "wait", | |
| condition: condition, //Yield if this function return true | |
| }; | |
| } | |
| function stop(type, condition) { //Stop loop or coroutine based on type | |
| return { | |
| func: "stop", | |
| type: type, //0: act like "break", 1: act like "continue", 2: break whole coroutine | |
| condition: condition, //Same as above, break only when function return true | |
| }; | |
| } | |
| function jump(type, value, condition) { //Jump to specific location like goto | |
| return { | |
| func: "jump", | |
| type: type, //0: set jump flag, 1: behavior based on value | |
| // else explained below | |
| condition: condition, //Return true if want to jump | |
| value: value, //Value like flag and number | |
| //If type is 1: | |
| //if string, jump to same-scope defined flag, | |
| //if number, jump to specific location of function in same-scope array of functions | |
| }; | |
| } | |
| //One obvious flaw: work only within array of functions, can't nest into children function itself | |
| //As pretty same as ES6 Generator, you can do like this: | |
| /* | |
| var testing = new Coroutine(**parameter needed goes here**); | |
| //Iterate to next step | |
| testing.next(); | |
| */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment