mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 14:20:45 +00:00
Add relations management to GroundTruthData and update Worker components
- Introduced new properties and methods in GroundTruthData for handling relations, including `needsRelationsUpload`, `relationsDenseBySmallId`, and `pendingRelationsPairs`. - Implemented logic to mark relations as dirty and upload relations conditionally based on changes in diplomacy. - Updated Worker.worker.ts to synchronize relations updates with the renderer, optimizing performance by only refreshing when necessary. - Enhanced WorkerTerritoryRenderer with methods to mark relations dirty, ensuring proper resource management during updates.
This commit is contained in:
@@ -113,6 +113,10 @@ export class GroundTruthData {
|
||||
private paletteMaxSmallId = 0;
|
||||
private ownerIndexWidth = 1;
|
||||
private relationsSize = 1;
|
||||
private needsRelationsUpload = true;
|
||||
private relationsDenseBySmallId: Uint32Array | null = null;
|
||||
private pendingRelationsPairs: Set<bigint> = new Set();
|
||||
private readonly relationWriteScratch = new Uint8Array(256);
|
||||
|
||||
private constructor(
|
||||
private readonly device: GPUDevice,
|
||||
@@ -720,6 +724,8 @@ export class GroundTruthData {
|
||||
}
|
||||
this.needsPaletteUpload = false;
|
||||
|
||||
const prevMaxSmallId = this.paletteMaxSmallId;
|
||||
|
||||
let maxSmallId = 0;
|
||||
let nextPaletteWidth = 0;
|
||||
let row0: Uint8Array | null = null;
|
||||
@@ -780,6 +786,10 @@ export class GroundTruthData {
|
||||
}
|
||||
|
||||
this.paletteMaxSmallId = maxSmallId;
|
||||
if (this.paletteMaxSmallId !== prevMaxSmallId) {
|
||||
// Relations/owner-index textures depend on maxSmallId.
|
||||
this.needsRelationsUpload = true;
|
||||
}
|
||||
|
||||
let textureRecreated = false;
|
||||
if (nextPaletteWidth !== this.paletteWidth) {
|
||||
@@ -819,6 +829,16 @@ export class GroundTruthData {
|
||||
}
|
||||
|
||||
uploadRelations(): boolean {
|
||||
if (!this.needsRelationsUpload && this.pendingRelationsPairs.size > 0) {
|
||||
return this.uploadRelationsPartial();
|
||||
}
|
||||
if (!this.needsRelationsUpload) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.needsRelationsUpload = false;
|
||||
this.pendingRelationsPairs.clear();
|
||||
|
||||
const players = this.game
|
||||
.playerViews()
|
||||
.filter((p) => p.smallID() > 0)
|
||||
@@ -852,6 +872,7 @@ export class GroundTruthData {
|
||||
dense++;
|
||||
denseBySmallId[id] = dense;
|
||||
}
|
||||
this.relationsDenseBySmallId = denseBySmallId;
|
||||
|
||||
const ownerIndexBytesPerRow = align(this.ownerIndexWidth * 4, 256);
|
||||
const ownerIndexPaddedU32 = new Uint32Array(ownerIndexBytesPerRow / 4);
|
||||
@@ -916,6 +937,69 @@ export class GroundTruthData {
|
||||
return textureRecreated;
|
||||
}
|
||||
|
||||
private uploadRelationsPartial(): boolean {
|
||||
if (!this.relationsDenseBySmallId || !this.relationsTexture) {
|
||||
// No stable mapping/texture yet: fall back to a full rebuild.
|
||||
this.needsRelationsUpload = true;
|
||||
this.pendingRelationsPairs.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
const denseBySmallId = this.relationsDenseBySmallId;
|
||||
const size = this.relationsSize;
|
||||
const scratch = this.relationWriteScratch;
|
||||
const bytesPerRow = 256;
|
||||
|
||||
const writeTexel = (x: number, y: number, value: number) => {
|
||||
if (x <= 0 || y <= 0 || x >= size || y >= size) {
|
||||
return;
|
||||
}
|
||||
scratch.fill(0);
|
||||
scratch[0] = value & 0xff;
|
||||
this.device.queue.writeTexture(
|
||||
{ texture: this.relationsTexture, origin: { x, y } },
|
||||
scratch,
|
||||
{ bytesPerRow, rowsPerImage: 1 },
|
||||
{ width: 1, height: 1, depthOrArrayLayers: 1 },
|
||||
);
|
||||
};
|
||||
|
||||
const computeCode = (aSmall: number, bSmall: number): number => {
|
||||
if (aSmall === bSmall) return 0;
|
||||
const aAny: any = (this.game as any).playerBySmallID?.(aSmall);
|
||||
const bAny: any = (this.game as any).playerBySmallID?.(bSmall);
|
||||
if (!aAny || !bAny || !aAny.isPlayer?.() || !bAny.isPlayer?.()) {
|
||||
return 0;
|
||||
}
|
||||
if (aAny.hasEmbargo?.(bAny)) {
|
||||
return 2;
|
||||
}
|
||||
if (aAny.isFriendly?.(bAny) || bAny.isFriendly?.(aAny)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
for (const key of this.pendingRelationsPairs) {
|
||||
const aSmall = Number(key >> 32n);
|
||||
const bSmall = Number(key & 0xffffffffn);
|
||||
const aDense = denseBySmallId[aSmall] ?? 0;
|
||||
const bDense = denseBySmallId[bSmall] ?? 0;
|
||||
if (aDense === 0 || bDense === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const code = computeCode(aSmall, bSmall);
|
||||
writeTexel(aDense, bDense, code);
|
||||
if (aDense !== bDense) {
|
||||
writeTexel(bDense, aDense, code);
|
||||
}
|
||||
}
|
||||
|
||||
this.pendingRelationsPairs.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
uploadDefensePosts(): void {
|
||||
if (!this.needsDefensePostsUpload) {
|
||||
return;
|
||||
@@ -1287,6 +1371,26 @@ export class GroundTruthData {
|
||||
this.needsPaletteUpload = true;
|
||||
}
|
||||
|
||||
markRelationsDirty(): void {
|
||||
this.needsRelationsUpload = true;
|
||||
this.pendingRelationsPairs.clear();
|
||||
}
|
||||
|
||||
markRelationsPairDirty(aSmallId: number, bSmallId: number): void {
|
||||
if (aSmallId <= 0 || bSmallId <= 0) {
|
||||
return;
|
||||
}
|
||||
if (!this.relationsDenseBySmallId) {
|
||||
// No mapping yet: ensure a full rebuild occurs.
|
||||
this.needsRelationsUpload = true;
|
||||
return;
|
||||
}
|
||||
const a = Math.min(aSmallId, bSmallId);
|
||||
const b = Math.max(aSmallId, bSmallId);
|
||||
const key = (BigInt(a) << 32n) | BigInt(b);
|
||||
this.pendingRelationsPairs.add(key);
|
||||
}
|
||||
|
||||
setPaletteOverride(
|
||||
paletteWidth: number,
|
||||
maxSmallId: number,
|
||||
|
||||
@@ -4,7 +4,15 @@ import { PastelTheme } from "../configuration/PastelTheme";
|
||||
import { PastelThemeDark } from "../configuration/PastelThemeDark";
|
||||
import { FetchGameMapLoader } from "../game/FetchGameMapLoader";
|
||||
import { PlayerID } from "../game/Game";
|
||||
import { ErrorUpdate, GameUpdateViewData } from "../game/GameUpdates";
|
||||
import {
|
||||
AllianceExpiredUpdate,
|
||||
AllianceRequestReplyUpdate,
|
||||
BrokeAllianceUpdate,
|
||||
EmbargoUpdate,
|
||||
ErrorUpdate,
|
||||
GameUpdateType,
|
||||
GameUpdateViewData,
|
||||
} from "../game/GameUpdates";
|
||||
import { loadTerrainMap, TerrainMapData } from "../game/TerrainMapLoader";
|
||||
import { createGameRunner, GameRunner } from "../GameRunner";
|
||||
import { ClientID, GameStartInfo, PlayerCosmetics } from "../Schemas";
|
||||
@@ -40,22 +48,75 @@ function gameUpdate(gu: GameUpdateViewData | ErrorUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep renderer-side adapter in sync (palette/relations/etc).
|
||||
(renderer as any)?.updateGameView?.(gu);
|
||||
|
||||
// Uploading relations is expensive; only refresh when diplomacy changes,
|
||||
// and only for the affected player pairs.
|
||||
const updates = gu.updates;
|
||||
let relationsChanged = false;
|
||||
if (renderer) {
|
||||
const markPair = (aSmallId: number, bSmallId: number) => {
|
||||
const r: any = renderer as any;
|
||||
if (r?.markRelationsPairDirty) {
|
||||
r.markRelationsPairDirty(aSmallId, bSmallId);
|
||||
relationsChanged = true;
|
||||
} else if (r?.markRelationsDirty) {
|
||||
// Fallback for older/other renderers.
|
||||
r.markRelationsDirty();
|
||||
relationsChanged = true;
|
||||
}
|
||||
};
|
||||
|
||||
for (const e of updates[GameUpdateType.EmbargoEvent] as EmbargoUpdate[]) {
|
||||
markPair(e.playerID, e.embargoedID);
|
||||
}
|
||||
for (const e of updates[
|
||||
GameUpdateType.AllianceRequestReply
|
||||
] as AllianceRequestReplyUpdate[]) {
|
||||
if (e.accepted) {
|
||||
markPair(e.request.requestorID, e.request.recipientID);
|
||||
}
|
||||
}
|
||||
for (const e of updates[
|
||||
GameUpdateType.BrokeAlliance
|
||||
] as BrokeAllianceUpdate[]) {
|
||||
markPair(e.traitorID, e.betrayedID);
|
||||
}
|
||||
for (const e of updates[
|
||||
GameUpdateType.AllianceExpired
|
||||
] as AllianceExpiredUpdate[]) {
|
||||
markPair(e.player1ID, e.player2ID);
|
||||
}
|
||||
}
|
||||
|
||||
// Flush simulation-derived dirty tiles into the renderer before running
|
||||
// compute passes for this tick.
|
||||
if (renderer && dirtyTiles) {
|
||||
let didWork = false;
|
||||
if (relationsChanged) {
|
||||
didWork = true;
|
||||
}
|
||||
if (dirtyTilesOverflow) {
|
||||
dirtyTilesOverflow = false;
|
||||
dirtyTiles.clear();
|
||||
renderer.markAllDirty();
|
||||
didWork = true;
|
||||
} else {
|
||||
const tiles = dirtyTiles.drain(dirtyTiles.pendingCount());
|
||||
for (const tile of tiles) {
|
||||
renderer.markTile(tile);
|
||||
const pending = dirtyTiles.pendingCount();
|
||||
if (pending > 0) {
|
||||
const tiles = dirtyTiles.drain(pending);
|
||||
for (const tile of tiles) {
|
||||
renderer.markTile(tile);
|
||||
}
|
||||
didWork = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Run compute passes at simulation tick cadence (not at render FPS).
|
||||
renderer.tick();
|
||||
if (didWork) {
|
||||
renderer.tick();
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage({
|
||||
|
||||
@@ -441,6 +441,14 @@ export class WorkerTerritoryRenderer {
|
||||
this.resources.markPaletteDirty();
|
||||
}
|
||||
|
||||
markRelationsDirty(): void {
|
||||
this.resources?.markRelationsDirty();
|
||||
}
|
||||
|
||||
markRelationsPairDirty(aSmallId: number, bSmallId: number): void {
|
||||
this.resources?.markRelationsPairDirty(aSmallId, bSmallId);
|
||||
}
|
||||
|
||||
setPaletteFromBytes(
|
||||
paletteWidth: number,
|
||||
maxSmallId: number,
|
||||
|
||||
Reference in New Issue
Block a user