perf(simulation): ⚡ Optimize Web Worker simulation logic for reduced computation overhead and improved thread handling in "Age of Dwarves" game
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
e3aae475a4
commit
5e15021933
1 changed files with 48 additions and 13 deletions
|
|
@ -8,6 +8,8 @@ import type {
|
|||
WorkerResponse,
|
||||
FramePayload,
|
||||
EasterEggSeed,
|
||||
EventCatalog,
|
||||
CrossTriggers,
|
||||
} from '@magic-civ/engine-ts'
|
||||
import {
|
||||
SCENARIOS,
|
||||
|
|
@ -18,13 +20,13 @@ import {
|
|||
STARTING_ATMOSPHERES,
|
||||
deriveSubstrates,
|
||||
classifyBiome,
|
||||
} from '@magic-civ/engine-ts'
|
||||
import {
|
||||
WasmClimatePhysics,
|
||||
WasmEcologyPhysics,
|
||||
WasmGrid,
|
||||
WasmMapGenerator,
|
||||
} from '@magic-civ/physics-rs'
|
||||
EventEvaluator,
|
||||
DEFAULT_DT,
|
||||
} from '@magic-civ/engine-ts'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Worker-internal scenario state
|
||||
|
|
@ -40,10 +42,12 @@ interface ScenarioState {
|
|||
wasmGrid: WasmGrid
|
||||
physics: WasmClimatePhysics
|
||||
ecology: WasmEcologyPhysics | null
|
||||
eventEvaluator: EventEvaluator | null
|
||||
cursorScenarioTurn: number
|
||||
totalScenarioTurns: number
|
||||
worldSeed: number
|
||||
isVolcanicWinter: boolean
|
||||
dt: number
|
||||
}
|
||||
|
||||
const CHUNK_SIZE = 5
|
||||
|
|
@ -72,9 +76,10 @@ function wasmPhysicsStep(
|
|||
wasmGrid: WasmGrid,
|
||||
turn: number,
|
||||
seed: number,
|
||||
dt: number,
|
||||
): void {
|
||||
physics.processStep(wasmGrid, turn, seed)
|
||||
if (ecology) ecology.processStep(wasmGrid)
|
||||
physics.processStep(wasmGrid, turn, seed, dt)
|
||||
if (ecology) ecology.processStep(wasmGrid, dt)
|
||||
physics.stepAtmosphericChemistry(wasmGrid)
|
||||
}
|
||||
|
||||
|
|
@ -144,14 +149,29 @@ function stepOneTurn(state: ScenarioState, scenarioTurn: number, specJson: strin
|
|||
state.wasmGrid.free()
|
||||
state.wasmGrid = jsGridToWasm(state.grid)
|
||||
}
|
||||
wasmPhysicsStep(state.physics, state.ecology, state.wasmGrid, absTurn, state.worldSeed)
|
||||
wasmPhysicsStep(state.physics, state.ecology, state.wasmGrid, absTurn, state.worldSeed, state.dt)
|
||||
// Compute stats in Rust — avoids full 960-tile JSON deserialization every turn.
|
||||
const prevStats = state.stats.length > 0 ? state.stats[state.stats.length - 1] : undefined
|
||||
const stats: TurnStats = JSON.parse(
|
||||
state.wasmGrid.computeStatsJson(prevStats?.avg_temp ?? 0.5, prevStats?.avg_moisture ?? 0.5)
|
||||
)
|
||||
state.cursorScenarioTurn = scenarioTurn + 1
|
||||
return { stats, events: [] }
|
||||
|
||||
// Fire stochastic events — only deserialize JS grid when events would fire or
|
||||
// there are active multi-turn effects already running.
|
||||
let events: EcologicalEvent[] = []
|
||||
const evaluator = state.eventEvaluator
|
||||
if (evaluator !== null && (evaluator.checkWillFire(absTurn, state.dt) || evaluator.hasActiveEffects())) {
|
||||
state.grid = wasmGridToJs(state.wasmGrid)
|
||||
events = evaluator.evaluateTurn(state.grid, absTurn, state.dt)
|
||||
if (events.length > 0) {
|
||||
// Sync any event-driven grid mutations back to WASM
|
||||
state.wasmGrid.free()
|
||||
state.wasmGrid = jsGridToWasm(state.grid)
|
||||
}
|
||||
}
|
||||
|
||||
return { stats, events }
|
||||
}
|
||||
|
||||
function addCheckpoint(state: ScenarioState, turn: number): void {
|
||||
|
|
@ -187,7 +207,7 @@ function seekToTurn(state: ScenarioState, targetSt: number): void {
|
|||
state.wasmGrid.free()
|
||||
state.wasmGrid = jsGridToWasm(state.grid)
|
||||
}
|
||||
wasmPhysicsStep(state.physics, state.ecology, state.wasmGrid, absTurn, state.worldSeed)
|
||||
wasmPhysicsStep(state.physics, state.ecology, state.wasmGrid, absTurn, state.worldSeed, state.dt)
|
||||
}
|
||||
state.grid = wasmGridToJs(state.wasmGrid)
|
||||
state.cursorScenarioTurn = targetSt
|
||||
|
|
@ -219,7 +239,7 @@ function seekToTurn(state: ScenarioState, targetSt: number): void {
|
|||
state.wasmGrid.free()
|
||||
state.wasmGrid = jsGridToWasm(state.grid)
|
||||
}
|
||||
wasmPhysicsStep(state.physics, state.ecology, state.wasmGrid, absTurn, state.worldSeed)
|
||||
wasmPhysicsStep(state.physics, state.ecology, state.wasmGrid, absTurn, state.worldSeed, state.dt)
|
||||
}
|
||||
state.grid = wasmGridToJs(state.wasmGrid)
|
||||
state.cursorScenarioTurn = targetSt
|
||||
|
|
@ -263,7 +283,7 @@ async function prebufferFrames(state: ScenarioState, scenarioId: string, frameCo
|
|||
tmpWasmGrid.free()
|
||||
tmpWasmGrid = jsGridToWasm(tmpGrid)
|
||||
}
|
||||
wasmPhysicsStep(tmpPhysics, tmpEcology, tmpWasmGrid, absTurn, state.worldSeed)
|
||||
wasmPhysicsStep(tmpPhysics, tmpEcology, tmpWasmGrid, absTurn, state.worldSeed, state.dt)
|
||||
}
|
||||
|
||||
// Use writeFrameBuffers for the texture data (hot path)
|
||||
|
|
@ -370,6 +390,7 @@ async function handleRun(scenarioId: string, turns: number, prebufferFrameCount:
|
|||
const worldAge = config.worldAge
|
||||
const totalAbsTurns = worldAge + turns
|
||||
const isVolcanicWinter = config.id === 'volcanic_winter'
|
||||
const dt = config.dt ?? DEFAULT_DT
|
||||
|
||||
// Planet-specific params override earth defaults when available
|
||||
const planet = config.planet ?? 'earth'
|
||||
|
|
@ -436,7 +457,7 @@ async function handleRun(scenarioId: string, turns: number, prebufferFrameCount:
|
|||
if (geoEcology) geoEcology.free()
|
||||
return
|
||||
}
|
||||
wasmPhysicsStep(geoPhysics, geoEcology, geoWasmGrid, turn, seed)
|
||||
wasmPhysicsStep(geoPhysics, geoEcology, geoWasmGrid, turn, seed, dt)
|
||||
|
||||
if (turn % CHUNK_SIZE === 0) {
|
||||
post({ type: 'progress', scenarioId, turn, total: totalAbsTurns, phase: 'geology' })
|
||||
|
|
@ -468,15 +489,29 @@ async function handleRun(scenarioId: string, turns: number, prebufferFrameCount:
|
|||
const physics = new WasmClimatePhysics(effectiveParamsJson, terrainJson, baseSpecJson)
|
||||
const ecology = config.abioticWorld ? null : new WasmEcologyPhysics()
|
||||
const wasmGrid = jsGridToWasm(grid)
|
||||
|
||||
// Build event evaluator from ecological_events catalog loaded at init
|
||||
const ecologicalEvents = baseSpec.ecological_events as Record<string, unknown> | undefined
|
||||
let eventEvaluator: EventEvaluator | null = null
|
||||
if (ecologicalEvents) {
|
||||
const { cross_triggers: crossTriggersRaw, ...eventCategories } = ecologicalEvents
|
||||
eventEvaluator = new EventEvaluator(
|
||||
eventCategories as EventCatalog,
|
||||
(crossTriggersRaw ?? {}) as CrossTriggers,
|
||||
seed,
|
||||
)
|
||||
}
|
||||
|
||||
const state: ScenarioState = {
|
||||
config, grid, wasmGrid, physics,
|
||||
ecology,
|
||||
ecology, eventEvaluator,
|
||||
stats: [], events: [], timings: [],
|
||||
checkpoints: new Map(),
|
||||
cursorScenarioTurn: 0,
|
||||
totalScenarioTurns: turns,
|
||||
worldSeed: seed,
|
||||
isVolcanicWinter,
|
||||
dt,
|
||||
}
|
||||
|
||||
addCheckpoint(state, 0)
|
||||
|
|
@ -592,7 +627,7 @@ function handleFrame(scenarioId: string, turn: number, lookahead: number): void
|
|||
tmpWasmGrid.free()
|
||||
tmpWasmGrid = jsGridToWasm(tmpGrid)
|
||||
}
|
||||
wasmPhysicsStep(tmpPhysics, tmpEcology, tmpWasmGrid, absTurn, state.worldSeed)
|
||||
wasmPhysicsStep(tmpPhysics, tmpEcology, tmpWasmGrid, absTurn, state.worldSeed, state.dt)
|
||||
}
|
||||
|
||||
// Use writeFrameBuffers for texture data
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue