mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-14 08:45:04 +00:00
Use dirty flags to coalesce tile updates in SAB ring
- Extend SharedTileRing to include a shared dirtyFlags buffer alongside header and data - Pass shared dirty buffer through WorkerClient/WorkerMessages and initialize views in Worker.worker - In SAB mode, mark tiles dirty via Atomics.compareExchange before enqueuing to ensure each tile is queued at most once until processed - On the main thread, clear dirty flags when draining the ring and build packedTileUpdates from distinct tile refs - Keep non-SAB behaviour unchanged while reducing ring pressure and making overflows reflect true backlog, not duplicate updates
This commit is contained in:
@@ -7,6 +7,7 @@ export type TerrainMapData = {
|
||||
gameMap: GameMap;
|
||||
miniGameMap: GameMap;
|
||||
sharedStateBuffer?: SharedArrayBuffer;
|
||||
sharedDirtyBuffer?: SharedArrayBuffer;
|
||||
};
|
||||
|
||||
const loadedMaps = new Map<GameMapType, TerrainMapData>();
|
||||
@@ -104,6 +105,7 @@ export async function loadTerrainMap(
|
||||
stateBuffer instanceof SharedArrayBuffer
|
||||
? stateBuffer
|
||||
: undefined,
|
||||
sharedDirtyBuffer: undefined, // populated by consumer when needed
|
||||
};
|
||||
// Always cache the result, but only use cache when appropriate
|
||||
loadedMaps.set(map, result);
|
||||
|
||||
@@ -3,11 +3,13 @@ import { TileRef } from "../game/GameMap";
|
||||
export interface SharedTileRingBuffers {
|
||||
header: SharedArrayBuffer;
|
||||
data: SharedArrayBuffer;
|
||||
dirty: SharedArrayBuffer;
|
||||
}
|
||||
|
||||
export interface SharedTileRingViews {
|
||||
header: Int32Array;
|
||||
buffer: Uint32Array;
|
||||
dirtyFlags: Uint8Array;
|
||||
capacity: number;
|
||||
}
|
||||
|
||||
@@ -18,10 +20,12 @@ export const TILE_RING_HEADER_OVERFLOW = 2;
|
||||
|
||||
export function createSharedTileRingBuffers(
|
||||
capacity: number,
|
||||
numTiles: number,
|
||||
): SharedTileRingBuffers {
|
||||
const header = new SharedArrayBuffer(3 * Int32Array.BYTES_PER_ELEMENT);
|
||||
const data = new SharedArrayBuffer(capacity * Uint32Array.BYTES_PER_ELEMENT);
|
||||
return { header, data };
|
||||
const dirty = new SharedArrayBuffer(numTiles * Uint8Array.BYTES_PER_ELEMENT);
|
||||
return { header, data, dirty };
|
||||
}
|
||||
|
||||
export function createSharedTileRingViews(
|
||||
@@ -29,9 +33,11 @@ export function createSharedTileRingViews(
|
||||
): SharedTileRingViews {
|
||||
const header = new Int32Array(buffers.header);
|
||||
const buffer = new Uint32Array(buffers.data);
|
||||
const dirtyFlags = new Uint8Array(buffers.dirty);
|
||||
return {
|
||||
header,
|
||||
buffer,
|
||||
dirtyFlags,
|
||||
capacity: buffer.length,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ let gameRunner: Promise<GameRunner> | null = null;
|
||||
const mapLoader = new FetchGameMapLoader(`/maps`, version);
|
||||
let isProcessingTurns = false;
|
||||
let sharedTileRing: SharedTileRingViews | null = null;
|
||||
let dirtyFlags: Uint8Array | null = null;
|
||||
|
||||
function gameUpdate(gu: GameUpdateViewData | ErrorUpdate) {
|
||||
// skip if ErrorUpdate
|
||||
@@ -73,19 +74,35 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
|
||||
sharedTileRing = createSharedTileRingViews({
|
||||
header: message.sharedTileRingHeader,
|
||||
data: message.sharedTileRingData,
|
||||
dirty: message.sharedDirtyBuffer!,
|
||||
});
|
||||
dirtyFlags = sharedTileRing.dirtyFlags;
|
||||
} else {
|
||||
sharedTileRing = null;
|
||||
dirtyFlags = null;
|
||||
}
|
||||
|
||||
console.log("[Worker.worker] init", {
|
||||
hasSharedStateBuffer: !!message.sharedStateBuffer,
|
||||
hasRingHeader: !!message.sharedTileRingHeader,
|
||||
hasRingData: !!message.sharedTileRingData,
|
||||
hasDirtyBuffer: !!message.sharedDirtyBuffer,
|
||||
});
|
||||
|
||||
gameRunner = createGameRunner(
|
||||
message.gameStartInfo,
|
||||
message.clientID,
|
||||
mapLoader,
|
||||
gameUpdate,
|
||||
sharedTileRing
|
||||
? (tile: TileRef) => pushTileUpdate(sharedTileRing!, tile)
|
||||
: undefined,
|
||||
sharedTileRing && dirtyFlags
|
||||
? (tile: TileRef) => {
|
||||
if (Atomics.compareExchange(dirtyFlags!, tile, 0, 1) === 0) {
|
||||
pushTileUpdate(sharedTileRing!, tile);
|
||||
}
|
||||
}
|
||||
: sharedTileRing
|
||||
? (tile: TileRef) => pushTileUpdate(sharedTileRing!, tile)
|
||||
: undefined,
|
||||
message.sharedStateBuffer,
|
||||
).then((gr) => {
|
||||
sendMessage({
|
||||
|
||||
@@ -25,6 +25,7 @@ export class WorkerClient {
|
||||
private clientID: ClientID,
|
||||
private sharedTileRingBuffers?: SharedTileRingBuffers,
|
||||
private sharedStateBuffer?: SharedArrayBuffer,
|
||||
private sharedDirtyBuffer?: SharedArrayBuffer,
|
||||
) {
|
||||
this.worker = new Worker(new URL("./Worker.worker.ts", import.meta.url));
|
||||
this.messageHandlers = new Map();
|
||||
@@ -76,6 +77,7 @@ export class WorkerClient {
|
||||
sharedTileRingHeader: this.sharedTileRingBuffers?.header,
|
||||
sharedTileRingData: this.sharedTileRingBuffers?.data,
|
||||
sharedStateBuffer: this.sharedStateBuffer,
|
||||
sharedDirtyBuffer: this.sharedDirtyBuffer,
|
||||
});
|
||||
|
||||
// Add timeout for initialization
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface InitMessage extends BaseWorkerMessage {
|
||||
sharedTileRingHeader?: SharedArrayBuffer;
|
||||
sharedTileRingData?: SharedArrayBuffer;
|
||||
sharedStateBuffer?: SharedArrayBuffer;
|
||||
sharedDirtyBuffer?: SharedArrayBuffer;
|
||||
}
|
||||
|
||||
export interface TurnMessage extends BaseWorkerMessage {
|
||||
|
||||
Reference in New Issue
Block a user