mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-27 02:24:36 +00:00
flawed but "working"
This commit is contained in:
@@ -32,6 +32,7 @@ export class TerritoryRendererProxy {
|
||||
private offscreenCanvas: OffscreenCanvas | null = null;
|
||||
private worker: WorkerClient | null = null;
|
||||
private ready = false;
|
||||
private failed = false;
|
||||
private initPromise: Promise<void> | null = null;
|
||||
private pendingMessages: any[] = [];
|
||||
|
||||
@@ -82,7 +83,12 @@ export class TerritoryRendererProxy {
|
||||
|
||||
private startInit(): void {
|
||||
if (this.initPromise) return;
|
||||
this.initPromise = this.init();
|
||||
this.initPromise = this.init().catch((err) => {
|
||||
this.failed = true;
|
||||
this.pendingMessages = [];
|
||||
console.error("Worker territory renderer init failed:", err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
private async init(): Promise<void> {
|
||||
@@ -120,6 +126,13 @@ export class TerritoryRendererProxy {
|
||||
if (message.type === "renderer_ready" && message.id === messageId) {
|
||||
clearTimeout(timeout);
|
||||
this.worker?.removeMessageHandler(messageId);
|
||||
if (message.ok === false) {
|
||||
reject(
|
||||
new Error(message.error ?? "Renderer initialization failed"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.ready = true;
|
||||
// Send any pending messages
|
||||
for (const msg of this.pendingMessages) {
|
||||
@@ -138,6 +151,9 @@ export class TerritoryRendererProxy {
|
||||
if (!this.worker) {
|
||||
return;
|
||||
}
|
||||
if (this.failed) {
|
||||
return;
|
||||
}
|
||||
if (!this.ready) {
|
||||
this.pendingMessages.push(message);
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Theme } from "../configuration/Config";
|
||||
import { Game, UnitType } from "../game/Game";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
import { GameUpdateViewData } from "../game/GameUpdates";
|
||||
import { GameView } from "../game/GameView";
|
||||
import { TerrainMapData } from "../game/TerrainMapLoader";
|
||||
@@ -10,8 +11,6 @@ import { TerrainMapData } from "../game/TerrainMapLoader";
|
||||
* without requiring the full GameView infrastructure.
|
||||
*/
|
||||
export class GameViewAdapter implements Partial<GameView> {
|
||||
private tileStateCache: Uint16Array | null = null;
|
||||
private terrainDataCache: Uint8Array | null = null;
|
||||
private lastUpdate: GameUpdateViewData | null = null;
|
||||
|
||||
constructor(
|
||||
@@ -26,9 +25,6 @@ export class GameViewAdapter implements Partial<GameView> {
|
||||
*/
|
||||
update(gu: GameUpdateViewData): void {
|
||||
this.lastUpdate = gu;
|
||||
// Invalidate caches when updated
|
||||
this.tileStateCache = null;
|
||||
this.terrainDataCache = null;
|
||||
}
|
||||
|
||||
config() {
|
||||
@@ -43,11 +39,11 @@ export class GameViewAdapter implements Partial<GameView> {
|
||||
return this.game.height();
|
||||
}
|
||||
|
||||
x(tile: bigint): number {
|
||||
x(tile: TileRef): number {
|
||||
return this.game.x(tile);
|
||||
}
|
||||
|
||||
y(tile: bigint): number {
|
||||
y(tile: TileRef): number {
|
||||
return this.game.y(tile);
|
||||
}
|
||||
|
||||
@@ -56,57 +52,20 @@ export class GameViewAdapter implements Partial<GameView> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build tile state view from game.
|
||||
* Cached until next update.
|
||||
* Return the authoritative tile state view.
|
||||
*
|
||||
* Important: this must be the live backing buffer, because GPU update passes
|
||||
* read from it when individual tiles are marked dirty.
|
||||
*/
|
||||
tileStateView(): Uint16Array {
|
||||
if (this.tileStateCache) {
|
||||
return this.tileStateCache;
|
||||
}
|
||||
// Build tile state from game
|
||||
const width = this.game.width();
|
||||
const height = this.game.height();
|
||||
const state = new Uint16Array(width * height);
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
const tile = this.game.ref(x, y);
|
||||
const owner = this.game.owner(tile);
|
||||
const ownerId = owner ? owner.smallID() : 0;
|
||||
const terrain = this.game.terrain(tile);
|
||||
const terrainType = terrain.type();
|
||||
const terrainMag = terrain.magnitude();
|
||||
// Pack state: ownerId (12 bits) | terrainType (2 bits) | terrainMag (2 bits)
|
||||
state[y * width + x] =
|
||||
(ownerId & 0xfff) |
|
||||
((terrainType & 0x3) << 12) |
|
||||
((terrainMag & 0x3) << 14);
|
||||
}
|
||||
}
|
||||
this.tileStateCache = state;
|
||||
return state;
|
||||
return this.game.tileStateView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build terrain data view from game.
|
||||
* Cached until next update.
|
||||
* Return the immutable terrain data view.
|
||||
*/
|
||||
terrainDataView(): Uint8Array {
|
||||
if (this.terrainDataCache) {
|
||||
return this.terrainDataCache;
|
||||
}
|
||||
// Build terrain data from game
|
||||
const width = this.game.width();
|
||||
const height = this.game.height();
|
||||
const terrainData = new Uint8Array(width * height);
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
const tile = this.game.ref(x, y);
|
||||
const terrain = this.game.terrain(tile);
|
||||
terrainData[y * width + x] = terrain.type();
|
||||
}
|
||||
}
|
||||
this.terrainDataCache = terrainData;
|
||||
return terrainData;
|
||||
return this.game.terrainDataView();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,16 +73,16 @@ export class GameViewAdapter implements Partial<GameView> {
|
||||
* Computes colors from theme directly (no PlayerView needed).
|
||||
*/
|
||||
playerViews(): any[] {
|
||||
const theme = this.game.config().theme();
|
||||
return this.game.players().map((p) => {
|
||||
// Get default colors from theme
|
||||
const defaultTerritoryColor = theme.territoryColor(p as any);
|
||||
const theme = this.theme;
|
||||
return this.game.players().map((player) => {
|
||||
const defaultTerritoryColor = theme.territoryColor(player as any);
|
||||
const defaultBorderColor = theme.borderColor(defaultTerritoryColor);
|
||||
const territoryRgb = defaultTerritoryColor.toRgb();
|
||||
const borderRgb = defaultBorderColor.toRgb();
|
||||
|
||||
return {
|
||||
smallID: () => p.smallID(),
|
||||
const view = {
|
||||
player,
|
||||
smallID: () => player.smallID(),
|
||||
territoryColor: () => ({
|
||||
rgba: {
|
||||
r: Math.round(territoryRgb.r),
|
||||
@@ -140,7 +99,22 @@ export class GameViewAdapter implements Partial<GameView> {
|
||||
a: Math.round((borderRgb.a ?? 1) * 255),
|
||||
},
|
||||
}),
|
||||
hasEmbargo: (other: any) => {
|
||||
const otherPlayer = other?.player;
|
||||
if (!otherPlayer) return false;
|
||||
return (
|
||||
player.hasEmbargoAgainst(otherPlayer) ||
|
||||
otherPlayer.hasEmbargoAgainst(player)
|
||||
);
|
||||
},
|
||||
isFriendly: (other: any) => {
|
||||
const otherPlayer = other?.player;
|
||||
if (!otherPlayer) return false;
|
||||
return player.isFriendly(otherPlayer);
|
||||
},
|
||||
};
|
||||
|
||||
return view;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,10 +130,16 @@ export class GameViewAdapter implements Partial<GameView> {
|
||||
/**
|
||||
* Get recently updated tiles from last game update.
|
||||
*/
|
||||
recentlyUpdatedTiles(): bigint[] {
|
||||
recentlyUpdatedTiles(): TileRef[] {
|
||||
if (!this.lastUpdate) {
|
||||
return [];
|
||||
}
|
||||
return Array.from(this.lastUpdate.packedTileUpdates);
|
||||
// packedTileUpdates encode [tileRef << 16 | state] as bigint.
|
||||
const packed = this.lastUpdate.packedTileUpdates;
|
||||
const out: TileRef[] = new Array(packed.length);
|
||||
for (let i = 0; i < packed.length; i++) {
|
||||
out[i] = Number(packed[i] >> 16n);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,14 +214,17 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
|
||||
sendMessage({
|
||||
type: "renderer_ready",
|
||||
id: message.id,
|
||||
ok: true,
|
||||
} as RendererReadyMessage);
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize renderer:", error);
|
||||
sendMessage({
|
||||
type: "renderer_ready",
|
||||
id: message.id,
|
||||
ok: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
} as RendererReadyMessage);
|
||||
throw error;
|
||||
renderer = null;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -78,7 +78,11 @@ export class WorkerClient {
|
||||
* Post a message to the worker with optional transferables.
|
||||
*/
|
||||
postMessage(message: any, transfer?: Transferable[]): void {
|
||||
this.worker.postMessage(message, transfer);
|
||||
if (transfer && transfer.length > 0) {
|
||||
this.worker.postMessage(message, transfer);
|
||||
return;
|
||||
}
|
||||
this.worker.postMessage(message);
|
||||
}
|
||||
|
||||
initialize(): Promise<void> {
|
||||
|
||||
@@ -204,6 +204,8 @@ export interface RenderFrameMessage extends BaseWorkerMessage {
|
||||
// Renderer messages from worker to main thread
|
||||
export interface RendererReadyMessage extends BaseWorkerMessage {
|
||||
type: "renderer_ready";
|
||||
ok: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface RendererMetricsMessage extends BaseWorkerMessage {
|
||||
|
||||
@@ -24,6 +24,7 @@ import { TerritoryRenderPass } from "../../client/graphics/webgpu/render/Territo
|
||||
*/
|
||||
export class WorkerTerritoryRenderer {
|
||||
private device: WebGPUDevice | null = null;
|
||||
private canvas: OffscreenCanvas | null = null;
|
||||
private resources: GroundTruthData | null = null;
|
||||
private gameViewAdapter: GameViewAdapter | null = null;
|
||||
private ready = false;
|
||||
@@ -70,6 +71,7 @@ export class WorkerTerritoryRenderer {
|
||||
mapData: TerrainMapData,
|
||||
theme: Theme,
|
||||
): Promise<void> {
|
||||
this.canvas = offscreenCanvas;
|
||||
const game = gameRunner.game;
|
||||
this.defensePostRange = game.config().defensePostRange();
|
||||
|
||||
@@ -218,10 +220,20 @@ export class WorkerTerritoryRenderer {
|
||||
const nextWidth = Math.max(1, Math.floor(width));
|
||||
const nextHeight = Math.max(1, Math.floor(height));
|
||||
|
||||
// OffscreenCanvas doesn't have width/height properties we can set
|
||||
// The size is managed by the main thread canvas
|
||||
let sizeChanged = true;
|
||||
if (this.canvas) {
|
||||
sizeChanged =
|
||||
nextWidth !== this.canvas.width || nextHeight !== this.canvas.height;
|
||||
if (sizeChanged) {
|
||||
this.canvas.width = nextWidth;
|
||||
this.canvas.height = nextHeight;
|
||||
}
|
||||
}
|
||||
|
||||
this.resources.setViewSize(nextWidth, nextHeight);
|
||||
this.device.reconfigure();
|
||||
if (sizeChanged) {
|
||||
this.device.reconfigure();
|
||||
}
|
||||
|
||||
if (this.postSmoothingEnabled && this.resources) {
|
||||
this.resources.ensurePostSmoothingTextures(
|
||||
@@ -229,7 +241,6 @@ export class WorkerTerritoryRenderer {
|
||||
nextHeight,
|
||||
this.device.canvasFormat,
|
||||
);
|
||||
this.resources.invalidateHistory();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,9 +590,8 @@ export class WorkerTerritoryRenderer {
|
||||
}
|
||||
if (pass === this.territoryRenderPass && this.postSmoothingEnabled) {
|
||||
if (!this.resources.getCurrentColorTexture()) {
|
||||
// Use view size from resources (stored via setViewSize)
|
||||
const viewWidth = (this.resources as any).viewWidth ?? 1;
|
||||
const viewHeight = (this.resources as any).viewHeight ?? 1;
|
||||
const viewWidth = this.canvas?.width ?? 1;
|
||||
const viewHeight = this.canvas?.height ?? 1;
|
||||
this.resources.ensurePostSmoothingTextures(
|
||||
viewWidth,
|
||||
viewHeight,
|
||||
|
||||
Reference in New Issue
Block a user