From 28bae2d7ebf0a573a62e1a990a9a6d220f3bc4b1 Mon Sep 17 00:00:00 2001 From: scamiv <6170744+scamiv@users.noreply.github.com> Date: Sat, 9 May 2026 15:30:10 +0200 Subject: [PATCH] Await name layer initialization before ticks Make layer initialization awaitable and have GameRenderer wait for each layer init before input and worker ticks start. This prevents the Pixi NameLayer from seeing players before its async font, atlas, and renderer setup has completed. Also store the AlternateViewEvent handler on NameLayer so destroy() can unsubscribe it, matching the existing resize listener cleanup and preventing torn-down instances from mutating state. --- src/client/ClientGameRunner.ts | 8 ++++---- src/client/graphics/GameRenderer.ts | 6 ++++-- src/client/graphics/layers/Layer.ts | 2 +- src/client/graphics/layers/NameLayer.ts | 5 ++++- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 51f29e6fd..110f4dbfc 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -148,9 +148,9 @@ export function joinLobby( terrainLoad, terrainMapFileLoader, ) - .then((r) => { + .then(async (r) => { currentGameRunner = r; - r.start(); + await r.start(); }) .catch((e) => { console.error("error creating client game", e); @@ -373,7 +373,7 @@ export class ClientGameRunner { endGame(record); } - public start() { + public async start() { this.soundManager.playBackgroundMusic(); console.log("starting client game"); @@ -410,7 +410,7 @@ export class ClientGameRunner { this.doBreakAllianceUnderCursor.bind(this), ); - this.renderer.initialize(); + await this.renderer.initialize(); this.input.initialize(); this.worker.start((gu: GameUpdateViewData | ErrorUpdate) => { if (this.lobby.gameStartInfo === undefined) { diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 4df65facc..f41048fb6 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -353,9 +353,11 @@ export class GameRenderer { this.context = context; } - initialize() { + async initialize() { this.eventBus.on(RedrawGraphicsEvent, () => this.redraw()); - this.layers.forEach((l) => l.init?.()); + for (const layer of this.layers) { + await layer.init?.(); + } // only append the canvas if it's not already in the document to avoid reparenting side-effects if (!document.body.contains(this.canvas)) { diff --git a/src/client/graphics/layers/Layer.ts b/src/client/graphics/layers/Layer.ts index 563647705..332bcf73c 100644 --- a/src/client/graphics/layers/Layer.ts +++ b/src/client/graphics/layers/Layer.ts @@ -1,5 +1,5 @@ export interface Layer { - init?: () => void; + init?: () => void | Promise; tick?: () => void; // Optional hint to throttle expensive ticks by wall-clock. // If omitted or <= 0, the layer ticks whenever GameRenderer ticks. diff --git a/src/client/graphics/layers/NameLayer.ts b/src/client/graphics/layers/NameLayer.ts index 1fcf624f2..1e61208c6 100644 --- a/src/client/graphics/layers/NameLayer.ts +++ b/src/client/graphics/layers/NameLayer.ts @@ -90,6 +90,8 @@ export class NameLayer implements Layer { private readonly pixiCanvas: HTMLCanvasElement = document.createElement("canvas"); private readonly onWindowResize = () => this.resizeCanvas(); + private readonly onAlternateViewHandler = (e: AlternateViewEvent) => + this.onAlternateViewChange(e); private renderer: PixiRenderer | null = null; private rendererInitialized = false; private rebuildPending = false; @@ -114,7 +116,7 @@ export class NameLayer implements Layer { this.rootStage.addChild(this.labelStage); this.rootStage.position.set(0, 0); - this.eventBus.on(AlternateViewEvent, (e) => this.onAlternateViewChange(e)); + this.eventBus.on(AlternateViewEvent, this.onAlternateViewHandler); window.addEventListener("resize", this.onWindowResize); await this.setupRenderer(); @@ -773,6 +775,7 @@ export class NameLayer implements Layer { } destroy() { + this.eventBus.off(AlternateViewEvent, this.onAlternateViewHandler); window.removeEventListener("resize", this.onWindowResize); for (const render of this.renders) { render.container.destroy({ children: true });