mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:20:46 +00:00
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:
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -185,6 +185,7 @@ export interface Theme {
|
||||
// unit color for alternate view
|
||||
selfColor(): Colord;
|
||||
allyColor(): Colord;
|
||||
neutralColor(): Colord;
|
||||
enemyColor(): Colord;
|
||||
spawnHighlightColor(): Colord;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ export class PastelTheme implements Theme {
|
||||
|
||||
private _selfColor = colord({ r: 0, g: 255, b: 0 });
|
||||
private _allyColor = colord({ r: 255, g: 255, b: 0 });
|
||||
private _neutralColor = colord({ r: 128, g: 128, b: 128 });
|
||||
private _enemyColor = colord({ r: 255, g: 0, b: 0 });
|
||||
|
||||
private _spawnHighlightColor = colord({ r: 255, g: 213, b: 79 });
|
||||
@@ -159,6 +160,9 @@ export class PastelTheme implements Theme {
|
||||
allyColor(): Colord {
|
||||
return this._allyColor;
|
||||
}
|
||||
neutralColor(): Colord {
|
||||
return this._neutralColor;
|
||||
}
|
||||
enemyColor(): Colord {
|
||||
return this._enemyColor;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ export class PastelThemeDark implements Theme {
|
||||
|
||||
private _selfColor = colord({ r: 0, g: 255, b: 0 });
|
||||
private _allyColor = colord({ r: 255, g: 255, b: 0 });
|
||||
private _neutralColor = colord({ r: 128, g: 128, b: 128 });
|
||||
private _enemyColor = colord({ r: 255, g: 0, b: 0 });
|
||||
|
||||
private _spawnHighlightColor = colord({ r: 255, g: 213, b: 79 });
|
||||
@@ -161,6 +162,9 @@ export class PastelThemeDark implements Theme {
|
||||
allyColor(): Colord {
|
||||
return this._allyColor;
|
||||
}
|
||||
neutralColor(): Colord {
|
||||
return this._neutralColor;
|
||||
}
|
||||
enemyColor(): Colord {
|
||||
return this._enemyColor;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export class AttackExecution implements Execution {
|
||||
this._owner.type() !== PlayerType.Bot
|
||||
) {
|
||||
// Don't let bots embargo since they can't trade anyway.
|
||||
targetPlayer.addEmbargo(this._owner.id(), true);
|
||||
targetPlayer.addEmbargo(this._owner, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Execution, Game, Player, PlayerID } from "../game/Game";
|
||||
export class EmbargoExecution implements Execution {
|
||||
private active = true;
|
||||
|
||||
private target: Player;
|
||||
|
||||
constructor(
|
||||
private player: Player,
|
||||
private targetID: PlayerID,
|
||||
@@ -15,11 +17,12 @@ export class EmbargoExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.target = mg.player(this.targetID);
|
||||
}
|
||||
|
||||
tick(_: number): void {
|
||||
if (this.action === "start") this.player.addEmbargo(this.targetID, false);
|
||||
else this.player.stopEmbargo(this.targetID);
|
||||
if (this.action === "start") this.player.addEmbargo(this.target, false);
|
||||
else this.player.stopEmbargo(this.target);
|
||||
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
@@ -100,12 +100,12 @@ export class FakeHumanExecution implements Execution {
|
||||
player.relation(other) <= Relation.Hostile &&
|
||||
!player.hasEmbargoAgainst(other)
|
||||
) {
|
||||
player.addEmbargo(other.id(), false);
|
||||
player.addEmbargo(other, false);
|
||||
} else if (
|
||||
player.relation(other) >= Relation.Neutral &&
|
||||
player.hasEmbargoAgainst(other)
|
||||
) {
|
||||
player.stopEmbargo(other.id());
|
||||
player.stopEmbargo(other);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -487,7 +487,7 @@ export interface TerraNullius {
|
||||
export interface Embargo {
|
||||
createdAt: Tick;
|
||||
isTemporary: boolean;
|
||||
target: PlayerID;
|
||||
target: Player;
|
||||
}
|
||||
|
||||
export interface Player {
|
||||
@@ -595,10 +595,10 @@ export interface Player {
|
||||
// Embargo
|
||||
hasEmbargoAgainst(other: Player): boolean;
|
||||
tradingPartners(): Player[];
|
||||
addEmbargo(other: PlayerID, isTemporary: boolean): void;
|
||||
addEmbargo(other: Player, isTemporary: boolean): void;
|
||||
getEmbargoes(): Embargo[];
|
||||
stopEmbargo(other: PlayerID): void;
|
||||
endTemporaryEmbargo(other: PlayerID): void;
|
||||
stopEmbargo(other: Player): void;
|
||||
endTemporaryEmbargo(other: Player): void;
|
||||
canTrade(other: Player): boolean;
|
||||
|
||||
// Attacking.
|
||||
|
||||
@@ -287,9 +287,9 @@ export class GameImpl implements Game {
|
||||
|
||||
// Automatically remove embargoes only if they were automatically created
|
||||
if (requestor.hasEmbargoAgainst(recipient))
|
||||
requestor.endTemporaryEmbargo(recipient.id());
|
||||
requestor.endTemporaryEmbargo(recipient);
|
||||
if (recipient.hasEmbargoAgainst(requestor))
|
||||
recipient.endTemporaryEmbargo(requestor.id());
|
||||
recipient.endTemporaryEmbargo(requestor);
|
||||
|
||||
this.addUpdate({
|
||||
type: GameUpdateType.AllianceRequestReply,
|
||||
|
||||
@@ -45,6 +45,7 @@ export enum GameUpdateType {
|
||||
BonusEvent,
|
||||
RailroadEvent,
|
||||
ConquestEvent,
|
||||
EmbargoEvent,
|
||||
}
|
||||
|
||||
export type GameUpdate =
|
||||
@@ -65,7 +66,8 @@ export type GameUpdate =
|
||||
| AllianceExtensionUpdate
|
||||
| BonusEventUpdate
|
||||
| RailroadUpdate
|
||||
| ConquestUpdate;
|
||||
| ConquestUpdate
|
||||
| EmbargoUpdate;
|
||||
|
||||
export interface BonusEventUpdate {
|
||||
type: GameUpdateType.BonusEvent;
|
||||
@@ -255,3 +257,10 @@ export interface UnitIncomingUpdate {
|
||||
messageType: MessageType;
|
||||
playerID: number;
|
||||
}
|
||||
|
||||
export interface EmbargoUpdate {
|
||||
type: GameUpdateType.EmbargoEvent;
|
||||
event: "start" | "stop";
|
||||
playerID: number;
|
||||
embargoedID: number;
|
||||
}
|
||||
|
||||
@@ -326,6 +326,10 @@ export class PlayerView {
|
||||
return this.data.embargoes.has(other.id());
|
||||
}
|
||||
|
||||
hasEmbargo(other: PlayerView): boolean {
|
||||
return this.hasEmbargoAgainst(other) || other.hasEmbargoAgainst(this);
|
||||
}
|
||||
|
||||
profile(): Promise<PlayerProfile> {
|
||||
return this.game.worker.playerProfile(this.smallID());
|
||||
}
|
||||
|
||||
+24
-11
@@ -642,27 +642,40 @@ export class PlayerImpl implements Player {
|
||||
return !embargo && other.id() !== this.id();
|
||||
}
|
||||
|
||||
addEmbargo(other: PlayerID, isTemporary: boolean): void {
|
||||
const embargo = this.embargoes.get(other);
|
||||
getEmbargoes(): Embargo[] {
|
||||
return [...this.embargoes.values()];
|
||||
}
|
||||
|
||||
addEmbargo(other: Player, isTemporary: boolean): void {
|
||||
const embargo = this.embargoes.get(other.id());
|
||||
if (embargo !== undefined && !embargo.isTemporary) return;
|
||||
|
||||
this.embargoes.set(other, {
|
||||
this.mg.addUpdate({
|
||||
type: GameUpdateType.EmbargoEvent,
|
||||
event: "start",
|
||||
playerID: this.smallID(),
|
||||
embargoedID: other.smallID(),
|
||||
});
|
||||
|
||||
this.embargoes.set(other.id(), {
|
||||
createdAt: this.mg.ticks(),
|
||||
isTemporary: isTemporary,
|
||||
target: other,
|
||||
});
|
||||
}
|
||||
|
||||
getEmbargoes(): Embargo[] {
|
||||
return [...this.embargoes.values()];
|
||||
stopEmbargo(other: Player): void {
|
||||
this.embargoes.delete(other.id());
|
||||
this.mg.addUpdate({
|
||||
type: GameUpdateType.EmbargoEvent,
|
||||
event: "stop",
|
||||
playerID: this.smallID(),
|
||||
embargoedID: other.smallID(),
|
||||
});
|
||||
}
|
||||
|
||||
stopEmbargo(other: PlayerID): void {
|
||||
this.embargoes.delete(other);
|
||||
}
|
||||
|
||||
endTemporaryEmbargo(other: PlayerID): void {
|
||||
const embargo = this.embargoes.get(other);
|
||||
endTemporaryEmbargo(other: Player): void {
|
||||
const embargo = this.embargoes.get(other.id());
|
||||
if (embargo !== undefined && !embargo.isTemporary) return;
|
||||
|
||||
this.stopEmbargo(other);
|
||||
|
||||
Reference in New Issue
Block a user