@@ -297,4 +297,88 @@ public int canBuildHere() {
297297 }
298298 return buildableCount ;
299299 }
300+
301+ /**
302+ * Separate state for worst-case late-game visibility churn:
303+ * - 700 active visible units distributed across IDs 0..3000 (simulating a 25-minute macro game
304+ * where ~2,300 units have died and new units have IDs > 127 outside Integer.valueOf cache,
305+ * and peak unit count previously reached ~1,200).
306+ * - 40 units at the fog-of-war boundary toggling unitHide / unitShow on a frame, followed by onFrame(frame).
307+ */
308+ @ State (Scope .Thread )
309+ public static class LateGameVisibilityState {
310+ Game game ;
311+ ClientData .GameData gameData ;
312+ int [] fogBoundaryIds ;
313+ int frame ;
314+
315+ @ Setup (Level .Trial )
316+ public void setup () throws IOException {
317+ game = GameBuilder .createGame (MAP_NAME );
318+ gameData = game .botClientData ().gameData ();
319+
320+ int selfId = game .self ().getID ();
321+ int enemyId = game .enemy ().getID ();
322+
323+ // Simulate peak of 1,200 units earlier in the game so the collection has experienced realistic growth
324+ for (int id = 0 ; id < 1200 ; id ++) {
325+ ClientData .UnitData ud = gameData .getUnits (id );
326+ ud .setId (id );
327+ ud .setPlayer (id % 2 == 0 ? selfId : enemyId );
328+ ud .setType (UnitType .Terran_Marine .id );
329+ ud .setPositionX (1000 );
330+ ud .setPositionY (1000 );
331+ ud .setHitPoints (40 );
332+ ud .setExists (true );
333+ ud .setIsCompleted (true );
334+ game .unitShow (id );
335+ }
336+ for (int id = 0 ; id < 1200 ; id ++) {
337+ game .unitHide (id );
338+ }
339+
340+ // Now populate 700 currently visible units scattered across IDs 0..2800 (step 4)
341+ fogBoundaryIds = new int [40 ];
342+ int count = 0 ;
343+ for (int id = 0 ; count < 700 ; id += 4 ) {
344+ ClientData .UnitData ud = gameData .getUnits (id );
345+ ud .setId (id );
346+ ud .setPlayer (count < 350 ? selfId : enemyId );
347+ ud .setType (UnitType .Terran_Marine .id );
348+ ud .setPositionX (1024 + (count & 63 ) * 8 );
349+ ud .setPositionY (1024 + (count >> 6 ) * 8 );
350+ ud .setHitPoints (40 );
351+ ud .setExists (true );
352+ ud .setIsCompleted (true );
353+ game .unitShow (id );
354+ if (count >= 660 ) {
355+ fogBoundaryIds [count - 660 ] = id ;
356+ }
357+ count ++;
358+ }
359+
360+ frame = 1 ;
361+ gameData .setFrameCount (frame );
362+ game .onFrame (frame );
363+ System .gc ();
364+ }
365+ }
366+
367+ /**
368+ * Benchmarks worst-case late-game visibility churn + onFrame rebuild of allUnits
369+ * (40 fog-of-war unitHide/unitShow events with IDs > 2600 + onFrame over 700 visible units).
370+ */
371+ @ Benchmark
372+ public int visibleUnitsWorstCaseOnFrame (LateGameVisibilityState s ) {
373+ Game g = s .game ;
374+ int [] boundary = s .fogBoundaryIds ;
375+ for (int i = 0 ; i < boundary .length ; i ++) {
376+ int id = boundary [i ];
377+ g .unitHide (id );
378+ g .unitShow (id );
379+ }
380+ int f = ++s .frame ;
381+ g .onFrame (f );
382+ return g .getAllUnits ().size ();
383+ }
300384}
0 commit comments