mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-26 11:22:44 +00:00
Merge branch 'sab' into Atomic-SAB
This commit is contained in:
@@ -31,6 +31,7 @@ import {
|
||||
drainTileUpdates,
|
||||
SharedTileRingBuffers,
|
||||
SharedTileRingViews,
|
||||
TILE_RING_HEADER_OVERFLOW,
|
||||
} from "../core/worker/SharedTileRing";
|
||||
import { WorkerClient } from "../core/worker/WorkerClient";
|
||||
import {
|
||||
@@ -561,7 +562,8 @@ export class ClientGameRunner {
|
||||
batch.length > 0 &&
|
||||
lastTick !== undefined
|
||||
) {
|
||||
const combinedGu = this.mergeGameUpdates(batch);
|
||||
const { gameUpdate: combinedGu, tileMetrics } =
|
||||
this.mergeGameUpdates(batch);
|
||||
if (combinedGu) {
|
||||
this.gameView.update(combinedGu);
|
||||
}
|
||||
@@ -599,6 +601,10 @@ export class ClientGameRunner {
|
||||
ticksPerRender,
|
||||
workerTicksPerSecond,
|
||||
renderTicksPerSecond,
|
||||
tileMetrics.count,
|
||||
tileMetrics.utilization,
|
||||
tileMetrics.overflow,
|
||||
tileMetrics.drainTime,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -616,9 +622,15 @@ export class ClientGameRunner {
|
||||
requestAnimationFrame(processFrame);
|
||||
}
|
||||
|
||||
private mergeGameUpdates(
|
||||
batch: GameUpdateViewData[],
|
||||
): GameUpdateViewData | null {
|
||||
private mergeGameUpdates(batch: GameUpdateViewData[]): {
|
||||
gameUpdate: GameUpdateViewData | null;
|
||||
tileMetrics: {
|
||||
count: number;
|
||||
utilization: number;
|
||||
overflow: number;
|
||||
drainTime: number;
|
||||
};
|
||||
} {
|
||||
if (batch.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -644,27 +656,60 @@ export class ClientGameRunner {
|
||||
}
|
||||
}
|
||||
|
||||
let tileMetrics = {
|
||||
count: 0,
|
||||
utilization: 0,
|
||||
overflow: 0,
|
||||
drainTime: 0,
|
||||
};
|
||||
|
||||
if (this.tileRingViews) {
|
||||
const MAX_TILE_UPDATES_PER_RENDER = 100000;
|
||||
const tileRefs: TileRef[] = [];
|
||||
const drainStart = performance.now();
|
||||
drainTileUpdates(
|
||||
this.tileRingViews,
|
||||
MAX_TILE_UPDATES_PER_RENDER,
|
||||
combinedPackedTileUpdates,
|
||||
tileRefs,
|
||||
);
|
||||
} else {
|
||||
for (const gu of batch) {
|
||||
gu.packedTileUpdates.forEach((tu) => {
|
||||
combinedPackedTileUpdates.push(tu);
|
||||
});
|
||||
const drainTime = performance.now() - drainStart;
|
||||
|
||||
// Calculate ring buffer utilization and overflow
|
||||
const TILE_RING_CAPACITY = 262144;
|
||||
const utilization = (tileRefs.length / TILE_RING_CAPACITY) * 100;
|
||||
const overflow = Atomics.load(
|
||||
this.tileRingViews.header,
|
||||
TILE_RING_HEADER_OVERFLOW,
|
||||
);
|
||||
|
||||
tileMetrics = {
|
||||
count: tileRefs.length,
|
||||
utilization,
|
||||
overflow,
|
||||
drainTime,
|
||||
};
|
||||
|
||||
for (const ref of tileRefs) {
|
||||
combinedPackedTileUpdates.push(BigInt(ref));
|
||||
}
|
||||
} else {
|
||||
// Non-SAB mode: count tile updates from batch
|
||||
let totalTileUpdates = 0;
|
||||
for (const gu of batch) {
|
||||
totalTileUpdates += gu.packedTileUpdates.length;
|
||||
}
|
||||
tileMetrics.count = totalTileUpdates;
|
||||
}
|
||||
|
||||
return {
|
||||
tick: last.tick,
|
||||
updates: combinedUpdates,
|
||||
packedTileUpdates: new BigUint64Array(combinedPackedTileUpdates),
|
||||
playerNameViewData: last.playerNameViewData,
|
||||
tickExecutionDuration: last.tickExecutionDuration,
|
||||
gameUpdate: {
|
||||
tick: last.tick,
|
||||
updates: combinedUpdates,
|
||||
packedTileUpdates: new BigUint64Array(combinedPackedTileUpdates),
|
||||
playerNameViewData: last.playerNameViewData,
|
||||
tickExecutionDuration: last.tickExecutionDuration,
|
||||
},
|
||||
tileMetrics,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,11 @@ export class TickMetricsEvent implements GameEvent {
|
||||
public readonly workerTicksPerSecond?: number,
|
||||
// Approximate render tick() calls per second
|
||||
public readonly renderTicksPerSecond?: number,
|
||||
// Tile update metrics
|
||||
public readonly tileUpdatesCount?: number,
|
||||
public readonly ringBufferUtilization?: number,
|
||||
public readonly ringBufferOverflows?: number,
|
||||
public readonly ringDrainTime?: number,
|
||||
) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -319,6 +319,14 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
this.layerStats.clear();
|
||||
this.layerBreakdown = [];
|
||||
|
||||
// reset tile metrics
|
||||
this.tileUpdatesPerRender = 0;
|
||||
this.tileUpdatesPeak = 0;
|
||||
this.ringBufferUtilization = 0;
|
||||
this.ringBufferOverflows = 0;
|
||||
this.ringDrainTime = 0;
|
||||
this.totalTilesUpdated = 0;
|
||||
|
||||
this.requestUpdate();
|
||||
};
|
||||
|
||||
@@ -437,6 +445,24 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
@state()
|
||||
private renderTicksPerSecond: number = 0;
|
||||
|
||||
@state()
|
||||
private tileUpdatesPerRender: number = 0;
|
||||
|
||||
@state()
|
||||
private tileUpdatesPeak: number = 0;
|
||||
|
||||
@state()
|
||||
private ringBufferUtilization: number = 0;
|
||||
|
||||
@state()
|
||||
private ringBufferOverflows: number = 0;
|
||||
|
||||
@state()
|
||||
private ringDrainTime: number = 0;
|
||||
|
||||
@state()
|
||||
private totalTilesUpdated: number = 0;
|
||||
|
||||
updateTickMetrics(
|
||||
tickExecutionDuration?: number,
|
||||
tickDelay?: number,
|
||||
@@ -444,6 +470,10 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
ticksPerRender?: number,
|
||||
workerTicksPerSecond?: number,
|
||||
renderTicksPerSecond?: number,
|
||||
tileUpdatesCount?: number,
|
||||
ringBufferUtilization?: number,
|
||||
ringBufferOverflows?: number,
|
||||
ringDrainTime?: number,
|
||||
) {
|
||||
if (!this.isVisible || !this.userSettings.performanceOverlay()) return;
|
||||
|
||||
@@ -497,6 +527,26 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
this.renderTicksPerSecond = renderTicksPerSecond;
|
||||
}
|
||||
|
||||
if (tileUpdatesCount !== undefined) {
|
||||
this.tileUpdatesPerRender = tileUpdatesCount;
|
||||
this.tileUpdatesPeak = Math.max(this.tileUpdatesPeak, tileUpdatesCount);
|
||||
this.totalTilesUpdated += tileUpdatesCount;
|
||||
}
|
||||
|
||||
if (ringBufferUtilization !== undefined) {
|
||||
this.ringBufferUtilization =
|
||||
Math.round(ringBufferUtilization * 100) / 100;
|
||||
}
|
||||
|
||||
if (ringBufferOverflows !== undefined) {
|
||||
// Accumulate overflows (overflows is a flag, so add 1 if set)
|
||||
this.ringBufferOverflows += ringBufferOverflows;
|
||||
}
|
||||
|
||||
if (ringDrainTime !== undefined) {
|
||||
this.ringDrainTime = Math.round(ringDrainTime * 100) / 100;
|
||||
}
|
||||
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
@@ -527,6 +577,14 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
executionSamples: [...this.tickExecutionTimes],
|
||||
delaySamples: [...this.tickDelayTimes],
|
||||
},
|
||||
tiles: {
|
||||
updatesPerRender: this.tileUpdatesPerRender,
|
||||
peakUpdates: this.tileUpdatesPeak,
|
||||
ringBufferUtilization: this.ringBufferUtilization,
|
||||
ringBufferOverflows: this.ringBufferOverflows,
|
||||
ringDrainTimeMs: this.ringDrainTime,
|
||||
totalTilesUpdated: this.totalTilesUpdated,
|
||||
},
|
||||
layers: this.layerBreakdown.map((layer) => ({ ...layer })),
|
||||
};
|
||||
}
|
||||
@@ -658,6 +716,21 @@ export class PerformanceOverlay extends LitElement implements Layer {
|
||||
Backlog turns:
|
||||
<span>${this.backlogTurns}</span>
|
||||
</div>
|
||||
<div class="performance-line">
|
||||
Tile updates/render:
|
||||
<span>${this.tileUpdatesPerRender}</span>
|
||||
(peak: <span>${this.tileUpdatesPeak}</span>)
|
||||
</div>
|
||||
<div class="performance-line">
|
||||
Ring buffer:
|
||||
<span>${this.ringBufferUtilization}%</span>
|
||||
(${this.totalTilesUpdated} total, ${this.ringBufferOverflows}
|
||||
overflows)
|
||||
</div>
|
||||
<div class="performance-line">
|
||||
Ring drain time:
|
||||
<span>${this.ringDrainTime.toFixed(2)}ms</span>
|
||||
</div>
|
||||
${this.layerBreakdown.length
|
||||
? html`<div class="layers-section">
|
||||
<div class="performance-line">
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function createGameRunner(
|
||||
clientID: ClientID,
|
||||
mapLoader: GameMapLoader,
|
||||
callBack: (gu: GameUpdateViewData | ErrorUpdate) => void,
|
||||
tileUpdateSink?: (update: bigint) => void,
|
||||
tileUpdateSink?: (tile: TileRef) => void,
|
||||
sharedStateBuffer?: SharedArrayBuffer,
|
||||
): Promise<GameRunner> {
|
||||
const config = await getConfig(gameStart.config, null);
|
||||
@@ -106,7 +106,7 @@ export class GameRunner {
|
||||
public game: Game,
|
||||
private execManager: Executor,
|
||||
private callBack: (gu: GameUpdateViewData | ErrorUpdate) => void,
|
||||
private tileUpdateSink?: (update: bigint) => void,
|
||||
private tileUpdateSink?: (tile: TileRef) => void,
|
||||
) {}
|
||||
|
||||
init() {
|
||||
@@ -187,7 +187,8 @@ export class GameRunner {
|
||||
const tileUpdates = updates[GameUpdateType.Tile];
|
||||
if (this.tileUpdateSink !== undefined) {
|
||||
for (const u of tileUpdates) {
|
||||
this.tileUpdateSink(u.update);
|
||||
const tileRef = Number(u.update >> 16n) as TileRef;
|
||||
this.tileUpdateSink(tileRef);
|
||||
}
|
||||
packedTileUpdates = new BigUint64Array();
|
||||
} else {
|
||||
|
||||
@@ -515,7 +515,7 @@ export class GameView implements GameMap {
|
||||
this.updatedTiles = [];
|
||||
if (this.usesSharedTileState) {
|
||||
this.lastUpdate.packedTileUpdates.forEach((tu) => {
|
||||
const tileRef = Number(tu >> 16n);
|
||||
const tileRef = Number(tu);
|
||||
this.updatedTiles.push(tileRef);
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export interface SharedTileRingBuffers {
|
||||
header: SharedArrayBuffer;
|
||||
data: SharedArrayBuffer;
|
||||
@@ -5,7 +7,7 @@ export interface SharedTileRingBuffers {
|
||||
|
||||
export interface SharedTileRingViews {
|
||||
header: Int32Array;
|
||||
buffer: BigUint64Array;
|
||||
buffer: Uint32Array;
|
||||
capacity: number;
|
||||
}
|
||||
|
||||
@@ -18,9 +20,7 @@ export function createSharedTileRingBuffers(
|
||||
capacity: number,
|
||||
): SharedTileRingBuffers {
|
||||
const header = new SharedArrayBuffer(3 * Int32Array.BYTES_PER_ELEMENT);
|
||||
const data = new SharedArrayBuffer(
|
||||
capacity * BigUint64Array.BYTES_PER_ELEMENT,
|
||||
);
|
||||
const data = new SharedArrayBuffer(capacity * Uint32Array.BYTES_PER_ELEMENT);
|
||||
return { header, data };
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export function createSharedTileRingViews(
|
||||
buffers: SharedTileRingBuffers,
|
||||
): SharedTileRingViews {
|
||||
const header = new Int32Array(buffers.header);
|
||||
const buffer = new BigUint64Array(buffers.data);
|
||||
const buffer = new Uint32Array(buffers.data);
|
||||
return {
|
||||
header,
|
||||
buffer,
|
||||
@@ -38,7 +38,7 @@ export function createSharedTileRingViews(
|
||||
|
||||
export function pushTileUpdate(
|
||||
views: SharedTileRingViews,
|
||||
value: bigint,
|
||||
value: TileRef,
|
||||
): void {
|
||||
const { header, buffer, capacity } = views;
|
||||
|
||||
@@ -60,7 +60,7 @@ export function pushTileUpdate(
|
||||
export function drainTileUpdates(
|
||||
views: SharedTileRingViews,
|
||||
maxItems: number,
|
||||
out: bigint[],
|
||||
out: TileRef[],
|
||||
): void {
|
||||
const { header, buffer, capacity } = views;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import version from "../../../resources/version.txt";
|
||||
import { createGameRunner, GameRunner } from "../GameRunner";
|
||||
import { FetchGameMapLoader } from "../game/FetchGameMapLoader";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
import { ErrorUpdate, GameUpdateViewData } from "../game/GameUpdates";
|
||||
import {
|
||||
createSharedTileRingViews,
|
||||
@@ -83,7 +84,7 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
|
||||
mapLoader,
|
||||
gameUpdate,
|
||||
sharedTileRing
|
||||
? (update: bigint) => pushTileUpdate(sharedTileRing!, update)
|
||||
? (tile: TileRef) => pushTileUpdate(sharedTileRing!, tile)
|
||||
: undefined,
|
||||
message.sharedStateBuffer,
|
||||
).then((gr) => {
|
||||
|
||||
Reference in New Issue
Block a user