UNPKG

a-star-for-async-data

Version:

A* search algorithm for asynchronous data sources

239 lines (238 loc) 9.85 kB
"use strict"; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [0, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); var logLevels = { 'none': 0, 'info': 1, 'debug': 2 }; var log_level = 0; function log(msg) { if (log_level >= logLevels.info) { console.log(msg); } } // fn runGenerator : based on code by Kyle Simpson (https://davidwalsh.name/async-generators on Dec 7, 2016) function runGenerator(it) { var ret; var value = undefined; var promise = new Promise(function (resolve, reject) { // asynchronously iterate over generator (function iterate(val) { try { ret = it.next(val); if (!ret.done) { value = ret.value; // poor man's "is it a promise?" test if (ret.value && typeof ret.value === "object" && "then" in ret.value) { // wait on the promise ret.value.then(iterate); } else { // avoid synchronous recursion setTimeout(function () { iterate(ret.value); }, 0); } } else { resolve(value); } } catch (err) { reject(err); } })(); }); return promise; } var Astar = /** @class */ (function () { function Astar(customCallbackFuncs) { if (customCallbackFuncs === void 0) { customCallbackFuncs = {}; } this.exitArcsForNodeId = customCallbackFuncs.exitArcsForNodeId || this.exitArcsForNodeId; this.h = customCallbackFuncs.h || this.h; } // Calculate the heuristic cost to traverse from one node to another. Astar.prototype.h = function (from, to) { return 0; }; // Promisify h Astar.prototype.lookupH = function (from, to) { return Promise.resolve(this.h(from, to)); }; // This function is used to test the goal state if the user provides a non function // for the search goal. Astar.prototype.exactMatchGoalFunc = function (goalNodeId) { return function (testNodeId) { return Promise.resolve(goalNodeId === testNodeId); }; }; /* * Clean-up a passed in goal function to ensure that it is usable. * * Functions are assumed to test a given node to see if it is a goal * node. It should return a boolean or a Promise thereof. * Boolean functions are Promisified. * * Simple non-func values are assumed to be goal node IDs. * * TODO: Perhaps we can have a built in function to handle arrays? * (Or is that best left to the caller?) */ Astar.prototype.cleanGoalFunc = function (goalOrGoalFunc) { if (goalOrGoalFunc && {}.toString.call(goalOrGoalFunc) !== '[object Function]') { return this.exactMatchGoalFunc(String(goalOrGoalFunc)); } else { var goalFunc_1 = goalOrGoalFunc; // A goal function was provided. Le'ts be sure it's promisified. return function (a) { return Promise.resolve(goalFunc_1(a)); }; } }; /* * This is a function that should return an array of edge data in the following * form (or a Promise that resolves to this data): * * [ * { from: <originNodeId>, to: <targetNodeId>}, cost: <edgeCost> }, * . * . * . * ] * */ Astar.prototype.exitArcsForNodeId = function (nodeId) { return []; }; // Promisify exitArcsForNodeId Astar.prototype.lookupExitArcsForNodeId = function (nodeId) { return Promise.resolve(this.exitArcsForNodeId(nodeId)); }; // By "extracting" the find path into a generator we can use synchronous-y constructs // while still providing async functionalities. Astar.prototype.findPathGenerator = function (startNodeId, goalOrGoalFunc) { var goalFunc, cameFrom, fCosts, gCosts, open, closed, iteration, bestId, nodeId, edges, _i, edges_1, edge_1, toNodeId, bestGCost, newGCost, gCostExists, _a, _b, _c, path, edge; return __generator(this, function (_d) { switch (_d.label) { case 0: goalFunc = this.cleanGoalFunc(goalOrGoalFunc); startNodeId = String(startNodeId); log("Finding path between " + startNodeId + " and " + goalOrGoalFunc); cameFrom = {}; fCosts = {}; gCosts = {}; open = {}; closed = {}; iteration = 1; open[startNodeId] = startNodeId; cameFrom[startNodeId] = false; gCosts[startNodeId] = 0; fCosts[startNodeId] = 0; // Is this correct? _d.label = 1; case 1: if (!true) return [3 /*break*/, 9]; bestId = null; // Select the best candidate from the open nodes. for (nodeId in open) { if (bestId === null || fCosts[nodeId] < fCosts[bestId]) { bestId = nodeId; } } if (bestId === null) { throw "No path to goal"; } return [4 /*yield*/, goalFunc(bestId)]; case 2: if (_d.sent()) { // We have a solution! return [3 /*break*/, 9]; } return [4 /*yield*/, this.lookupExitArcsForNodeId(bestId)]; case 3: edges = _d.sent(); _i = 0, edges_1 = edges; _d.label = 4; case 4: if (!(_i < edges_1.length)) return [3 /*break*/, 8]; edge_1 = edges_1[_i]; toNodeId = String(edge_1.to); if (!!closed[toNodeId]) return [3 /*break*/, 7]; bestGCost = gCosts[bestId]; newGCost = bestGCost + edge_1.cost; gCostExists = gCosts.hasOwnProperty(toNodeId); if (!(!gCostExists || gCosts[toNodeId] > newGCost)) return [3 /*break*/, 6]; gCosts[toNodeId] = newGCost; _a = fCosts; _b = toNodeId; _c = newGCost; return [4 /*yield*/, this.lookupH(startNodeId, toNodeId)]; case 5: _a[_b] = _c + (_d.sent()); cameFrom[toNodeId] = edge_1; _d.label = 6; case 6: open[toNodeId] = toNodeId; _d.label = 7; case 7: _i++; return [3 /*break*/, 4]; case 8: ; closed[bestId] = true; delete open[bestId]; iteration++; return [3 /*break*/, 1]; case 9: path = []; for (edge = cameFrom[bestId]; edge !== false; edge = cameFrom[String(edge.from)]) { path.unshift(edge); } return [4 /*yield*/, { cost: gCosts[bestId], path: path }]; case 10: _d.sent(); return [2 /*return*/]; } }); }; Astar.prototype.findPath = function (startNodeId, goalFunc) { var pathGenerator = this.findPathGenerator(startNodeId, goalFunc); return runGenerator(pathGenerator); }; return Astar; }()); exports.Astar = Astar; Astar.Debug = function () { log_level = 1; return Astar; };