@@ -3,18 +3,23 @@ import { Execution, Game, Player, UnitType } from "../game/Game";
33import { GameMap , TileRef } from "../game/GameMap" ;
44import { calculateBoundingBox , getMode , inscribed , simpleHash } from "../Util" ;
55
6+ interface ClusterTraversalState {
7+ visited : Uint32Array ;
8+ gen : number ;
9+ }
10+
11+ // Per-game traversal state used by calculateClusters() to avoid per-player buffers.
12+ const traversalStates = new WeakMap < Game , ClusterTraversalState > ( ) ;
13+
614export class PlayerExecution implements Execution {
715 private readonly ticksPerClusterCalc = 20 ;
816
917 private config : Config ;
1018 private lastCalc = 0 ;
1119 private mg : Game ;
1220 private active = true ;
13- private _visitedBuffer : Uint8Array ;
1421
15- constructor ( private player : Player ) {
16- this . _visitedBuffer = new Uint8Array ( 0 ) ; // Initialize empty buffer
17- }
22+ constructor ( private player : Player ) { }
1823
1924 activeDuringSpawnPhase ( ) : boolean {
2025 return false ;
@@ -264,38 +269,47 @@ export class PlayerExecution implements Execution {
264269 const borderTiles = this . player . borderTiles ( ) ;
265270 if ( borderTiles . size === 0 ) return [ ] ;
266271
267- // Ensure buffer is large enough
268- const mapSize = this . mg . width ( ) * this . mg . height ( ) ;
269- if ( ! this . _visitedBuffer || this . _visitedBuffer . length < mapSize ) {
270- this . _visitedBuffer = new Uint8Array ( mapSize ) ;
271- } else {
272- // Fast clear (much faster than creating a new Set)
273- this . _visitedBuffer . fill ( 0 ) ;
272+ const totalTiles = this . mg . width ( ) * this . mg . height ( ) ;
273+
274+ // Retrieve or initialize traversal state for this specific Game instance.
275+ let state = traversalStates . get ( this . mg ) ;
276+ if ( ! state || state . visited . length < totalTiles ) {
277+ state = {
278+ visited : new Uint32Array ( totalTiles ) ,
279+ gen : 0 ,
280+ } ;
281+ traversalStates . set ( this . mg , state ) ;
282+ }
283+
284+ // Generational clear: bump generation instead of filling the array.
285+ state . gen ++ ;
286+ if ( state . gen === 0xffffffff ) {
287+ // Extremely rare wrap-around; reset the buffer.
288+ state . visited . fill ( 0 ) ;
289+ state . gen = 1 ;
274290 }
275291
292+ const currentGen = state . gen ;
293+ const visited = state . visited ;
294+
276295 const clusters : Set < TileRef > [ ] = [ ] ;
277- const stack : TileRef [ ] = [ ] ; // Reusable stack
296+ const stack : TileRef [ ] = [ ] ;
278297
279298 for ( const startTile of borderTiles ) {
280- // FAST: Array access instead of Set.has()
281- if ( this . _visitedBuffer [ startTile ] === 1 ) continue ;
299+ if ( visited [ startTile ] === currentGen ) continue ;
282300
283301 const currentCluster = new Set < TileRef > ( ) ;
284302 stack . push ( startTile ) ;
285- this . _visitedBuffer [ startTile ] = 1 ;
303+ visited [ startTile ] = currentGen ;
286304
287305 while ( stack . length > 0 ) {
288306 const tile = stack . pop ( ) ! ;
289307 currentCluster . add ( tile ) ;
290308
291- //Use callback to avoid creating a 'neighbors' Array
292309 this . mg . forEachNeighborWithDiag ( tile , ( neighbor ) => {
293- if (
294- borderTiles . has ( neighbor ) &&
295- this . _visitedBuffer [ neighbor ] === 0
296- ) {
310+ if ( borderTiles . has ( neighbor ) && visited [ neighbor ] !== currentGen ) {
297311 stack . push ( neighbor ) ;
298- this . _visitedBuffer [ neighbor ] = 1 ;
312+ visited [ neighbor ] = currentGen ;
299313 }
300314 } ) ;
301315 }
0 commit comments