diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index df1d7ab67..4b71b2191 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -1,4 +1,4 @@ -import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game"; +import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, TerrainType, Tile, Unit, UnitType } from "../game/Game"; import { PathFinder } from "../pathfinding/PathFinding"; import { PathFindResultType } from "../pathfinding/AStar"; import { SerialAStar } from "../pathfinding/SerialAStar"; @@ -83,7 +83,7 @@ export class PortExecution implements Execution { } continue } - const asyncPF = this.worker.createParallelAStar(this.port.tile(), port.tile(), 100) + const asyncPF = this.worker.createParallelAStar(this.port.tile(), port.tile(), 100, [TerrainType.Ocean]) // console.log(`adding new port path from ${this.player().name()}:${this.port.tile().cell()} to ${port.owner().name()}:${port.tile().cell()}`) this.computingPaths.set(port, asyncPF) } diff --git a/src/core/pathfinding/PathFinding.ts b/src/core/pathfinding/PathFinding.ts index 40ec4fda1..5a2ce35c0 100644 --- a/src/core/pathfinding/PathFinding.ts +++ b/src/core/pathfinding/PathFinding.ts @@ -1,4 +1,4 @@ -import { Cell, Game, Tile } from "../game/Game"; +import { Cell, Game, TerrainTile, TerrainType, Tile } from "../game/Game"; import { manhattanDist } from "../Util"; import { AStar, PathFindResultType, SearchNode, TileResult } from "./AStar"; import { ParallelAStar, WorkerClient } from "../worker/WorkerClient"; @@ -51,11 +51,14 @@ export class PathFinder { ) } - public static Parallel(game: Game, worker: WorkerClient, numTicks: number): PathFinder { + public static Parallel(game: Game, worker: WorkerClient, numTicks: number, ...types: TerrainType[]): PathFinder { + if (types.length == 0) { + types = [TerrainType.Ocean] + } return new PathFinder( game, (curr: Tile, dst: Tile) => { - return worker.createParallelAStar(curr, dst, numTicks) + return worker.createParallelAStar(curr, dst, numTicks, types) } ) } diff --git a/src/core/worker/Worker.worker.ts b/src/core/worker/Worker.worker.ts index cb8504cbe..abfc37aed 100644 --- a/src/core/worker/Worker.worker.ts +++ b/src/core/worker/Worker.worker.ts @@ -3,15 +3,19 @@ import { Cell, GameMap, TerrainMap, TerrainTile, TerrainType } from "../game/Gam import { createMiniMap, loadTerrainMap } from "../game/TerrainMapLoader"; import { PriorityQueue } from "@datastructures-js/priority-queue"; import { SerialAStar } from "../pathfinding/SerialAStar"; -import { PathFindResultType, SearchNode } from "../pathfinding/AStar"; +import { AStar, PathFindResultType, SearchNode } from "../pathfinding/AStar"; +import { MiniAStar } from "../pathfinding/MiniAStar"; -let terrainMapPromise: Promise; +let terrainMapPromise: Promise<{ + terrainMap: TerrainMap, + miniMap: TerrainMap +}> | null = null; let searches = new PriorityQueue((a: Search, b: Search) => (a.deadline - b.deadline)) let processingInterval: number | null = null; let isProcessingSearch = false interface Search { - aStar: SerialAStar, + aStar: AStar, deadline: number requestId: string, end: Cell @@ -32,22 +36,31 @@ self.onmessage = (e) => { initializeMap(e.data); break; case 'findPath': - terrainMapPromise.then(tm => findPath(tm, e.data)) + terrainMapPromise.then(tm => findPath(tm.terrainMap, tm.miniMap, e.data)) break; } }; function initializeMap(data: { gameMap: GameMap }) { - terrainMapPromise = loadTerrainMap(data.gameMap).then(tm => createMiniMap(tm)) + terrainMapPromise = loadTerrainMap(data.gameMap) + .then(async terrainMap => { + const miniMap = await createMiniMap(terrainMap); + return { + terrainMap: terrainMap, + miniMap: miniMap + }; + }); self.postMessage({ type: 'initialized' }); - processingInterval = setInterval(computeSearches, .1) as unknown as number; + processingInterval = setInterval(computeSearches, 100000) as unknown as number; } -function findPath(terrainMap: TerrainMap, req: SearchRequest) { +function findPath(terrainMap: TerrainMap, miniTerrainMap: TerrainMap, req: SearchRequest) { console.log(`terrain map height: ${terrainMap.height()}`) - const aStar = new SerialAStar( - terrainMap.terrain(new Cell(Math.floor(req.start.x / 2), Math.floor(req.start.y / 2))), - terrainMap.terrain(new Cell(Math.floor(req.end.x / 2), Math.floor(req.end.y / 2))), + const aStar = new MiniAStar( + terrainMap, + miniTerrainMap, + terrainMap.terrain(req.start), + terrainMap.terrain(req.end), (sn: SearchNode) => (sn as TerrainTile).terrainType() == TerrainType.Ocean, 10_000, req.duration, @@ -76,12 +89,10 @@ function computeSearches() { const search = searches.dequeue() switch (search.aStar.compute()) { case PathFindResultType.Completed: - const path = upscalePath(search.aStar.reconstructPath()) - path.push(search.end) self.postMessage({ type: 'pathFound', requestId: search.requestId, - path: path + path: search.aStar.reconstructPath() }); break; @@ -101,44 +112,3 @@ function computeSearches() { isProcessingSearch = false } } - -function upscalePath(path: Cell[], scaleFactor: number = 2): Cell[] { - // Scale up each point - const scaledPath = path.map(point => (new Cell( - point.x * scaleFactor, - point.y * scaleFactor - ))); - - const smoothPath: Cell[] = []; - - for (let i = 0; i < scaledPath.length - 1; i++) { - const current = scaledPath[i]; - const next = scaledPath[i + 1]; - - // Add the current point - smoothPath.push(current); - - // Always interpolate between scaled points - const dx = next.x - current.x; - const dy = next.y - current.y; - - // Calculate number of steps needed - const distance = Math.max(Math.abs(dx), Math.abs(dy)); - const steps = distance; - - // Add intermediate points - for (let step = 1; step < steps; step++) { - smoothPath.push(new Cell( - Math.round(current.x + (dx * step) / steps), - Math.round(current.y + (dy * step) / steps) - )); - } - } - - // Add the last point - if (scaledPath.length > 0) { - smoothPath.push(scaledPath[scaledPath.length - 1]); - } - - return smoothPath; -} diff --git a/src/core/worker/WorkerClient.ts b/src/core/worker/WorkerClient.ts index 5f28ea4b2..e5cffc9b4 100644 --- a/src/core/worker/WorkerClient.ts +++ b/src/core/worker/WorkerClient.ts @@ -1,5 +1,6 @@ -import { Cell, Game, GameMap, Tile } from "../game/Game"; +import { Cell, Game, GameMap, TerrainTile, TerrainType, Tile } from "../game/Game"; import { AStar, PathFindResultType } from "../pathfinding/AStar"; +import { MiniAStar } from "../pathfinding/MiniAStar"; export class WorkerClient { @@ -34,17 +35,18 @@ export class WorkerClient { }); } - createParallelAStar(src: Tile, dst: Tile, numTicks: number): ParallelAStar { + createParallelAStar(src: Tile, dst: Tile, numTicks: number, types: TerrainType[]): ParallelAStar { if (!this.isInitialized) { throw new Error('PathFinder not initialized'); } - return new ParallelAStar(this.game, this.worker, src, dst, numTicks); + return new ParallelAStar(this.game, this.worker, src, dst, numTicks, types); } cleanup() { this.worker.terminate(); } } + export class ParallelAStar implements AStar { private path: Cell[] | 'NOT_FOUND' | null = null; private promise: Promise; @@ -54,7 +56,8 @@ export class ParallelAStar implements AStar { private worker: Worker, private src: Tile, private dst: Tile, - private numTicks: number + private numTicks: number, + private terrainTypes: TerrainType[] ) { } findPath(): Promise { @@ -85,6 +88,7 @@ export class ParallelAStar implements AStar { this.worker.postMessage({ type: 'findPath', requestId: requestId, + terrainTypes: this.terrainTypes, currentTick: this.game.ticks(), duration: this.numTicks, start: { x: this.src.cell().x, y: this.src.cell().y }, @@ -108,7 +112,36 @@ export class ParallelAStar implements AStar { if (this.path != null) { return PathFindResultType.Completed; } - throw new Error(`path not completed in time`); + // Path was not found in worker thread in time, so now we need + // to recompute it in main thread. This will lock up game. + console.warn(`path not completed in worker thread, recomputing`) + const local = new MiniAStar( + this.game.terrainMap(), + this.game.terrainMiniMap(), + this.src, this.dst, + (t: TerrainTile) => t.terrainType() == TerrainType.Ocean, + 100_000_000, + 20 + ) + const result = local.compute() + switch (result) { + case PathFindResultType.Completed: + console.log('recomputed path in worker client') + this.path = local.reconstructPath() + break + case PathFindResultType.PathNotFound: + this.path = "NOT_FOUND" + break + case PathFindResultType.Pending: + // TODO: make sure same number of tries as worker thread. + console.warn("path not found after many tries") + this.path = "NOT_FOUND" + break + } + if (result == PathFindResultType.Completed) { + this.path = local.reconstructPath() + } + return result } return PathFindResultType.Pending; } @@ -121,4 +154,3 @@ export class ParallelAStar implements AStar { } } -