mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:00:43 +00:00
Optimize nuke explosion (#3176)
## Description: When a nuke detonates, the explosion is looping over each tiles. Among other things, it filters the owner units to find the `TransportShips` units. This operation is cpu-intensive and repeated over each tiles can make the tick noticably slower. On this screenshot the detonation function took 100ms: <img width="1617" height="488" alt="image" src="https://github.com/user-attachments/assets/07009b18-4342-4caf-9e82-9ae5147b63f8" /> <img width="1645" height="375" alt="image" src="https://github.com/user-attachments/assets/fe9ead87-550a-4166-96ab-092d0c08be82" /> I suspect it led to a few frame freeze when I MIRVed too. Suggestion: loop over the impacted players rather than the tiles. With this suggestion, the same nuke takes between 4ms to 20ms: <img width="989" height="365" alt="image" src="https://github.com/user-attachments/assets/25c0faf0-cc34-41b7-8091-b14bde6db595" /> However this changes the death formula used, as they were repeated over each tiles with ever-smaller values, and with this change the operation is done all-at-once. This will result in a different outcome. In my opinion the performance gain is seductive enough to maybe tweak the formula to make it work with this revised strategy. What do you think? ## 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: IngloriousTom
This commit is contained in:
@@ -255,55 +255,59 @@ export class NukeExecution implements Execution {
|
||||
const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type());
|
||||
const toDestroy = this.tilesToDestroy();
|
||||
|
||||
const maxTroops = this.target().isPlayer()
|
||||
? this.mg.config().maxTroops(this.target() as Player)
|
||||
: 1;
|
||||
|
||||
// Retrieve all impacted players and the number of tiles
|
||||
const tilesPerPlayers = new Map<Player, number>();
|
||||
for (const tile of toDestroy) {
|
||||
const owner = this.mg.owner(tile);
|
||||
if (owner.isPlayer()) {
|
||||
owner.relinquish(tile);
|
||||
owner.removeTroops(
|
||||
this.mg
|
||||
.config()
|
||||
.nukeDeathFactor(
|
||||
this.nukeType,
|
||||
owner.troops(),
|
||||
owner.numTilesOwned(),
|
||||
maxTroops,
|
||||
),
|
||||
);
|
||||
owner.outgoingAttacks().forEach((attack) => {
|
||||
const deaths =
|
||||
this.mg
|
||||
?.config()
|
||||
.nukeDeathFactor(
|
||||
this.nukeType,
|
||||
attack.troops(),
|
||||
owner.numTilesOwned(),
|
||||
maxTroops,
|
||||
) ?? 0;
|
||||
attack.setTroops(attack.troops() - deaths);
|
||||
});
|
||||
owner.units(UnitType.TransportShip).forEach((attack) => {
|
||||
const deaths =
|
||||
this.mg
|
||||
?.config()
|
||||
.nukeDeathFactor(
|
||||
this.nukeType,
|
||||
attack.troops(),
|
||||
owner.numTilesOwned(),
|
||||
maxTroops,
|
||||
) ?? 0;
|
||||
attack.setTroops(attack.troops() - deaths);
|
||||
});
|
||||
const numTiles = tilesPerPlayers.get(owner);
|
||||
tilesPerPlayers.set(owner, numTiles === undefined ? 1 : numTiles + 1);
|
||||
}
|
||||
|
||||
if (this.mg.isLand(tile)) {
|
||||
this.mg.setFallout(tile, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Then compute the explosion effect on each player
|
||||
for (const [player, numImpactedTiles] of tilesPerPlayers) {
|
||||
const config = this.mg.config();
|
||||
const tilesBeforeNuke = player.numTilesOwned() + numImpactedTiles;
|
||||
const transportShips = player.units(UnitType.TransportShip);
|
||||
const maxTroops = config.maxTroops(player);
|
||||
// nukeDeathFactor could compute the complete fallout in a single call instead
|
||||
for (let i = 0; i < numImpactedTiles; i++) {
|
||||
// Diminishing effect as each affected tile has been nuked
|
||||
const numTilesLeft = tilesBeforeNuke - i;
|
||||
player.removeTroops(
|
||||
config.nukeDeathFactor(
|
||||
this.nukeType,
|
||||
player.troops(),
|
||||
numTilesLeft,
|
||||
maxTroops,
|
||||
),
|
||||
);
|
||||
player.outgoingAttacks().forEach((attack) => {
|
||||
const deaths = config.nukeDeathFactor(
|
||||
this.nukeType,
|
||||
attack.troops(),
|
||||
numTilesLeft,
|
||||
maxTroops,
|
||||
);
|
||||
attack.setTroops(attack.troops() - deaths);
|
||||
});
|
||||
transportShips.forEach((unit) => {
|
||||
const deaths = config.nukeDeathFactor(
|
||||
this.nukeType,
|
||||
unit.troops(),
|
||||
numTilesLeft,
|
||||
maxTroops,
|
||||
);
|
||||
unit.setTroops(unit.troops() - deaths);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const outer2 = magnitude.outer * magnitude.outer;
|
||||
for (const unit of this.mg.units()) {
|
||||
if (
|
||||
|
||||
@@ -33,7 +33,7 @@ export class AttackImpl implements Attack {
|
||||
return this._troops;
|
||||
}
|
||||
setTroops(troops: number) {
|
||||
this._troops = troops;
|
||||
this._troops = Math.max(0, troops);
|
||||
}
|
||||
|
||||
isActive() {
|
||||
|
||||
@@ -164,7 +164,7 @@ export class UnitImpl implements Unit {
|
||||
}
|
||||
|
||||
setTroops(troops: number): void {
|
||||
this._troops = troops;
|
||||
this._troops = Math.max(0, troops);
|
||||
}
|
||||
troops(): number {
|
||||
return this._troops;
|
||||
|
||||
Reference in New Issue
Block a user