mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-23 03:02:52 +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 paletteMaxSmallId = 0;
|
||||||
private ownerIndexWidth = 1;
|
private ownerIndexWidth = 1;
|
||||||
private relationsSize = 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 constructor(
|
||||||
private readonly device: GPUDevice,
|
private readonly device: GPUDevice,
|
||||||
@@ -720,6 +724,8 @@ export class GroundTruthData {
|
|||||||
}
|
}
|
||||||
this.needsPaletteUpload = false;
|
this.needsPaletteUpload = false;
|
||||||
|
|
||||||
|
const prevMaxSmallId = this.paletteMaxSmallId;
|
||||||
|
|
||||||
let maxSmallId = 0;
|
let maxSmallId = 0;
|
||||||
let nextPaletteWidth = 0;
|
let nextPaletteWidth = 0;
|
||||||
let row0: Uint8Array | null = null;
|
let row0: Uint8Array | null = null;
|
||||||
@@ -780,6 +786,10 @@ export class GroundTruthData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.paletteMaxSmallId = maxSmallId;
|
this.paletteMaxSmallId = maxSmallId;
|
||||||
|
if (this.paletteMaxSmallId !== prevMaxSmallId) {
|
||||||
|
// Relations/owner-index textures depend on maxSmallId.
|
||||||
|
this.needsRelationsUpload = true;
|
||||||
|
}
|
||||||
|
|
||||||
let textureRecreated = false;
|
let textureRecreated = false;
|
||||||
if (nextPaletteWidth !== this.paletteWidth) {
|
if (nextPaletteWidth !== this.paletteWidth) {
|
||||||
@@ -819,6 +829,16 @@ export class GroundTruthData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uploadRelations(): boolean {
|
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
|
const players = this.game
|
||||||
.playerViews()
|
.playerViews()
|
||||||
.filter((p) => p.smallID() > 0)
|
.filter((p) => p.smallID() > 0)
|
||||||
@@ -852,6 +872,7 @@ export class GroundTruthData {
|
|||||||
dense++;
|
dense++;
|
||||||
denseBySmallId[id] = dense;
|
denseBySmallId[id] = dense;
|
||||||
}
|
}
|
||||||
|
this.relationsDenseBySmallId = denseBySmallId;
|
||||||
|
|
||||||
const ownerIndexBytesPerRow = align(this.ownerIndexWidth * 4, 256);
|
const ownerIndexBytesPerRow = align(this.ownerIndexWidth * 4, 256);
|
||||||
const ownerIndexPaddedU32 = new Uint32Array(ownerIndexBytesPerRow / 4);
|
const ownerIndexPaddedU32 = new Uint32Array(ownerIndexBytesPerRow / 4);
|
||||||
@@ -916,6 +937,69 @@ export class GroundTruthData {
|
|||||||
return textureRecreated;
|
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 {
|
uploadDefensePosts(): void {
|
||||||
if (!this.needsDefensePostsUpload) {
|
if (!this.needsDefensePostsUpload) {
|
||||||
return;
|
return;
|
||||||
@@ -1287,6 +1371,26 @@ export class GroundTruthData {
|
|||||||
this.needsPaletteUpload = true;
|
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(
|
setPaletteOverride(
|
||||||
paletteWidth: number,
|
paletteWidth: number,
|
||||||
maxSmallId: number,
|
maxSmallId: number,
|
||||||
|
|||||||
@@ -4,7 +4,15 @@ import { PastelTheme } from "../configuration/PastelTheme";
|
|||||||
import { PastelThemeDark } from "../configuration/PastelThemeDark";
|
import { PastelThemeDark } from "../configuration/PastelThemeDark";
|
||||||
import { FetchGameMapLoader } from "../game/FetchGameMapLoader";
|
import { FetchGameMapLoader } from "../game/FetchGameMapLoader";
|
||||||
import { PlayerID } from "../game/Game";
|
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 { loadTerrainMap, TerrainMapData } from "../game/TerrainMapLoader";
|
||||||
import { createGameRunner, GameRunner } from "../GameRunner";
|
import { createGameRunner, GameRunner } from "../GameRunner";
|
||||||
import { ClientID, GameStartInfo, PlayerCosmetics } from "../Schemas";
|
import { ClientID, GameStartInfo, PlayerCosmetics } from "../Schemas";
|
||||||
@@ -40,22 +48,75 @@ function gameUpdate(gu: GameUpdateViewData | ErrorUpdate) {
|
|||||||
return;
|
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
|
// Flush simulation-derived dirty tiles into the renderer before running
|
||||||
// compute passes for this tick.
|
// compute passes for this tick.
|
||||||
if (renderer && dirtyTiles) {
|
if (renderer && dirtyTiles) {
|
||||||
|
let didWork = false;
|
||||||
|
if (relationsChanged) {
|
||||||
|
didWork = true;
|
||||||
|
}
|
||||||
if (dirtyTilesOverflow) {
|
if (dirtyTilesOverflow) {
|
||||||
dirtyTilesOverflow = false;
|
dirtyTilesOverflow = false;
|
||||||
dirtyTiles.clear();
|
dirtyTiles.clear();
|
||||||
renderer.markAllDirty();
|
renderer.markAllDirty();
|
||||||
|
didWork = true;
|
||||||
} else {
|
} else {
|
||||||
const tiles = dirtyTiles.drain(dirtyTiles.pendingCount());
|
const pending = dirtyTiles.pendingCount();
|
||||||
for (const tile of tiles) {
|
if (pending > 0) {
|
||||||
renderer.markTile(tile);
|
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).
|
// Run compute passes at simulation tick cadence (not at render FPS).
|
||||||
renderer.tick();
|
if (didWork) {
|
||||||
|
renderer.tick();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage({
|
sendMessage({
|
||||||
|
|||||||
@@ -441,6 +441,14 @@ export class WorkerTerritoryRenderer {
|
|||||||
this.resources.markPaletteDirty();
|
this.resources.markPaletteDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markRelationsDirty(): void {
|
||||||
|
this.resources?.markRelationsDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
markRelationsPairDirty(aSmallId: number, bSmallId: number): void {
|
||||||
|
this.resources?.markRelationsPairDirty(aSmallId, bSmallId);
|
||||||
|
}
|
||||||
|
|
||||||
setPaletteFromBytes(
|
setPaletteFromBytes(
|
||||||
paletteWidth: number,
|
paletteWidth: number,
|
||||||
maxSmallId: number,
|
maxSmallId: number,
|
||||||
|
|||||||
Reference in New Issue
Block a user