mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:32:21 +00:00
Batch attack cluster positions into one worker request per tick; raise z-index above names
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { Cell } from "../../../core/game/Game";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { UserSettings } from "../../../core/game/UserSettings";
|
||||
import { AlternateViewEvent } from "../../InputHandler";
|
||||
import { renderTroops } from "../../Utils";
|
||||
@@ -26,12 +26,14 @@ interface AttackLabel {
|
||||
elements: HTMLDivElement[];
|
||||
positions: (Cell | null)[];
|
||||
isIncoming: boolean;
|
||||
attackerTroops: number;
|
||||
defenderTroops: number;
|
||||
}
|
||||
|
||||
export class TroopAdvantageLayer implements Layer {
|
||||
private container: HTMLDivElement;
|
||||
private labels = new Map<string, AttackLabel>();
|
||||
private inFlightPositionRequests = new Set<string>();
|
||||
private inFlightRequest = false;
|
||||
private isVisible = true;
|
||||
private onAlternateView: (e: AlternateViewEvent) => void;
|
||||
|
||||
@@ -52,7 +54,7 @@ export class TroopAdvantageLayer implements Layer {
|
||||
this.container.style.left = "50%";
|
||||
this.container.style.top = "50%";
|
||||
this.container.style.pointerEvents = "none";
|
||||
this.container.style.zIndex = "2";
|
||||
this.container.style.zIndex = "4";
|
||||
document.body.appendChild(this.container);
|
||||
|
||||
this.onAlternateView = (e) => {
|
||||
@@ -110,15 +112,7 @@ export class TroopAdvantageLayer implements Layer {
|
||||
this.removeLabel(attack.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.processAttack(
|
||||
myPlayer,
|
||||
attack.id,
|
||||
attack.attackerID,
|
||||
attack.troops,
|
||||
defender.troops(),
|
||||
false,
|
||||
);
|
||||
this.ensureLabel(attack.id, attack.troops, defender.troops(), false);
|
||||
}
|
||||
|
||||
// Incoming attacks — red if attacker > my troops, orange if attacker < my troops
|
||||
@@ -128,66 +122,65 @@ export class TroopAdvantageLayer implements Layer {
|
||||
this.removeLabel(attack.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.processAttack(
|
||||
myPlayer,
|
||||
attack.id,
|
||||
attack.attackerID,
|
||||
attack.troops,
|
||||
myTroops,
|
||||
true,
|
||||
);
|
||||
this.ensureLabel(attack.id, attack.troops, myTroops, true);
|
||||
}
|
||||
|
||||
// Single request per tick for all attack cluster positions
|
||||
if (this.inFlightRequest) return;
|
||||
this.inFlightRequest = true;
|
||||
|
||||
void myPlayer
|
||||
.attackClusterPositions(myPlayer.smallID())
|
||||
.then((attacks) => {
|
||||
for (const { id, clusters } of attacks) {
|
||||
const lbl = this.labels.get(id);
|
||||
if (!lbl) continue;
|
||||
|
||||
while (lbl.elements.length < clusters.length) {
|
||||
lbl.elements.push(
|
||||
this.createLabelElement(
|
||||
lbl.attackerTroops,
|
||||
lbl.defenderTroops,
|
||||
lbl.isIncoming,
|
||||
),
|
||||
);
|
||||
lbl.positions.push(null);
|
||||
}
|
||||
while (lbl.elements.length > clusters.length) {
|
||||
lbl.elements.pop()!.remove();
|
||||
lbl.positions.pop();
|
||||
}
|
||||
|
||||
for (let i = 0; i < clusters.length; i++) {
|
||||
lbl.positions[i] = clusters[i];
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
for (const lbl of this.labels.values()) lbl.positions.fill(null);
|
||||
})
|
||||
.finally(() => {
|
||||
this.inFlightRequest = false;
|
||||
});
|
||||
}
|
||||
|
||||
private processAttack(
|
||||
myPlayer: PlayerView,
|
||||
private ensureLabel(
|
||||
attackID: string,
|
||||
attackerSmallID: number,
|
||||
attackerTroops: number,
|
||||
defenderTroops: number,
|
||||
isIncoming: boolean,
|
||||
) {
|
||||
let label = this.labels.get(attackID);
|
||||
if (!label) {
|
||||
label = { elements: [], positions: [], isIncoming };
|
||||
label = { elements: [], positions: [], isIncoming, attackerTroops, defenderTroops };
|
||||
this.labels.set(attackID, label);
|
||||
} else {
|
||||
label.attackerTroops = attackerTroops;
|
||||
label.defenderTroops = defenderTroops;
|
||||
}
|
||||
for (const el of label.elements) {
|
||||
this.updateLabelContent(el, attackerTroops, defenderTroops, isIncoming);
|
||||
}
|
||||
|
||||
if (this.inFlightPositionRequests.has(attackID)) return;
|
||||
this.inFlightPositionRequests.add(attackID);
|
||||
|
||||
void myPlayer
|
||||
.attackClusterPositions(attackerSmallID, attackID)
|
||||
.then((clusters) => {
|
||||
const lbl = this.labels.get(attackID);
|
||||
if (!lbl) return;
|
||||
|
||||
while (lbl.elements.length < clusters.length) {
|
||||
lbl.elements.push(
|
||||
this.createLabelElement(attackerTroops, defenderTroops, isIncoming),
|
||||
);
|
||||
lbl.positions.push(null);
|
||||
}
|
||||
while (lbl.elements.length > clusters.length) {
|
||||
lbl.elements.pop()!.remove();
|
||||
lbl.positions.pop();
|
||||
}
|
||||
|
||||
for (let i = 0; i < clusters.length; i++) {
|
||||
lbl.positions[i] = clusters[i];
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
const lbl = this.labels.get(attackID);
|
||||
if (lbl) lbl.positions.fill(null);
|
||||
})
|
||||
.finally(() => {
|
||||
this.inFlightPositionRequests.delete(attackID);
|
||||
});
|
||||
}
|
||||
|
||||
renderLayer(_context: CanvasRenderingContext2D) {
|
||||
@@ -263,7 +256,6 @@ export class TroopAdvantageLayer implements Layer {
|
||||
if (!label) return;
|
||||
for (const el of label.elements) el.remove();
|
||||
this.labels.delete(attackID);
|
||||
this.inFlightPositionRequests.delete(attackID);
|
||||
}
|
||||
|
||||
private clearAllLabels() {
|
||||
@@ -271,6 +263,5 @@ export class TroopAdvantageLayer implements Layer {
|
||||
for (const el of label.elements) el.remove();
|
||||
}
|
||||
this.labels.clear();
|
||||
this.inFlightPositionRequests.clear();
|
||||
}
|
||||
}
|
||||
|
||||
+10
-8
@@ -256,20 +256,22 @@ export class GameRunner {
|
||||
|
||||
public attackClusterPositions(
|
||||
playerID: number,
|
||||
attackID: string,
|
||||
): { x: number; y: number }[] {
|
||||
attackID?: string,
|
||||
): { id: string; clusters: { x: number; y: number }[] }[] {
|
||||
const player = this.game.playerBySmallID(playerID);
|
||||
if (!player.isPlayer()) {
|
||||
throw new Error(`player with id ${playerID} not found`);
|
||||
}
|
||||
|
||||
const condition = (a: Attack) => a.id() === attackID;
|
||||
const attack =
|
||||
player.outgoingAttacks().find(condition) ??
|
||||
player.incomingAttacks().find(condition);
|
||||
if (attack === undefined) return [];
|
||||
const allAttacks = [
|
||||
...player.outgoingAttacks(),
|
||||
...player.incomingAttacks(),
|
||||
];
|
||||
const attacks = attackID
|
||||
? allAttacks.filter((a) => a.id() === attackID)
|
||||
: allAttacks;
|
||||
|
||||
return attack.clusterPositions();
|
||||
return attacks.map((a) => ({ id: a.id(), clusters: a.clusterPositions() }));
|
||||
}
|
||||
|
||||
public attackAveragePosition(
|
||||
|
||||
@@ -462,8 +462,8 @@ export class PlayerView {
|
||||
|
||||
async attackClusterPositions(
|
||||
playerID: number,
|
||||
attackID: string,
|
||||
): Promise<Cell[]> {
|
||||
attackID?: string,
|
||||
): Promise<{ id: string; clusters: Cell[] }[]> {
|
||||
return this.game.worker.attackClusterPositions(playerID, attackID);
|
||||
}
|
||||
|
||||
|
||||
@@ -271,21 +271,21 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const clusters = (await gameRunner).attackClusterPositions(
|
||||
const attacks = (await gameRunner).attackClusterPositions(
|
||||
message.playerID,
|
||||
message.attackID,
|
||||
);
|
||||
sendMessage({
|
||||
type: "attack_cluster_positions_result",
|
||||
id: message.id,
|
||||
clusters,
|
||||
attacks,
|
||||
} as AttackClusterPositionsResultMessage);
|
||||
} catch (error) {
|
||||
console.error("Failed to get attack cluster positions:", error);
|
||||
sendMessage({
|
||||
type: "attack_cluster_positions_result",
|
||||
id: message.id,
|
||||
clusters: [],
|
||||
attacks: [],
|
||||
} as AttackClusterPositionsResultMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -266,7 +266,10 @@ export class WorkerClient {
|
||||
});
|
||||
}
|
||||
|
||||
attackClusterPositions(playerID: number, attackID: string): Promise<Cell[]> {
|
||||
attackClusterPositions(
|
||||
playerID: number,
|
||||
attackID?: string,
|
||||
): Promise<{ id: string; clusters: Cell[] }[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isInitialized) {
|
||||
reject(new Error("Worker not initialized"));
|
||||
@@ -277,7 +280,12 @@ export class WorkerClient {
|
||||
|
||||
this.messageHandlers.set(messageId, (message) => {
|
||||
if (message.type === "attack_cluster_positions_result") {
|
||||
resolve(message.clusters.map((c) => new Cell(c.x, c.y)));
|
||||
resolve(
|
||||
message.attacks.map((a) => ({
|
||||
id: a.id,
|
||||
clusters: a.clusters.map((c) => new Cell(c.x, c.y)),
|
||||
})),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -125,12 +125,12 @@ export interface AttackAveragePositionResultMessage extends BaseWorkerMessage {
|
||||
export interface AttackClusterPositionsMessage extends BaseWorkerMessage {
|
||||
type: "attack_cluster_positions";
|
||||
playerID: number;
|
||||
attackID: string;
|
||||
attackID?: string;
|
||||
}
|
||||
|
||||
export interface AttackClusterPositionsResultMessage extends BaseWorkerMessage {
|
||||
type: "attack_cluster_positions_result";
|
||||
clusters: { x: number; y: number }[];
|
||||
attacks: { id: string; clusters: { x: number; y: number }[] }[];
|
||||
}
|
||||
|
||||
export interface TransportShipSpawnMessage extends BaseWorkerMessage {
|
||||
|
||||
Reference in New Issue
Block a user