fix alternate view perf regression (#1734)

## Description:

Have the diplomacy view only draw border, not interior tiles. Drawing
the interior tiles is a very expensive operation and caused main thread
cpu usage to spike to close to 100%.

Also change the color scheme so that neutral players are gray, and
embargoed players are red. I think long term embargo should be more of a
war state.

Added embargo update and cleaned it up to use Player instead of
PlayerID. There's no reason to pass ids around.


<img width="493" height="466" alt="Screenshot 2025-08-07 at 6 25 55 PM"
src="https://github.com/user-attachments/assets/75552036-42f1-4103-9537-234ff1c0464f"
/>

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I have read and accepted the CLA agreement (only required once).

## Please put your Discord username so you can be contacted if a bug or
regression is found:

evan
This commit is contained in:
evanpelle
2025-08-08 16:37:17 -07:00
committed by GitHub
parent 7743c39ecb
commit 7de962eb5b
13 changed files with 118 additions and 65 deletions
+1 -5
View File
@@ -531,17 +531,13 @@ export class NameLayer implements Layer {
// Embargo icon
let existingEmbargo = iconsDiv.querySelector('[data-icon="embargo"]');
const hasEmbargo =
myPlayer &&
(render.player.hasEmbargoAgainst(myPlayer) ||
myPlayer.hasEmbargoAgainst(render.player));
const isThemeEmbargoIcon =
existingEmbargo?.getAttribute("dark-mode") === isDarkMode.toString();
const embargoIconImageSrc = isDarkMode
? this.embargoWhiteIconImage.src
: this.embargoBlackIconImage.src;
if (myPlayer && hasEmbargo) {
if (myPlayer?.hasEmbargo(render.player)) {
// Create new icon to match theme
if (existingEmbargo && !isThemeEmbargoIcon) {
existingEmbargo.remove();
+56 -37
View File
@@ -104,10 +104,8 @@ export class TerritoryLayer implements Layer {
if (myPlayer) {
updates?.[GameUpdateType.BrokeAlliance]?.forEach((update) => {
const territory = this.game.playerBySmallID(update.betrayedID);
console.log("betrayedID", update.betrayedID);
console.log("territory", territory);
if (territory && territory instanceof PlayerView) {
this.redrawTerritory(territory);
this.redrawBorder(territory);
}
});
@@ -123,10 +121,23 @@ export class TerritoryLayer implements Layer {
: update.request.requestorID;
const territory = this.game.playerBySmallID(territoryId);
if (territory && territory instanceof PlayerView) {
this.redrawTerritory(territory);
this.redrawBorder(territory);
}
}
});
updates?.[GameUpdateType.EmbargoEvent]?.forEach((update) => {
const player = this.game.playerBySmallID(update.playerID) as PlayerView;
const embargoed = this.game.playerBySmallID(
update.embargoedID,
) as PlayerView;
if (
player.id() === myPlayer?.id() ||
embargoed.id() === myPlayer?.id()
) {
this.redrawBorder(player, embargoed);
}
});
}
const focusedPlayer = this.game.focusedPlayer();
@@ -237,7 +248,7 @@ export class TerritoryLayer implements Layer {
if (this.highlightedTerritory) {
territories.push(this.highlightedTerritory);
}
this.redrawTerritory(territories);
this.redrawBorder(...territories);
}
}
@@ -298,16 +309,15 @@ export class TerritoryLayer implements Layer {
});
}
redrawTerritory(territory: PlayerView | PlayerView[]) {
const territories = Array.isArray(territory) ? territory : [territory];
const territorySet = new Set(territories);
this.game.forEachTile((t) => {
const owner = this.game.owner(t) as PlayerView;
if (territorySet.has(owner)) {
this.paintTerritory(t);
}
});
redrawBorder(...players: PlayerView[]) {
return Promise.all(
players.map(async (player) => {
const tiles = await player.borderTiles();
tiles.borderTiles.forEach((tile: TileRef) => {
this.paintTerritory(tile, true);
});
}),
);
}
initImageData() {
@@ -419,12 +429,7 @@ export class TerritoryLayer implements Layer {
if (this.game.isBorder(tile)) {
const playerIsFocused = owner && this.game.focusedPlayer() === owner;
if (myPlayer) {
let alternativeColor = owner.isFriendly(myPlayer)
? this.theme.allyColor()
: this.theme.enemyColor();
if (owner.smallID() === myPlayer.smallID()) {
alternativeColor = this.theme.selfColor();
}
const alternativeColor = this.alternateViewColor(owner);
this.paintTile(this.alternativeImageData, tile, alternativeColor, 255);
}
if (
@@ -449,25 +454,12 @@ export class TerritoryLayer implements Layer {
this.paintTile(this.imageData, tile, useBorderColor, 255);
}
} else {
// Interior tiles
const pattern = owner.cosmetics.pattern;
const patternsEnabled = this.cachedTerritoryPatternsEnabled ?? false;
if (myPlayer) {
let alternativeColor = owner.isFriendly(myPlayer)
? this.theme.allyColor()
: this.theme.enemyColor();
// If the current player is the owner
if (owner.smallID() === myPlayer.smallID()) {
alternativeColor = this.theme.selfColor();
}
// If the tile is on a ally territory, use the ally color
this.paintTile(
this.alternativeImageData,
tile,
alternativeColor,
isHighlighted ? 150 : 60,
);
}
// Alternative view only shows borders.
this.clearAlternativeTile(tile);
if (pattern === undefined || patternsEnabled === false) {
this.paintTile(
@@ -490,6 +482,28 @@ export class TerritoryLayer implements Layer {
}
}
alternateViewColor(other: PlayerView): Colord {
const myPlayer = this.game.myPlayer();
if (!myPlayer) {
return this.theme.neutralColor();
}
if (other.smallID() === myPlayer.smallID()) {
return this.theme.selfColor();
}
if (other.isFriendly(myPlayer)) {
return this.theme.allyColor();
}
if (!other.hasEmbargo(myPlayer)) {
return this.theme.neutralColor();
}
return this.theme.enemyColor();
}
paintAlternateViewTile(tile: TileRef, other: PlayerView) {
const color = this.alternateViewColor(other);
this.paintTile(this.alternativeImageData, tile, color, 255);
}
paintTile(imageData: ImageData, tile: TileRef, color: Colord, alpha: number) {
const offset = tile * 4;
imageData.data[offset] = color.rgba.r;
@@ -504,6 +518,11 @@ export class TerritoryLayer implements Layer {
this.alternativeImageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent)
}
clearAlternativeTile(tile: TileRef) {
const offset = tile * 4;
this.alternativeImageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent)
}
enqueueTile(tile: TileRef) {
this.tileToRenderQueue.push({
tile: tile,