mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 15:40:22 +00:00
Perf clusters (#3127)
## Description: This PR reduces server/client tick CPU spent on territory cluster maintenance by: - Cutting redundant work in `PlayerExecution.removeClusters()` (largest-cluster bounding box reuse, fewer allocations in `isSurrounded()` and `removeCluster()`). - Making `calculateBoundingBox()` allocation-free per tile by switching from `gm.cell(tile)` to `gm.x(tile)`/`gm.y(tile)` (it now only allocates the two result `Cell`s, instead of 1 per tile). ## Commits - `51de0a1b` core: reduce PlayerExecution cluster overhead - `6d9d85c5` core: avoid Cell allocations in PlayerExecution isSurrounded - `346f6a8c` core(util): speed up calculateBoundingBox by avoiding Cell allocations ## Notes - This PR is intended to be behavior-preserving; changes are limited to hot-path micro-optimizations. - Follow-up opportunity: `calculateClusters`/flood-fill is now the top hotspot; further wins likely come from reducing traversal work or caching. ## Please complete the following: - [ ] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [ ] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: DISCORD_USERNAME
This commit is contained in:
+8
-7
@@ -80,13 +80,14 @@ export function calculateBoundingBox(
|
||||
maxX = -Infinity,
|
||||
maxY = -Infinity;
|
||||
|
||||
borderTiles.forEach((tile: TileRef) => {
|
||||
const cell = gm.cell(tile);
|
||||
minX = Math.min(minX, cell.x);
|
||||
minY = Math.min(minY, cell.y);
|
||||
maxX = Math.max(maxX, cell.x);
|
||||
maxY = Math.max(maxY, cell.y);
|
||||
});
|
||||
for (const tile of borderTiles) {
|
||||
const x = gm.x(tile);
|
||||
const y = gm.y(tile);
|
||||
minX = Math.min(minX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x);
|
||||
maxY = Math.max(maxY, y);
|
||||
}
|
||||
|
||||
return { min: new Cell(minX, minY), max: new Cell(maxX, maxY) };
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Config } from "../configuration/Config";
|
||||
import { Execution, Game, Player, UnitType } from "../game/Game";
|
||||
import { Cell, Execution, Game, Player, UnitType } from "../game/Game";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
import { calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util";
|
||||
|
||||
@@ -139,11 +139,12 @@ export class PlayerExecution implements Execution {
|
||||
const largestCluster = clusters[largestIndex];
|
||||
if (largestCluster === undefined) throw new Error("No clusters");
|
||||
|
||||
this.player.largestClusterBoundingBox = calculateBoundingBox(
|
||||
this.mg,
|
||||
const largestClusterBox = calculateBoundingBox(this.mg, largestCluster);
|
||||
this.player.largestClusterBoundingBox = largestClusterBox;
|
||||
const surroundedBy = this.surroundedBySamePlayer(
|
||||
largestCluster,
|
||||
largestClusterBox,
|
||||
);
|
||||
const surroundedBy = this.surroundedBySamePlayer(largestCluster);
|
||||
if (surroundedBy && !surroundedBy.isFriendly(this.player)) {
|
||||
this.removeCluster(largestCluster);
|
||||
}
|
||||
@@ -158,7 +159,10 @@ export class PlayerExecution implements Execution {
|
||||
}
|
||||
}
|
||||
|
||||
private surroundedBySamePlayer(cluster: Set<TileRef>): false | Player {
|
||||
private surroundedBySamePlayer(
|
||||
cluster: Set<TileRef>,
|
||||
clusterBox: { min: Cell; max: Cell },
|
||||
): false | Player {
|
||||
const enemies = new Set<number>();
|
||||
for (const tile of cluster) {
|
||||
let hasUnownedNeighbor = false;
|
||||
@@ -187,7 +191,6 @@ export class PlayerExecution implements Execution {
|
||||
}
|
||||
const enemy = this.mg.playerBySmallID(Array.from(enemies)[0]) as Player;
|
||||
const enemyBox = calculateBoundingBox(this.mg, enemy.borderTiles());
|
||||
const clusterBox = calculateBoundingBox(this.mg, cluster);
|
||||
if (inscribed(enemyBox, clusterBox)) {
|
||||
return enemy;
|
||||
}
|
||||
@@ -195,7 +198,11 @@ export class PlayerExecution implements Execution {
|
||||
}
|
||||
|
||||
private isSurrounded(cluster: Set<TileRef>): boolean {
|
||||
const enemyTiles = new Set<TileRef>();
|
||||
let hasEnemy = false;
|
||||
let minX = Infinity,
|
||||
minY = Infinity,
|
||||
maxX = -Infinity,
|
||||
maxY = -Infinity;
|
||||
for (const tr of cluster) {
|
||||
if (this.mg.isShore(tr) || this.mg.isOnEdgeOfMap(tr)) {
|
||||
return false;
|
||||
@@ -203,27 +210,31 @@ export class PlayerExecution implements Execution {
|
||||
this.mg.forEachNeighbor(tr, (n) => {
|
||||
const owner = this.mg.owner(n);
|
||||
if (owner.isPlayer() && this.mg.ownerID(n) !== this.player.smallID()) {
|
||||
enemyTiles.add(n);
|
||||
hasEnemy = true;
|
||||
const x = this.mg.x(n);
|
||||
const y = this.mg.y(n);
|
||||
minX = Math.min(minX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x);
|
||||
maxY = Math.max(maxY, y);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (enemyTiles.size === 0) {
|
||||
if (!hasEnemy) {
|
||||
return false;
|
||||
}
|
||||
const enemyBox = calculateBoundingBox(this.mg, enemyTiles);
|
||||
const clusterBox = calculateBoundingBox(this.mg, cluster);
|
||||
const enemyBox = { min: new Cell(minX, minY), max: new Cell(maxX, maxY) };
|
||||
return inscribed(enemyBox, clusterBox);
|
||||
}
|
||||
|
||||
private removeCluster(cluster: Set<TileRef>) {
|
||||
if (
|
||||
Array.from(cluster).some(
|
||||
(t) => this.mg?.ownerID(t) !== this.player?.smallID(),
|
||||
)
|
||||
) {
|
||||
// Other removeCluster operations could change tile owners,
|
||||
// so double check.
|
||||
return;
|
||||
for (const t of cluster) {
|
||||
if (this.mg?.ownerID(t) !== this.player?.smallID()) {
|
||||
// Other removeCluster operations could change tile owners,
|
||||
// so double check.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const capturing = this.getCapturingPlayer(cluster);
|
||||
|
||||
Reference in New Issue
Block a user