diff --git a/src/Constants.js b/src/Constants.js index 805c49c40..a4fb293fe 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -1,5 +1,8 @@ CONSTANTS = { //Max level for any skill. Determined by max numerical value in javascript and the skill level //formula in Player.js - MaxSkillLevel: 1796, + MaxSkillLevel: 1796, + + //Time (ms) it takes to run one operation in Netscript. + CodeInstructionRunTime: 1500, } \ No newline at end of file diff --git a/src/Netscript/NetscriptWorker.js b/src/Netscript/NetscriptWorker.js index 11c7397f7..855fb7ed0 100644 --- a/src/Netscript/NetscriptWorker.js +++ b/src/Netscript/NetscriptWorker.js @@ -7,53 +7,76 @@ * Evaluates the Abstract Syntax Tree for Netscript * generated by the Parser class */ +// Evaluator should return a Promise, so that any call to evaluate() can just +//wait for that promise to finish before continuing function evaluate(exp, workerScript) { var env = workerScript.env; switch (exp.type) { case "num": case "str": case "bool": - return exp.value; - + return new Promise(function(resolve, reject) { + resolve(exp.value); + }); + break; case "var": - return env.get(exp.value); - + return new Promise(function(resolve, reject) { + resolve(env.get(exp.value)); + }); + break; //Can currently only assign to "var"s case "assign": - if (exp.left.type != "var") - throw new Error("Cannot assign to " + JSON.stringify(exp.left)); - - var p = new Promise(function(resolve, reject) { - setTimeout(function() { - var res = evaluate(exp.right, workerScript); - resolve(res); - }, 3000) - }); - - return p.then(function(expRight) { - return env.set(exp.left.value, expRight); + console.log("Evaluating assign operation"); + return new Promise(function(resolve, reject) { + if (exp.left.type != "var") + throw new Error("Cannot assign to " + JSON.stringify(exp.left)); + + var p = new Promise(function(resolve, reject) { + setTimeout(function() { + var expRightPromise = evaluate(exp.right, workerScript); + expRightPromise.then(function(expRight) { + resolve(expRight); + }); + }, CONSTANTS.CodeInstructionRunTime) + }); + + p.then(function(expRight) { + console.log("Right side of assign operation resolved with value: " + expRight); + env.set(exp.left.value, expRight); + console.log("Assign operation finished"); + resolve("assignFinished"); + }); }); case "binary": - var pLeft = new Promise(function(resolve, reject) { - setTimeout(function() { - var resLeft = evaluate(exp.left, workerScript); - resolve(resLeft); - }, 3000); - }); + console.log("Binary operation called"); + return new Promise(function(resolve, reject) { + var pLeft = new Promise(function(resolve, reject) { + setTimeout(function() { + var promise = evaluate(exp.left, workerScript); + promise.then(function(valLeft) { + resolve(valLeft); + }); + }, CONSTANTS.CodeInstructionRunTime); + }); - var pRight = pLeft.then(function(expLeft) { - setTimeout(function() { - var resRight = evaluate(exp.right, workerScript); - resolve([expLeft, resRight]); - }, 3000); - }); - - return pRight.then(function(args) { - return apply_op(exp.operator, - args[0], - args[1]); + pLeft.then(function(valLeft) { + var pRight = new Promise(function(resolve, reject) { + setTimeout(function() { + var promise = evaluate(exp.right, workerScript); + promise.then(function(valRight) { + resolve([valLeft, valRight]); + }); + }, CONSTANTS.CodeInstructionRunTime); + }); + + pRight.then(function(args) { + console.log("Resolving binary operation"); + resolve(apply_op(exp.operator, args[0], args[1])); + }); + }); }); + break; //TODO case "if": @@ -73,98 +96,42 @@ function evaluate(exp, workerScript) { return exp.else ? evaluate(exp.else, workerScript) : false; case "for": - var pInit = new Promise(function(resolve, reject) { - setTimeout(function() { - var resInit = evaluate(exp.init, workerScript); - resolve(resInit); - }, 3000); - }); - - pInit.then(function(expInit) { - setTimeout(function() { - var resCond = evaluate(exp.cond, workerScript); - console.log("Evaluated the conditional"); - resolve(resCond); - }, 3000); - }) - - .then(function(expCond) { - while (expCond) { - var pCode = new Promise(function(resolve, reject) { - setTimeout(function() { - var resCode = evaluate(exp.code, workerScript); - resolve(resCode); - }, 3000); + return new Promise(function(resolve, reject) { + console.log("for loop encountered in evaluator"); + var pInit = new Promise(function(resolve, reject) { + setTimeout(function() { + var resInit = evaluate(exp.init, workerScript); + resInit.then(function(foo) { + resolve(resInit); + }); + }, CONSTANTS.CodeInstructionRunTime); + }); + + pInit.then(function(expInit) { + var pForLoop = evaluateFor(exp, workerScript); + pForLoop.then(function(forLoopRes) { + resolve("forLoopDone"); }); - - var pPostloop = pCode.then(function(expCode) { - setTimeout(function() { - var resPostloop = evaluate(exp.postloop, workerScript); - resolve(resPostloop); - }, 3000); - }); - - var pCond = pPostloop.then(function(expPostloop) { - setTimeout(function() { - var resCond = evaluate(exp.cond, workerScript); - resolve(resCond); - }, 3000); - }); - - pCond.then(function(resCond) { - expCond = resCond; - //Do i need resolve here? - }); - } + }); }); - - - //TODO I don't think I need to return anything..but I might be wrong break; case "while": - var pCond = new Promise(function(resolve, reject) { - setTimeout(function() { - var resCond = evaluate(exp.cond, workerScript); - resolve(resCond); - }, 3000); + console.log("Evaluating while loop"); + return new Promise(function(resolve, reject) { + var pEvaluateWhile = evaluateWhile(exp, workerScript); + pEvaluateWhile.then(function(whileLoopRes) { + resolve("whileLoopDone"); + }); }); - - pCond.then(function(expCond) { - while (expCond) { - var pCode = new Promise(function(resolve, reject) { - setTimeout(function() { - var resCode = evaluate(exp.code, workerScript); - resolve(resCode); - }, 3000); - }); - - pCode.then(function(expCode) { - expCond = evaluate(exp.cond, workerScript); - //Do i need resolve here? - }); - } - }); - - - - //TODO I don't think I need to return anything..but I might be wrong break; case "prog": - var val = false; - exp.prog.forEach(function(exp){ - var pExp = new Promise(function(resolve, reject) { - setTimeout(function() { - var resExp = evaluate(exp, workerScript); - resolve(resExp); - }, 3000); - }); - - pExp.then(function(resExp) { - val = resExp; + return new Promise(function(resolve, reject) { + var evaluateProgPromise = evaluateProg(exp, workerScript, 0); + evaluateProgPromise.then(function(res) { + resolve(res); }); }); - - return val; + break; /* Currently supported function calls: * hack() @@ -178,13 +145,32 @@ function evaluate(exp, workerScript) { //return func.apply(null, exp.args.map(function(arg){ // return evaluate(arg, env); //})); - if (exp.func.value == "hack") { - console.log("Execute hack()"); - } else if (exp.func.value == "sleep") { - console.log("Execute sleep()"); - } else if (exp.func.value == "print") { - post(evaluate(exp.args[0], workerScript).toString()); - } + return new Promise(function(resolve, reject) { + setTimeout(function() { + if (exp.func.value == "hack") { + console.log("Execute hack()"); + resolve("hackExecuted"); + } else if (exp.func.value == "sleep") { + console.log("Execute sleep()"); + resolve("sleepExecuted"); + } else if (exp.func.value == "print") { + var p = new Promise(function(resolve, reject) { + setTimeout(function() { + var evaluatePromise = evaluate(exp.args[0], workerScript); + evaluatePromise.then(function(res) { + resolve(res); + }); + }, CONSTANTS.CodeInstructionRunTime); + }); + + p.then(function(res) { + post(res.toString()); + console.log("Print call executed"); + resolve("printExecuted"); + }); + } + }, CONSTANTS.CodeInstructionRunTime); + }); break; default: @@ -192,6 +178,131 @@ function evaluate(exp, workerScript) { } } +//Evaluate the looping part of a for loop (Initialization block is NOT done in here) +function evaluateFor(exp, workerScript) { + console.log("evaluateFor() called"); + return new Promise(function(resolve, reject) { + var pCond = new Promise(function(resolve, reject) { + setTimeout(function() { + var evaluatePromise = evaluate(exp.cond, workerScript); + evaluatePromise.then(function(resCond) { + console.log("Conditional evaluated to: " + resCond); + resolve(resCond); + }); + }, CONSTANTS.CodeInstructionRunTime); + }); + + pCond.then(function(resCond) { + if (resCond) { + console.log("About to evaluate an iteration of for loop code"); + //Run the for loop code + var pCode = new Promise(function(resolve, reject) { + setTimeout(function() { + var evaluatePromise = evaluate(exp.code, workerScript); + evaluatePromise.then(function(resCode) { + console.log("Evaluated an iteration of for loop code"); + resolve(resCode); + }); + }, CONSTANTS.CodeInstructionRunTime); + }); + + //After the code executes make a recursive call + pCode.then(function(resCode) { + var pPostLoop = new Promise(function(resolve, reject) { + setTimeout(function() { + var evaluatePromise = evaluate(exp.postloop, workerScript); + evaluatePromise.then(function(foo) { + console.log("Evaluated for loop postloop"); + resolve("postLoopFinished"); + }); + }, CONSTANTS.CodeInstructionRunTime); + }); + + pPostLoop.then(function(resPostloop) { + var recursiveCall = evaluateFor(exp, workerScript); + recursiveCall.then(function(foo) { + resolve("endForLoop"); + }); + }); + + }); + } else { + console.log("Cond is false, stopping for loop"); + resolve("endForLoop"); //Doesn't need to resolve to any particular value + } + }); + }); +} + +function evaluateWhile(exp, workerScript) { + console.log("evaluateWhile() called"); + return new Promise(function(resolve, reject) { + var pCond = new Promise(function(resolve, reject) { + setTimeout(function() { + var evaluatePromise = evaluate(exp.cond, workerScript); + evaluatePromise.then(function(resCond) { + console.log("Conditional evaluated to: " + resCond); + resolve(resCond); + }); + }, CONSTANTS.CodeInstructionRunTime); + }); + + pCond.then(function(resCond) { + if (resCond) { + //Run the while loop code + var pCode = new Promise(function(resolve, reject) { + setTimeout(function() { + var evaluatePromise = evaluate(exp.code, workerScript); + evaluatePromise.then(function(resCode) { + console.log("Evaluated an iteration of while loop code"); + resolve(resCode); + }); + }, CONSTANTS.CodeInstructionRunTime); + }); + + //After the code executes make a recursive call + pCode.then(function(resCode) { + var recursiveCall = evaluateWhile(exp, workerScript); + recursiveCall.then(function(foo) { + resolve("endWhileLoop"); + }); + }); + } else { + console.log("Cond is false, stopping while loop"); + resolve("endWhileLoop"); //Doesn't need to resolve to any particular value + } + }); + }); +} + +function evaluateProg(exp, workerScript, index) { + console.log("evaluateProg() called"); + return new Promise(function(resolve, reject) { + if (index >= exp.prog.length) { + console.log("Prog done. Resolving recursively"); + resolve("progFinished"); + } else { + //Evaluate this line of code in the prog + var code = new Promise(function(resolve, reject) { + setTimeout(function() { + var evaluatePromise = evaluate(exp.prog[index], workerScript); + evaluatePromise.then(function(evalRes) { + resolve(evalRes); + }); + }, CONSTANTS.CodeInstructionRunTime); + }); + + //After the code finishes evaluating, evaluate the next line recursively + code.then(function(codeRes) { + var nextLine = evaluateProg(exp, workerScript, index + 1); + nextLine.then(function(nextLineRes) { + resolve("progDone"); + }); + }); + } + }); +} + function apply_op(op, a, b) { function num(x) { if (typeof x != "number") @@ -726,207 +837,20 @@ function InputStream(input) { } } - -/* Object that defines the performance/statistics of the script, - * such as how much money it has stolen from every Foreign Server - * and how much exp it has gained for the player. - * - * Every NetscriptWorker has its own ScriptStats object that is passed - * back to the main thread regularly. The main thread then uses the information - * from the object to change the game state. Then, the worker resets the Script Stats - * object and continues. */ -function ScriptStats() { - ECorpMoneyHacked = 0; - ECorpTimesHacked = 0; - MegaCorpMoneyHacked = 0; - MegaCorpTimesHacked = 0; - BachmanAndAssociatesMoneyHacked = 0; - BachmanAndAssociatesTimesHacked = 0; - NWOMoneyHacked = 0; - NWOTimesHacked = 0; - ClarkeIncorporatedMoneyHacked = 0; - ClarkeIncorporatedTimesHacked = 0; - OmniTekIncorporatedMoneyHacked = 0; - OmniTekIncorporatedTimesHacked = 0; - FourSigmaMoneyHacked = 0; - FourSigmaTimesHacked = 0; - KuaiGongInternationalMoneyHacked = 0; - KuaiGongInternationalTimesHacked = 0; - - FulcrumTechnologiesMoneyHacked = 0; - FulcrumTechnologiesTimesHacked = 0; - FulcrumSecretTechnologiesMoneyHacked = 0; - FulcrumSecretTechnologiesTimesHacked = 0; - StormTechnologiesMoneyHacked = 0; - StormTechnologiesTimesHacked = 0; - DefCommMoneyHacked = 0; - DefCommTimesHacked = 0; - InfoCommMoneyHacked = 0; - InfoCommTimesHacked = 0; - HeliosLabsMoneyHacked = 0; - HeliosLabsTimesHacked = 0; - VitaLifeMoneyHacked = 0; - VitaLifeTimesHacked = 0; - IcarusMicrosystemsMoneyHacked = 0; - IcarusMicrosystemsTimesHacked = 0; - UniversalEnergyMoneyHacked = 0; - UniversalEnergyTimesHacked = 0; - TitanLabsMoneyHacked = 0; - TitanLabsTimesHacked = 0; - MicrodyneTechnologiesMoneyHacked = 0; - MicrodyneTechnologiesTimesHacked = 0; - TaiYangDigitalMoneyHacked = 0; - TaiYangDigitalTimesHacked = 0; - GalacticCybersystemsMoneyHacked = 0; - GalacticCybersystemsTimesHacked = 0; - - AeroCorpMoneyHacked = 0; - AeroCorpTimesHacked = 0; - OmniaCybersystemsMoneyHacked = 0; - OmniaCybersystemsTimesHacked = 0; - ZBDefenseMoneyHacked = 0; - ZBDefenseTimesHacked = 0; - AppliedEnergeticsMoneyHacked = 0; - AppliedEnergeticsTimesHacked = 0; - SolarisSpaceSystemsMoneyHacked = 0; - SolarisSpaceSystemsTimesHacked = 0; - DeltaOneMoneyHacked = 0; - DeltaOneTimesHacked = 0; - - GlobalPharmaceuticalsMoneyHacked = 0; - GlobalPharmaceuticalsTimesHacked = 0; - NovaMedicalMoneyHacked = 0; - NovaMedicalTimesHacked = 0; - ZeusMedicalMoneyHacked = 0; - ZeusMedicalTimesHacked = 0; - UnitaLifeGroupMoneyHacked = 0; - UnitaLifeGroupTimesHacked = 0; - - LexoCorpMoneyHacked = 0; - LexoCorpTimesHacked = 0; - RhoConstructionMoneyHacked = 0; - RhoConstructionTimesHacked = 0; - AlphaEnterprisesMoneyHacked = 0; - AlphaEnterprisesTimesHacked = 0; - AevumPoliceMoneyHacked = 0; - AevumPoliceTimesHacked = 0; - RothmanUniversityMoneyHacked = 0; - RothmanUniversityTimesHacked = 0; - ZBInstituteOfTechnologyMoneyHacked = 0; - ZBInstituteOfTechnologyTimesHacked = 0; - SummitUniversityMoneyHacked = 0; - SummitUniversityTimesHacked = 0; - SysCoreSecuritiesMoneyHacked = 0; - SysCoreSecuritiesTimesHacked = 0; - CatalystVenturesMoneyHacked = 0; - CatalystVenturesTimesHacked = 0; - TheHubMoneyHacked = 0; - TheHubTimesHacked = 0; - CompuTekMoneyHacked = 0; - CompuTekTimesHacked = 0; - NetLinkTechnologiesMoneyHacked = 0; - NetLinkTechnologiesTimesHacked = 0; - JohnsonOrthopedicsMoneyHacked = 0; - JohnsonOrthopedicsTimesHacked = 0; - - FoodNStuffMoneyHacked = 0; - FoodNStuffTimesHacked = 0; - SigmaCosmeticsMoneyHacked = 0; - SigmaCosmeticsTimesHacked = 0; - JoesGunsMoneyHacked = 0; - JoesGunsTimesHacked = 0; - Zer0NightclubMoneyHacked = 0; - Zer0NightclubTimesHacked = 0; - NectarNightclubMoneyHacked = 0; - NectarNightclubTimesHacked = 0; - NeoNightclubMoneyHacked = 0; - NeoNightclubTimesHacked = 0; - SilverHelixMoneyHacked = 0; - SilverHelixTimesHacked = 0; - HongFangTeaHouseMoneyHacked = 0; - HongFangTeaHouseTimesHacked = 0; - HaraKiriSushiBarMoneyHacked = 0; - HaraKiriSushiBarTimesHacked = 0; - PhantasyMoneyHacked = 0; - PhantasyTimesHacked = 0; - MaxHardwareMoneyHacked = 0; - MaxHardwareTimesHacked = 0; - OmegaSoftwareMoneyHacked = 0; - OmegaSoftwareTimesHacked = 0; - - CrushFitnessGymMoneyHacked = 0; - CrushFitnessGymTimesHacked = 0; - IronGymMoneyHacked = 0; - IronGymTimesHacked = 0; - MilleniumFitnessGymMoneyHacked = 0; - MilleniumFitnessGymTimesHacked = 0; - PowerhouseGymMoneyHacked = 0; - PowerhouseGymTimesHacked = 0; - SnapFitnessGymMoneyHacked = 0; - SnapFitnessGymTimesHacked = 0; -} - -ScriptStats.prototype.reset = function() { - for (var key in this) { - if (this.hasOwnProperty(key)) { - this[key] = 0; - } - } -} - - /* Actual Worker Code */ function WorkerScript() { this.name = ""; this.running = false; + this.server = null; this.code = ""; this.env = new Environment(); this.timeout = null; } -var scriptStats = new ScriptStats(); - -var workerForeignServers = null; -var workerPlayer = null; var workerScripts = []; -self.addEventListener('message', msgRecvHandler); -function msgRecvHandler(e) { - /* The first element of the data array from main thread specifies - * what kind of data it is: - * Status Update - * Start Script - * Stop Script - */ - if (e.data.type == "Status Update") { - console.log("Status update received in Script Web Worker"); - ForeignServersScript = JSON.parse(e.data.buf1); - PlayerScript = JSON.parse(e.data.buf2); - } else if (e.data.type == "Start Script") { - var s = new WorkerScript(); - s.name = e.data.buf1; - s.code = e.data.buf2; - workerScripts.push(s); - } else if (e.data.type == "Stop Script") { - var scriptName = e.data.buf1; - for (var i = 0; i < workerScripts.length; i++) { - if (workerScripts[i].name == scriptName) { - //Stop the script from running and then remove it from workerScripts - clearTimeout(workerScripts[i].timeout); - workerScripts.splice(i, 1); - } - } - } - - var ast = Parser(Tokenizer(InputStream(code))); - var globalEnv = new Environment(); - - evaluate(ast, globalEnv); -} - //Loop through workerScripts and run every script that is not currently running function runScriptsLoop() { - console.log("runScriptsLoop() iteration"); for (var i = 0; i < workerScripts.length; i++) { if (workerScripts[i].running == false) { var ast = Parser(Tokenizer(InputStream(workerScripts[i].code))); diff --git a/src/Terminal.js b/src/Terminal.js index 76e7d5706..ec5b2ed53 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -440,6 +440,7 @@ var Terminal = { s.code = Player.currentServer.scripts[i].code; workerScripts.push(s); console.log("Pushed script onto workerScripts"); + return; } } } diff --git a/src/engine.js b/src/engine.js index e3cf92807..b03f29401 100644 --- a/src/engine.js +++ b/src/engine.js @@ -147,7 +147,6 @@ var Engine = { }, /* Main Event Loop */ - _scriptUpdateStatusCounter: 0, idleTimer: function() { //Get time difference var _thisUpdate = new Date().getTime(); @@ -155,21 +154,13 @@ var Engine = { //Divide this by cycle time to determine how many cycles have elapsed since last update diff = Math.round(diff / Engine._idleSpeed); - - Engine._scriptUpdateStatusCounter += diff; - + if (diff > 0) { //Update the game engine by the calculated number of cycles Engine.updateGame(diff); Engine._lastUpdate = _thisUpdate; } - if (Engine._scriptUpdateStatusCounter >= 50) { - console.log("Updating Script Status"); - Engine._scriptUpdateStatusCounter = 0; - //Engine.updateScriptStatus(); - } - window.requestAnimationFrame(Engine.idleTimer); }, @@ -226,16 +217,6 @@ var Engine = { } }, - /* NetScript Web Worker Stuff */ - _scriptWebWorker: null, - updateScriptStatus: function() { - Engine._scriptWebWorker.postMessage( - {'type': "Status Update", - 'buf1': JSON.stringify(ForeignServers), - 'buf2': JSON.stringify(Player)} - ) - }, - /* Initialization */ init: function() { //Initialization functions