From 8616e9bfcbb101c3dc65e0d9f67254539796d46d Mon Sep 17 00:00:00 2001 From: Evan Date: Thu, 2 Jan 2025 20:22:11 -0800 Subject: [PATCH] game runs in seperate thread --- src/client/ClientGameRunner.ts | 11 +- src/client/graphics/layers/EventsDisplay.ts | 25 +--- src/core/GameRunner.ts | 59 +++++++-- src/core/GameView.ts | 4 +- src/core/execution/AttackExecution.ts | 2 +- src/core/execution/FakeHumanExecution.ts | 2 - src/core/execution/PortExecution.ts | 1 - src/core/execution/TradeShipExecution.ts | 2 +- src/core/execution/TransportShipExecution.ts | 3 +- src/core/game/Game.ts | 23 +++- src/core/game/GameImpl.ts | 3 +- src/core/game/PlayerImpl.ts | 8 +- src/core/game/TileImpl.ts | 4 +- src/core/game/UnitImpl.ts | 6 +- src/core/pathfinding/PathFinding.ts | 1 - src/core/worker/Worker.worker.ts | 113 +++------------- src/core/worker/WorkerClient.ts | 131 +++---------------- 17 files changed, 135 insertions(+), 263 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 77f420896..d14b112bf 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -10,10 +10,12 @@ import { and, bfs, dist, generateID, manhattanDist } from "../core/Util"; import { WinCheckExecution } from "../core/execution/WinCheckExecution"; import { SendAttackIntentEvent, SendSpawnIntentEvent, Transport } from "./Transport"; import { createCanvas } from "./Utils"; -import { DisplayMessageEvent, MessageType } from "./graphics/layers/EventsDisplay"; +import { MessageType } from '../core/game/Game'; +import { DisplayMessageEvent } from '../core/game/Game'; import { WorkerClient } from "../core/worker/WorkerClient"; import { consolex, initRemoteSender } from "../core/Consolex"; import { getConfig, getServerConfig } from "../core/configuration/Config"; +import { GameUpdateViewData } from "../core/GameView"; export interface LobbyConfig { playerName: () => string @@ -75,6 +77,10 @@ export async function createClientGame(lobbyConfig: LobbyConfig, gameConfig: Gam const terrainMap = await loadTerrainMap(gameConfig.gameMap); let game = createGame(terrainMap.map, terrainMap.miniMap, eventBus, config) + const worker = new WorkerClient(lobbyConfig.gameID, gameConfig) + await worker.initialize((gu: GameUpdateViewData) => { + console.log('got update!') + }) consolex.log('going to init path finder') consolex.log('inited path finder') @@ -92,6 +98,7 @@ export async function createClientGame(lobbyConfig: LobbyConfig, gameConfig: Gam new InputHandler(canvas, eventBus), new Executor(game, lobbyConfig.gameID), transport, + worker, ) } @@ -115,6 +122,7 @@ export class ClientGameRunner { private input: InputHandler, private executor: Executor, private transport: Transport, + private worker: WorkerClient ) { } public start() { @@ -175,6 +183,7 @@ export class ClientGameRunner { return } this.isProcessingTurn = true + this.worker.sendTurn(this.turns[this.currTurn]) this.gs.addExecution(...this.executor.createExecs(this.turns[this.currTurn])) try { const start = performance.now() diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index 0f84d9588..e598fc03f 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -1,17 +1,15 @@ import { LitElement, html, css } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { EventBus, GameEvent } from "../../../core/EventBus"; +import { EventBus } from "../../../core/EventBus"; import { AllianceExpiredEvent, AllianceRequestEvent, AllianceRequestReplyEvent, AllPlayers, - BrokeAllianceEvent, - EmojiMessageEvent, + BrokeAllianceEvent, DisplayMessageEvent, EmojiMessageEvent, Game, - Player, - PlayerID, - TargetPlayerEvent, + MessageType, + Player, TargetPlayerEvent, UnitEvent } from "../../../core/game/Game"; import { ClientID } from "../../../core/Schemas"; @@ -20,21 +18,6 @@ import { SendAllianceReplyIntentEvent } from "../../Transport"; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { onlyImages, sanitize } from '../../../core/Util'; -export enum MessageType { - SUCCESS, - INFO, - WARN, - ERROR, -} - -export class DisplayMessageEvent implements GameEvent { - constructor( - public readonly message: string, - public readonly type: MessageType, - public readonly playerID: PlayerID | null = null - ) { } -} - interface Event { description: string; unsafeDescription?: boolean diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index 1217cf772..8a114c487 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -1,31 +1,74 @@ import { getConfig } from "./configuration/Config"; import { EventBus } from "./EventBus"; import { Executor } from "./execution/ExecutionManager"; -import { Game, Tile, TileEvent } from "./game/Game"; +import { WinCheckExecution } from "./execution/WinCheckExecution"; +import { Game, MutableGame, MutableTile, Tile, TileEvent } from "./game/Game"; import { createGame } from "./game/GameImpl"; import { loadTerrainMap } from "./game/TerrainMapLoader"; import { GameUpdateViewData } from "./GameView"; import { GameConfig, Turn } from "./Schemas"; -export async function createGameRunner(gameID: string, gameConfig: GameConfig): Promise { +export async function createGameRunner(gameID: string, gameConfig: GameConfig, callBack: (gu: GameUpdateViewData) => void): Promise { const config = getConfig(gameConfig) const terrainMap = await loadTerrainMap(gameConfig.gameMap); const eventBus = new EventBus() const game = createGame(terrainMap.map, terrainMap.miniMap, eventBus, config) - return new GameRunner(game, eventBus, new Executor(game, gameID)) + const gr = new GameRunner(game as MutableGame, eventBus, new Executor(game, gameID), callBack) + gr.init() + return gr } export class GameRunner { - private updatedTiles: Tile[] + private updatedTiles: MutableTile[] + private tickInterval = null + private turns: Turn[] = [] + private currTurn = 0 + private isExecuting = false - constructor(private game: Game, private eventBus: EventBus, private execManager: Executor) { - eventBus.on(TileEvent, (e) => { this.updatedTiles.push(e.tile) }) + constructor( + private game: MutableGame, + private eventBus: EventBus, + private execManager: Executor, + private callBack: (gu: GameUpdateViewData) => void + ) { } - public executeNextTick(turn: Turn): GameUpdateViewData { + init() { + this.eventBus.on(TileEvent, (e) => { this.updatedTiles.push(e.tile as MutableTile) }) + this.game.addExecution(...this.execManager.spawnBots(this.game.config().numBots())) + if (this.game.config().spawnNPCs()) { + this.game.addExecution(...this.execManager.fakeHumanExecutions()) + } + this.game.addExecution(new WinCheckExecution(this.eventBus)) + this.tickInterval = setInterval(() => this.executeNextTick(), 10) + } + + public addTurn(turn: Turn): void { + this.turns.push(turn) + } + + public executeNextTick() { + if (this.isExecuting) { + return + } + if (this.currTurn >= this.turns.length) { + return + } + this.isExecuting = true this.updatedTiles = [] + + + this.game.addExecution(...this.execManager.createExecs(this.turns[this.currTurn])) + this.currTurn++ this.game.executeNextTick() - return null + + this.callBack({ + units: this.game.units().map(u => u.toViewData()), + tileUpdates: this.updatedTiles.map(t => t.toViewData()), + players: this.game.players().map(p => p.toViewData()) + }) + + this.isExecuting = false } } \ No newline at end of file diff --git a/src/core/GameView.ts b/src/core/GameView.ts index 9cf4f3fcb..7a8db599a 100644 --- a/src/core/GameView.ts +++ b/src/core/GameView.ts @@ -1,10 +1,10 @@ -import { MessageType } from "../client/graphics/layers/EventsDisplay"; +import { MessageType } from './game/Game'; import { Config } from "./configuration/Config"; import { Alliance, AllianceRequest, AllPlayers, Cell, DefenseBonus, EmojiMessage, Execution, ExecutionView, Game, Gold, MutableTile, Nation, Player, PlayerID, PlayerInfo, PlayerType, Relation, TerrainMap, TerrainTile, TerrainType, TerraNullius, Tick, Tile, Unit, UnitInfo, UnitType } from "./game/Game"; import { ClientID } from "./Schemas"; export interface ViewSerializable { - toViewData(): ViewData; + toViewData(): T; } export interface ViewData { diff --git a/src/core/execution/AttackExecution.ts b/src/core/execution/AttackExecution.ts index e509d4c64..07ff5c9ee 100644 --- a/src/core/execution/AttackExecution.ts +++ b/src/core/execution/AttackExecution.ts @@ -2,7 +2,7 @@ import { PriorityQueue } from "@datastructures-js/priority-queue"; import { Cell, Execution, MutableGame, MutablePlayer, Player, PlayerID, PlayerType, TerrainType, TerraNullius, Tile } from "../game/Game"; import { PseudoRandom } from "../PseudoRandom"; import { manhattanDist } from "../Util"; -import { MessageType } from "../../client/graphics/layers/EventsDisplay"; +import { MessageType } from '../game/Game'; import { renderNumber } from "../../client/Utils"; export class AttackExecution implements Execution { diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index 61028db7f..a1c88462a 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -5,8 +5,6 @@ import { AttackExecution } from "./AttackExecution"; import { TransportShipExecution } from "./TransportShipExecution"; import { SpawnExecution } from "./SpawnExecution"; import { PortExecution } from "./PortExecution"; -import { ParallelAStar, WorkerClient } from "../worker/WorkerClient"; -import { PathFinder } from "../pathfinding/PathFinding"; import { DestroyerExecution } from "./DestroyerExecution"; import { BattleshipExecution } from "./BattleshipExecution"; import { GameID } from "../Schemas"; diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index 7bfa6e716..6f1857954 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -5,7 +5,6 @@ import { SerialAStar } from "../pathfinding/SerialAStar"; import { PseudoRandom } from "../PseudoRandom"; import { bfs, dist, manhattanDist } from "../Util"; import { TradeShipExecution } from "./TradeShipExecution"; -import { ParallelAStar, WorkerClient } from "../worker/WorkerClient"; import { consolex } from "../Consolex"; import { MiniAStar } from "../pathfinding/MiniAStar"; diff --git a/src/core/execution/TradeShipExecution.ts b/src/core/execution/TradeShipExecution.ts index 962a4893c..57aa0a2a9 100644 --- a/src/core/execution/TradeShipExecution.ts +++ b/src/core/execution/TradeShipExecution.ts @@ -1,4 +1,4 @@ -import { MessageType } from "../../client/graphics/layers/EventsDisplay"; +import { MessageType } from '../game/Game'; import { renderNumber } from "../../client/Utils"; import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game"; import { PathFinder } from "../pathfinding/PathFinding"; diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts index 823bb0a0a..e9e14439f 100644 --- a/src/core/execution/TransportShipExecution.ts +++ b/src/core/execution/TransportShipExecution.ts @@ -1,7 +1,8 @@ import { Unit, Cell, Execution, MutableUnit, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, Tile, TileEvent, UnitType, TerrainType } from "../game/Game"; import { and, bfs, manhattanDistWrapped, sourceDstOceanShore, targetTransportTile } from "../Util"; import { AttackExecution } from "./AttackExecution"; -import { DisplayMessageEvent, MessageType } from "../../client/graphics/layers/EventsDisplay"; +import { MessageType } from '../game/Game'; +import { DisplayMessageEvent } from '../game/Game'; import { PathFinder } from "../pathfinding/PathFinding"; import { PathFindResultType } from "../pathfinding/AStar"; import { SerialAStar } from "../pathfinding/SerialAStar"; diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 53b750daf..4632f6350 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -1,8 +1,8 @@ import { Config } from "../configuration/Config" import { GameEvent } from "../EventBus" import { ClientID, GameConfig, GameID } from "../Schemas" -import { MessageType } from "../../client/graphics/layers/EventsDisplay" import { SearchNode } from "../pathfinding/AStar" +import { PlayerViewData, TileViewData, UnitViewData, ViewSerializable } from "../GameView" export type PlayerID = string export type Tick = number @@ -191,7 +191,7 @@ export interface Tile extends SearchNode { hasDefenseBonus(): boolean } -export interface MutableTile extends Tile { +export interface MutableTile extends Tile, ViewSerializable { // defense bonus against this player defenseBonus(player: Player): number borders(other: Player | TerraNullius): boolean @@ -209,7 +209,7 @@ export interface Unit { health(): number } -export interface MutableUnit extends Unit { +export interface MutableUnit extends Unit, ViewSerializable { move(tile: Tile): void owner(): MutablePlayer setTroops(troops: number): void @@ -271,7 +271,7 @@ export interface Player { lastTileChange(): Tick } -export interface MutablePlayer extends Player { +export interface MutablePlayer extends Player, ViewSerializable { // Targets for this player targets(): Player[] // Targets of player and all allies. @@ -390,3 +390,18 @@ export class EmojiMessageEvent implements GameEvent { constructor(public readonly message: EmojiMessage) { } } +export class DisplayMessageEvent implements GameEvent { + constructor( + public readonly message: string, + public readonly type: MessageType, + public readonly playerID: PlayerID | null = null + ) { } +} + +export enum MessageType { + SUCCESS, + INFO, + WARN, + ERROR +} + diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 7d89b4407..a155f774b 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -9,7 +9,8 @@ import { TileImpl } from "./TileImpl"; import { AllianceRequestImpl } from "./AllianceRequestImpl"; import { AllianceImpl } from "./AllianceImpl"; import { ClientID, GameConfig } from "../Schemas"; -import { DisplayMessageEvent, MessageType } from "../../client/graphics/layers/EventsDisplay"; +import { MessageType } from './Game'; +import { DisplayMessageEvent } from './Game'; import { UnitImpl } from "./UnitImpl"; import { consolex } from "../Consolex"; diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 68795a7a3..46549c782 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -4,7 +4,7 @@ import { assertNever, bfs, closestOceanShoreFromPlayer, dist, distSortUnit, manh import { CellString, GameImpl } from "./GameImpl"; import { UnitImpl } from "./UnitImpl"; import { TileImpl } from "./TileImpl"; -import { MessageType } from "../../client/graphics/layers/EventsDisplay"; +import { MessageType } from './Game'; import { renderTroops } from "../../client/Utils"; import { PlayerViewData, ViewData, ViewSerializable } from "../GameView"; @@ -17,7 +17,7 @@ class Donation { constructor(public readonly recipient: Player, public readonly tick: Tick) { } } -export class PlayerImpl implements MutablePlayer, ViewSerializable { +export class PlayerImpl implements MutablePlayer { public _lastTileChange: number = 0 @@ -52,10 +52,10 @@ export class PlayerImpl implements MutablePlayer, ViewSerializable { + toViewData(): PlayerViewData { return { clientID: this.clientID(), name: this.name(), diff --git a/src/core/game/TileImpl.ts b/src/core/game/TileImpl.ts index e57f92aaa..e883703d2 100644 --- a/src/core/game/TileImpl.ts +++ b/src/core/game/TileImpl.ts @@ -7,7 +7,7 @@ import { TerraNulliusImpl } from "./TerraNulliusImpl"; import { TileView, TileViewData, ViewData, ViewSerializable } from "../GameView"; -export class TileImpl implements MutableTile, ViewSerializable { +export class TileImpl implements MutableTile { public _isBorder = false; private _neighbors: Tile[] = null; @@ -23,7 +23,7 @@ export class TileImpl implements MutableTile, ViewSerializable { private readonly _terrain: TerrainTileImpl ) { } - toViewData(): ViewData { + toViewData(): TileViewData { return { x: this._cell.x, y: this._cell.y, diff --git a/src/core/game/UnitImpl.ts b/src/core/game/UnitImpl.ts index 23262da56..faacaca18 100644 --- a/src/core/game/UnitImpl.ts +++ b/src/core/game/UnitImpl.ts @@ -1,4 +1,4 @@ -import { MessageType } from "../../client/graphics/layers/EventsDisplay"; +import { MessageType } from './Game'; import { UnitViewData, ViewData, ViewSerializable } from "../GameView"; import { simpleHash, within } from "../Util"; import { MutableUnit, Tile, TerraNullius, UnitType, Player, UnitInfo } from "./Game"; @@ -7,7 +7,7 @@ import { PlayerImpl } from "./PlayerImpl"; import { TerraNulliusImpl } from "./TerraNulliusImpl"; -export class UnitImpl implements MutableUnit, ViewSerializable { +export class UnitImpl implements MutableUnit { private _active = true; private _health: number @@ -22,7 +22,7 @@ export class UnitImpl implements MutableUnit, ViewSerializable { this._health = (this.g.unitInfo(_type).maxHealth ?? 2) / 2 } - toViewData(): ViewData { + toViewData(): UnitViewData { return { type: this._type, troops: this._troops, diff --git a/src/core/pathfinding/PathFinding.ts b/src/core/pathfinding/PathFinding.ts index a63b34feb..508f78793 100644 --- a/src/core/pathfinding/PathFinding.ts +++ b/src/core/pathfinding/PathFinding.ts @@ -1,7 +1,6 @@ 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"; import { SerialAStar } from "./SerialAStar"; import { MiniAStar } from "./MiniAStar"; import { consolex } from "../Consolex"; diff --git a/src/core/worker/Worker.worker.ts b/src/core/worker/Worker.worker.ts index ecd4a41e4..69eced1f2 100644 --- a/src/core/worker/Worker.worker.ts +++ b/src/core/worker/Worker.worker.ts @@ -1,106 +1,27 @@ -// pathfinding.ts -import { Cell, GameMap, TerrainMap, TerrainTile, TerrainType } from "../game/Game"; -import { loadTerrainMap } from "../game/TerrainMapLoader"; -import { PriorityQueue } from "@datastructures-js/priority-queue"; -import { AStar, PathFindResultType, SearchNode } from "../pathfinding/AStar"; -import { MiniAStar } from "../pathfinding/MiniAStar"; -import { consolex } from "../Consolex"; +import { createGameRunner, GameRunner } from "../GameRunner"; +import { GameUpdateViewData } from "../GameView"; -let terrainMapPromise: Promise<{ - map: 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 +let gameRunner: Promise = null -interface Search { - aStar: AStar, - deadline: number - requestId: string, - end: Cell -} -interface SearchRequest { - requestId: string - currentTick: number - // duration in ticks - duration: number - start: Cell - end: Cell +function gameUpdate(gu: GameUpdateViewData) { + self.postMessage({ + type: "game_update", + gameUpdate: gu + }) } self.onmessage = (e) => { switch (e.data.type) { case 'init': - initializeMap(e.data); - break; - case 'findPath': - terrainMapPromise.then(tm => findPath(tm.map, tm.miniMap, e.data)) + gameRunner = createGameRunner(e.data.gameID, e.data.gameConfig, gameUpdate).then(gr => { + self.postMessage({ + type: 'initialized' + }); + return gr; + }); break; + case 'turn': + gameRunner.then(gr => gr.addTurn(e.data.turn)) } -}; - -function initializeMap(data: { gameMap: GameMap }) { - terrainMapPromise = loadTerrainMap(data.gameMap) - self.postMessage({ type: 'initialized' }); - processingInterval = setInterval(computeSearches, .1) as unknown as number; -} - -function findPath(terrainMap: TerrainMap, miniTerrainMap: TerrainMap, req: SearchRequest) { - const aStar = new MiniAStar( - terrainMap, - miniTerrainMap, - terrainMap.terrain(req.start), - terrainMap.terrain(req.end), - (sn: SearchNode) => (sn as TerrainTile).type() == TerrainType.Ocean, - 10_000, - req.duration, - ); - - searches.enqueue({ - aStar: aStar, - deadline: req.currentTick + req.duration, - requestId: req.requestId, - end: req.end - }) -} - -function computeSearches() { - if (isProcessingSearch || searches.isEmpty()) { - return - } - - isProcessingSearch = true - - try { - for (let i = 0; i < 10; i++) { - if (searches.isEmpty()) { - return - } - const search = searches.dequeue() - switch (search.aStar.compute()) { - case PathFindResultType.Completed: - self.postMessage({ - type: 'pathFound', - requestId: search.requestId, - path: search.aStar.reconstructPath() - }); - break; - - case PathFindResultType.Pending: - searches.push(search) - break - case PathFindResultType.PathNotFound: - consolex.warn(`worker: path not found to port`); - self.postMessage({ - type: 'pathNotFound', - requestId: search.requestId, - }); - break - } - } - } finally { - isProcessingSearch = false - } -} +}; \ No newline at end of file diff --git a/src/core/worker/WorkerClient.ts b/src/core/worker/WorkerClient.ts index eb673b133..6684b97ec 100644 --- a/src/core/worker/WorkerClient.ts +++ b/src/core/worker/WorkerClient.ts @@ -1,7 +1,9 @@ import { consolex } from "../Consolex"; import { Cell, Game, GameMap, TerrainTile, TerrainType, Tile } from "../game/Game"; +import { GameUpdateViewData } from "../GameView"; import { AStar, PathFindResultType } from "../pathfinding/AStar"; import { MiniAStar } from "../pathfinding/MiniAStar"; +import { GameConfig, GameID, Turn } from "../Schemas"; import { generateID } from "../Util"; @@ -9,39 +11,43 @@ export class WorkerClient { private worker: Worker; private isInitialized = false; - constructor(private game: Game, private gameMap: GameMap) { + constructor(private gameID: GameID, private gameConfig: GameConfig) { // Create a new worker using webpack worker-loader // The import.meta.url ensures webpack can properly bundle the worker this.worker = new Worker(new URL('./Worker.worker.ts', import.meta.url)); } - initialize(): Promise { + initialize(gameUpdate: (gu: GameUpdateViewData) => void): Promise { return new Promise((resolve, reject) => { this.worker.postMessage({ type: 'init', - gameMap: this.gameMap + gameID: this.gameID, + gameConfig: this.gameConfig }); const handler = (e: MessageEvent) => { if (e.data.type === 'initialized') { - this.worker.removeEventListener('message', handler); this.isInitialized = true; resolve(); - } else { - this.worker.removeEventListener('message', handler); + return + } + if (!this.isInitialized) { reject('Failed to initialize pathfinder'); } + if (e.data.type == "game_update") { + gameUpdate(e.data.gameUpdate) + } }; this.worker.addEventListener('message', handler); }); } - 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, types); + sendTurn(turn: Turn) { + this.worker.postMessage({ + type: "turn", + turn: turn + }) } cleanup() { @@ -49,106 +55,3 @@ export class WorkerClient { } } -export class ParallelAStar implements AStar { - private path: Cell[] | 'NOT_FOUND' | null = null; - private promise: Promise; - - constructor( - private game: Game, - private worker: Worker, - private src: Tile, - private dst: Tile, - private numTicks: number, - private terrainTypes: TerrainType[] - ) { } - - findPath(): Promise { - const requestId = generateID() - this.promise = new Promise((resolve, reject) => { - - const handler = (e: MessageEvent) => { - if (e.data.requestId != requestId) { - return; - } - this.worker.removeEventListener('message', handler); - - if (e.data.type === 'pathFound') { - this.path = e.data.path - resolve(); - } else if (e.data.type === 'pathNotFound') { - this.path = 'NOT_FOUND'; - } else { - reject(e.data.reason || "Path not found"); - } - }; - - this.worker.addEventListener('message', handler); - 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 }, - end: { x: this.dst.cell().x, y: this.dst.cell().y } - }); - }); - - return this.promise; - } - - // TODO: rename to poll? - compute(): PathFindResultType { - if (this.promise == null) { - this.findPath(); - } - this.numTicks--; - if (this.numTicks <= 0) { - if (this.path == 'NOT_FOUND') { - return PathFindResultType.PathNotFound; - } - if (this.path != null) { - return PathFindResultType.Completed; - } - // Path was not found in worker thread in time, so now we need - // to recompute it in main thread. This will lock up game. - consolex.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.type() == TerrainType.Ocean, - 100_000_000, - 20 - ) - const result = local.compute() - switch (result) { - case PathFindResultType.Completed: - consolex.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. - consolex.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; - } - - reconstructPath(): Cell[] { - if (this.path == "NOT_FOUND" || this.path == null) { - throw Error(`cannot reconstruct path: ${this.path}`); - } - return this.path - } - -}