diff --git a/README.md b/README.md index 074d82b..ca4ca5a 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ * **Phaser:** see [Phaser Plugin](https://github.com/appsbu-de/phaser_plugin_pathfinding) * **Bower:** `bower install easystarjs` -## Description -easystar.js is an asynchronous A* pathfinding API written in Javascript for use in your HTML5 games and interactive projects. The goal of this project is to make it easy and fast to implement performance conscious pathfinding. +## Description +easystar.js is an asynchronous A\* pathfinding API written in Javascript for use in your HTML5 games and interactive projects. The goal of this project is to make it easy and fast to implement performance conscious pathfinding. ## Features @@ -73,6 +73,9 @@ var instanceId = easystar.findPath(startX, startY, endX, endY, callback); // ... easystar.cancelPath(instanceId); ``` +```javascript +easystar.enableParallelCompute(); +``` ## Usage @@ -96,7 +99,7 @@ var grid = [[0,0,1,0,0], ``` Set our grid. -```javascript +```javascript easystar.setGrid(grid); ``` Set tiles which are "walkable". @@ -115,14 +118,14 @@ easystar.findPath(0, 0, 4, 0, function( path ) { }); ``` -EasyStar will not yet start calculating my path. +EasyStar will not yet start calculating my path. In order for EasyStar to actually start calculating, I must call the calculate() method. You should call `easystar.calculate()` on a ticker, or setInterval. -If you have a large grid, then it is possible that these calculations could slow down the browser. -For this reason, it might be a good idea to give EasyStar a smaller `iterationsPerCalculation` value via +If you have a large grid, then it is possible that these calculations could slow down the browser. +For this reason, it might be a good idea to give EasyStar a smaller `iterationsPerCalculation` value via ```javascript easystar.setIterationsPerCalculation(1000); diff --git a/index.d.ts b/index.d.ts index a31b48e..4efa09f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -41,6 +41,20 @@ export class js { */ disableDiagonals(): void + /** + * Enables parallel path computing. + * + * If multiple calls to .findPath() are made, this + * will distribute the load by running iterationsPerCalculation before + * moving onto the next path in the queue. + **/ + enableParallelCompute(): void + + /** + * Disables parallel path computing. + */ + disableParallelCompute(): void + /** * Sets the collision grid that EasyStar uses. * diff --git a/src/easystar.js b/src/easystar.js index 8323066..dd7eff6 100755 --- a/src/easystar.js +++ b/src/easystar.js @@ -31,9 +31,11 @@ EasyStar.js = function() { var iterationsSoFar; var instances = {}; var instanceQueue = []; + var instanceQueueIndex = 0; var iterationsPerCalculation = Number.MAX_VALUE; var acceptableTiles; var diagonalsEnabled = false; + var parallelEnabled = false; /** * Sets the collision grid that EasyStar uses. @@ -67,6 +69,24 @@ EasyStar.js = function() { syncEnabled = false; }; + /** + * Enables parallel path computing. + * + * If multiple calls to .findPath() are made, this + * will distribute the load by running iterationsPerCalculation before + * moving onto the next path in the queue. + **/ + this.enableParallelCompute = function() { + parallelEnabled = true; + }; + + /** + * Disables parallel path computing. + **/ + this.disableParallelCompute = function() { + parallelEnabled = false; + }; + /** * Enable diagonal pathfinding. */ @@ -305,6 +325,7 @@ EasyStar.js = function() { var instanceId = nextInstanceId ++; instances[instanceId] = instance; instanceQueue.push(instanceId); + return instanceId; }; @@ -334,6 +355,7 @@ EasyStar.js = function() { if (instanceQueue.length === 0 || collisionGrid === undefined || acceptableTiles === undefined) { return; } + for (iterationsSoFar = 0; iterationsSoFar < iterationsPerCalculation; iterationsSoFar++) { if (instanceQueue.length === 0) { return; @@ -344,11 +366,12 @@ EasyStar.js = function() { iterationsSoFar = 0; } - var instanceId = instanceQueue[0]; + var instanceId = instanceQueue[instanceQueueIndex]; var instance = instances[instanceId]; if (typeof instance == 'undefined') { // This instance was cancelled - instanceQueue.shift(); + instanceQueue.splice(instanceQueueIndex, 1); + updateQueueIndex(); continue; } @@ -356,7 +379,8 @@ EasyStar.js = function() { if (instance.openList.size() === 0) { instance.callback(null); delete instances[instanceId]; - instanceQueue.shift(); + instanceQueue.splice(instanceQueueIndex, 1); + updateQueueIndex(); continue; } @@ -375,7 +399,8 @@ EasyStar.js = function() { var ip = path; instance.callback(ip); delete instances[instanceId]; - instanceQueue.shift(); + instanceQueue.splice(instanceQueueIndex, 1); + updateQueueIndex(); continue; } @@ -441,6 +466,8 @@ EasyStar.js = function() { } } + + updateQueueIndex(); }; // Private methods follow @@ -544,6 +571,18 @@ EasyStar.js = function() { return (dx + dy); } }; + + var updateQueueIndex = function () { + if (!parallelEnabled || !instanceQueue.length) { + return; + } + + instanceQueueIndex--; + + if (instanceQueueIndex < 0) { + instanceQueueIndex = instanceQueue.length - 1; + } + }; } EasyStar.TOP = 'TOP' diff --git a/test/easystartest.js b/test/easystartest.js index f932812..29f73e0 100644 --- a/test/easystartest.js +++ b/test/easystartest.js @@ -346,4 +346,84 @@ describe("EasyStar.js", function() { easyStar.calculate(); }) + + it("It should handle computing paths in serial", function (done) { + var easyStar = new EasyStar.js(); + var map = [[1,1,0,1,1], + [1,1,0,1,1], + [1,1,0,1,1], + [1,1,1,1,1], + [1,1,1,1,1]]; + + easyStar.setGrid(map); + easyStar.setAcceptableTiles([1]); + easyStar.setIterationsPerCalculation(2); + easyStar.disableParallelCompute(); + + var iteration = 0; + // iteration each path was found on + var firstIteration = -1; + var secondIteration = -1; + var maxIterations = 8; + + easyStar.findPath(1, 2, 3, 2, () => { + firstIteration = iteration; + }); + + easyStar.findPath(1, 2, 3, 2, () => { + secondIteration = iteration; + expect(firstIteration).toBeLessThan(secondIteration); + done(); + }); + + function iterate() { + if (iteration < maxIterations) { + easyStar.calculate(); + iteration += 1; + setTimeout(iterate, 1); + } + } + + iterate(); + }) + + it("It should handle computing paths in parallel", function (done) { + var easyStar = new EasyStar.js(); + var map = [[1,1,0,1,1], + [1,1,0,1,1], + [1,1,0,1,1], + [1,1,1,1,1], + [1,1,1,1,1]]; + + easyStar.setGrid(map); + easyStar.setAcceptableTiles([1]); + easyStar.setIterationsPerCalculation(2); + easyStar.enableParallelCompute(); + + var iteration = 0; + // iteration each path was found on + var firstIteration = -1; + var secondIteration = -1; + var maxIterations = 8; + + easyStar.findPath(1, 2, 3, 2, () => { + firstIteration = iteration; + }); + + easyStar.findPath(1, 2, 3, 2, () => { + secondIteration = iteration; + expect(firstIteration).toEqual(secondIteration); + done(); + }); + + function iterate() { + if (iteration < maxIterations) { + easyStar.calculate(); + iteration += 1; + setTimeout(iterate, 1); + } + } + + iterate(); + }) });