mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-19 02:04:39 +00:00
Add performance stats (#2338)
## Description: Enhanced the performance overlay to display additional tick-related performance metrics. The overlay now shows: 1. **Tick Execution Duration** - Average and maximum time (in milliseconds) it takes to execute a game tick 2. **Tick Delay** - Average and maximum time (in milliseconds) between receiving tick updates from the server The server sends 10 updates per second (100ms interval), so these metrics help identify: - Client-side performance bottlenecks (tick execution duration) - Network latency issues (tick delay) **Additional improvements:** - Renamed `FPSDisplay` component to `PerformanceOverlay` to better reflect its expanded purpose - Updated method names (`updateFPS` → `updateFrameMetrics`) and CSS classes for consistency All metrics are tracked over the last 60 ticks, providing rolling averages and maximum values for performance analysis. ## Please complete the following: - [x] I have added screenshots for all UI updates: - <img width="495" height="227" alt="image" src="https://github.com/user-attachments/assets/142b0313-61bf-46cc-b595-61fe73f6b54c" /> - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - Translation keys already exist in en.json for "performance_overlay_label" and "performance_overlay_desc" - [x] I have added relevant tests to the test directory - All existing tests pass (309/310 tests passed) - No new tests added as this is primarily a display enhancement - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - Tested locally with npm test - Verified performance overlay displays all metrics correctly - Confirmed tick metrics are calculated and displayed accurately ## Please put your Discord username so you can be contacted if a bug or regression is found: Discord: kerverse --------- Co-authored-by: Evan <evanpelle@gmail.com>
This commit is contained in:
committed by
evanpelle
co-authored by
Evan
parent
45539623fc
commit
822d08bb7c
@@ -33,6 +33,7 @@ import {
|
|||||||
InputHandler,
|
InputHandler,
|
||||||
MouseMoveEvent,
|
MouseMoveEvent,
|
||||||
MouseUpEvent,
|
MouseUpEvent,
|
||||||
|
TickMetricsEvent,
|
||||||
} from "./InputHandler";
|
} from "./InputHandler";
|
||||||
import { endGame, startGame, startTime } from "./LocalPersistantStats";
|
import { endGame, startGame, startTime } from "./LocalPersistantStats";
|
||||||
import { getPersistentID } from "./Main";
|
import { getPersistentID } from "./Main";
|
||||||
@@ -201,6 +202,8 @@ export class ClientGameRunner {
|
|||||||
|
|
||||||
private lastMessageTime: number = 0;
|
private lastMessageTime: number = 0;
|
||||||
private connectionCheckInterval: NodeJS.Timeout | null = null;
|
private connectionCheckInterval: NodeJS.Timeout | null = null;
|
||||||
|
private lastTickReceiveTime: number = 0;
|
||||||
|
private currentTickDelay: number | undefined = undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private lobby: LobbyConfig,
|
private lobby: LobbyConfig,
|
||||||
@@ -292,6 +295,14 @@ export class ClientGameRunner {
|
|||||||
this.gameView.update(gu);
|
this.gameView.update(gu);
|
||||||
this.renderer.tick();
|
this.renderer.tick();
|
||||||
|
|
||||||
|
// Emit tick metrics event for performance overlay
|
||||||
|
this.eventBus.emit(
|
||||||
|
new TickMetricsEvent(gu.tickExecutionDuration, this.currentTickDelay),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset tick delay for next measurement
|
||||||
|
this.currentTickDelay = undefined;
|
||||||
|
|
||||||
if (gu.updates[GameUpdateType.Win].length > 0) {
|
if (gu.updates[GameUpdateType.Win].length > 0) {
|
||||||
this.saveGame(gu.updates[GameUpdateType.Win][0]);
|
this.saveGame(gu.updates[GameUpdateType.Win][0]);
|
||||||
}
|
}
|
||||||
@@ -359,6 +370,14 @@ export class ClientGameRunner {
|
|||||||
this.transport.joinGame(0);
|
this.transport.joinGame(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Track when we receive the turn to calculate delay
|
||||||
|
const now = Date.now();
|
||||||
|
if (this.lastTickReceiveTime > 0) {
|
||||||
|
// Calculate delay between receiving turn messages
|
||||||
|
this.currentTickDelay = now - this.lastTickReceiveTime;
|
||||||
|
}
|
||||||
|
this.lastTickReceiveTime = now;
|
||||||
|
|
||||||
if (this.turnsSeen !== message.turn.turnNumber) {
|
if (this.turnsSeen !== message.turn.turnNumber) {
|
||||||
console.error(
|
console.error(
|
||||||
`got wrong turn have turns ${this.turnsSeen}, received turn ${message.turn.turnNumber}`,
|
`got wrong turn have turns ${this.turnsSeen}, received turn ${message.turn.turnNumber}`,
|
||||||
|
|||||||
@@ -115,6 +115,13 @@ export class AutoUpgradeEvent implements GameEvent {
|
|||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TickMetricsEvent implements GameEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly tickExecutionDuration?: number,
|
||||||
|
public readonly tickDelay?: number,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
export class InputHandler {
|
export class InputHandler {
|
||||||
private lastPointerX: number = 0;
|
private lastPointerX: number = 0;
|
||||||
private lastPointerY: number = 0;
|
private lastPointerY: number = 0;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { ChatModal } from "./layers/ChatModal";
|
|||||||
import { ControlPanel } from "./layers/ControlPanel";
|
import { ControlPanel } from "./layers/ControlPanel";
|
||||||
import { EmojiTable } from "./layers/EmojiTable";
|
import { EmojiTable } from "./layers/EmojiTable";
|
||||||
import { EventsDisplay } from "./layers/EventsDisplay";
|
import { EventsDisplay } from "./layers/EventsDisplay";
|
||||||
import { FPSDisplay } from "./layers/FPSDisplay";
|
|
||||||
import { FxLayer } from "./layers/FxLayer";
|
import { FxLayer } from "./layers/FxLayer";
|
||||||
import { GameLeftSidebar } from "./layers/GameLeftSidebar";
|
import { GameLeftSidebar } from "./layers/GameLeftSidebar";
|
||||||
import { GameRightSidebar } from "./layers/GameRightSidebar";
|
import { GameRightSidebar } from "./layers/GameRightSidebar";
|
||||||
@@ -23,6 +22,7 @@ import { Leaderboard } from "./layers/Leaderboard";
|
|||||||
import { MainRadialMenu } from "./layers/MainRadialMenu";
|
import { MainRadialMenu } from "./layers/MainRadialMenu";
|
||||||
import { MultiTabModal } from "./layers/MultiTabModal";
|
import { MultiTabModal } from "./layers/MultiTabModal";
|
||||||
import { NameLayer } from "./layers/NameLayer";
|
import { NameLayer } from "./layers/NameLayer";
|
||||||
|
import { PerformanceOverlay } from "./layers/PerformanceOverlay";
|
||||||
import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay";
|
import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay";
|
||||||
import { PlayerPanel } from "./layers/PlayerPanel";
|
import { PlayerPanel } from "./layers/PlayerPanel";
|
||||||
import { RailroadLayer } from "./layers/RailroadLayer";
|
import { RailroadLayer } from "./layers/RailroadLayer";
|
||||||
@@ -202,12 +202,14 @@ export function createRenderer(
|
|||||||
|
|
||||||
const structureLayer = new StructureLayer(game, eventBus, transformHandler);
|
const structureLayer = new StructureLayer(game, eventBus, transformHandler);
|
||||||
|
|
||||||
const fpsDisplay = document.querySelector("fps-display") as FPSDisplay;
|
const performanceOverlay = document.querySelector(
|
||||||
if (!(fpsDisplay instanceof FPSDisplay)) {
|
"performance-overlay",
|
||||||
console.error("fps display not found");
|
) as PerformanceOverlay;
|
||||||
|
if (!(performanceOverlay instanceof PerformanceOverlay)) {
|
||||||
|
console.error("performance overlay not found");
|
||||||
}
|
}
|
||||||
fpsDisplay.eventBus = eventBus;
|
performanceOverlay.eventBus = eventBus;
|
||||||
fpsDisplay.userSettings = userSettings;
|
performanceOverlay.userSettings = userSettings;
|
||||||
|
|
||||||
const alertFrame = document.querySelector("alert-frame") as AlertFrame;
|
const alertFrame = document.querySelector("alert-frame") as AlertFrame;
|
||||||
if (!(alertFrame instanceof AlertFrame)) {
|
if (!(alertFrame instanceof AlertFrame)) {
|
||||||
@@ -263,7 +265,7 @@ export function createRenderer(
|
|||||||
multiTabModal,
|
multiTabModal,
|
||||||
new AdTimer(game),
|
new AdTimer(game),
|
||||||
alertFrame,
|
alertFrame,
|
||||||
fpsDisplay,
|
performanceOverlay,
|
||||||
];
|
];
|
||||||
|
|
||||||
return new GameRenderer(
|
return new GameRenderer(
|
||||||
@@ -273,7 +275,7 @@ export function createRenderer(
|
|||||||
transformHandler,
|
transformHandler,
|
||||||
uiState,
|
uiState,
|
||||||
layers,
|
layers,
|
||||||
fpsDisplay,
|
performanceOverlay,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +289,7 @@ export class GameRenderer {
|
|||||||
public transformHandler: TransformHandler,
|
public transformHandler: TransformHandler,
|
||||||
public uiState: UIState,
|
public uiState: UIState,
|
||||||
private layers: Layer[],
|
private layers: Layer[],
|
||||||
private fpsDisplay: FPSDisplay,
|
private performanceOverlay: PerformanceOverlay,
|
||||||
) {
|
) {
|
||||||
const context = canvas.getContext("2d");
|
const context = canvas.getContext("2d");
|
||||||
if (context === null) throw new Error("2d context not supported");
|
if (context === null) throw new Error("2d context not supported");
|
||||||
@@ -371,7 +373,7 @@ export class GameRenderer {
|
|||||||
requestAnimationFrame(() => this.renderGame());
|
requestAnimationFrame(() => this.renderGame());
|
||||||
const duration = performance.now() - start;
|
const duration = performance.now() - start;
|
||||||
|
|
||||||
this.fpsDisplay.updateFPS(duration);
|
this.performanceOverlay.updateFrameMetrics(duration);
|
||||||
|
|
||||||
if (duration > 50) {
|
if (duration > 50) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
|||||||
+91
-21
@@ -2,11 +2,14 @@ import { LitElement, css, html } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
import { EventBus } from "../../../core/EventBus";
|
import { EventBus } from "../../../core/EventBus";
|
||||||
import { UserSettings } from "../../../core/game/UserSettings";
|
import { UserSettings } from "../../../core/game/UserSettings";
|
||||||
import { TogglePerformanceOverlayEvent } from "../../InputHandler";
|
import {
|
||||||
|
TickMetricsEvent,
|
||||||
|
TogglePerformanceOverlayEvent,
|
||||||
|
} from "../../InputHandler";
|
||||||
import { Layer } from "./Layer";
|
import { Layer } from "./Layer";
|
||||||
|
|
||||||
@customElement("fps-display")
|
@customElement("performance-overlay")
|
||||||
export class FPSDisplay extends LitElement implements Layer {
|
export class PerformanceOverlay extends LitElement implements Layer {
|
||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
public eventBus!: EventBus;
|
public eventBus!: EventBus;
|
||||||
|
|
||||||
@@ -22,6 +25,18 @@ export class FPSDisplay extends LitElement implements Layer {
|
|||||||
@state()
|
@state()
|
||||||
private frameTime: number = 0;
|
private frameTime: number = 0;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private tickExecutionAvg: number = 0;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private tickExecutionMax: number = 0;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private tickDelayAvg: number = 0;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private tickDelayMax: number = 0;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private isVisible: boolean = false;
|
private isVisible: boolean = false;
|
||||||
|
|
||||||
@@ -38,9 +53,11 @@ export class FPSDisplay extends LitElement implements Layer {
|
|||||||
private lastSecondTime: number = 0;
|
private lastSecondTime: number = 0;
|
||||||
private framesThisSecond: number = 0;
|
private framesThisSecond: number = 0;
|
||||||
private dragStart: { x: number; y: number } = { x: 0, y: 0 };
|
private dragStart: { x: number; y: number } = { x: 0, y: 0 };
|
||||||
|
private tickExecutionTimes: number[] = [];
|
||||||
|
private tickDelayTimes: number[] = [];
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
.fps-display {
|
.performance-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -57,25 +74,25 @@ export class FPSDisplay extends LitElement implements Layer {
|
|||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fps-display.dragging {
|
.performance-overlay.dragging {
|
||||||
cursor: grabbing;
|
cursor: grabbing;
|
||||||
transition: none;
|
transition: none;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fps-line {
|
.performance-line {
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fps-good {
|
.performance-good {
|
||||||
color: #4ade80; /* green-400 */
|
color: #4ade80; /* green-400 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.fps-warning {
|
.performance-warning {
|
||||||
color: #fbbf24; /* amber-400 */
|
color: #fbbf24; /* amber-400 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.fps-bad {
|
.performance-bad {
|
||||||
color: #f87171; /* red-400 */
|
color: #f87171; /* red-400 */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +125,9 @@ export class FPSDisplay extends LitElement implements Layer {
|
|||||||
this.eventBus.on(TogglePerformanceOverlayEvent, () => {
|
this.eventBus.on(TogglePerformanceOverlayEvent, () => {
|
||||||
this.userSettings.togglePerformanceOverlay();
|
this.userSettings.togglePerformanceOverlay();
|
||||||
});
|
});
|
||||||
|
this.eventBus.on(TickMetricsEvent, (event: TickMetricsEvent) => {
|
||||||
|
this.updateTickMetrics(event.tickExecutionDuration, event.tickDelay);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisible(visible: boolean) {
|
setVisible(visible: boolean) {
|
||||||
@@ -159,7 +179,7 @@ export class FPSDisplay extends LitElement implements Layer {
|
|||||||
document.removeEventListener("mouseup", this.handleMouseUp);
|
document.removeEventListener("mouseup", this.handleMouseUp);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateFPS(frameDuration: number) {
|
updateFrameMetrics(frameDuration: number) {
|
||||||
this.isVisible = this.userSettings.performanceOverlay();
|
this.isVisible = this.userSettings.performanceOverlay();
|
||||||
|
|
||||||
if (!this.isVisible) return;
|
if (!this.isVisible) return;
|
||||||
@@ -216,14 +236,54 @@ export class FPSDisplay extends LitElement implements Layer {
|
|||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateTickMetrics(tickExecutionDuration?: number, tickDelay?: number) {
|
||||||
|
if (!this.isVisible || !this.userSettings.performanceOverlay()) return;
|
||||||
|
|
||||||
|
// Update tick execution duration stats
|
||||||
|
if (tickExecutionDuration !== undefined) {
|
||||||
|
this.tickExecutionTimes.push(tickExecutionDuration);
|
||||||
|
if (this.tickExecutionTimes.length > 60) {
|
||||||
|
this.tickExecutionTimes.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.tickExecutionTimes.length > 0) {
|
||||||
|
const avg =
|
||||||
|
this.tickExecutionTimes.reduce((a, b) => a + b, 0) /
|
||||||
|
this.tickExecutionTimes.length;
|
||||||
|
this.tickExecutionAvg = Math.round(avg * 100) / 100;
|
||||||
|
this.tickExecutionMax = Math.round(
|
||||||
|
Math.max(...this.tickExecutionTimes),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update tick delay stats
|
||||||
|
if (tickDelay !== undefined) {
|
||||||
|
this.tickDelayTimes.push(tickDelay);
|
||||||
|
if (this.tickDelayTimes.length > 60) {
|
||||||
|
this.tickDelayTimes.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.tickDelayTimes.length > 0) {
|
||||||
|
const avg =
|
||||||
|
this.tickDelayTimes.reduce((a, b) => a + b, 0) /
|
||||||
|
this.tickDelayTimes.length;
|
||||||
|
this.tickDelayAvg = Math.round(avg * 100) / 100;
|
||||||
|
this.tickDelayMax = Math.round(Math.max(...this.tickDelayTimes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
shouldTransform(): boolean {
|
shouldTransform(): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFPSColor(fps: number): string {
|
private getPerformanceColor(fps: number): string {
|
||||||
if (fps >= 55) return "fps-good";
|
if (fps >= 55) return "performance-good";
|
||||||
if (fps >= 30) return "fps-warning";
|
if (fps >= 30) return "performance-warning";
|
||||||
return "fps-bad";
|
return "performance-bad";
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -239,29 +299,39 @@ export class FPSDisplay extends LitElement implements Layer {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="fps-display ${this.isDragging ? "dragging" : ""}"
|
class="performance-overlay ${this.isDragging ? "dragging" : ""}"
|
||||||
style="${style}"
|
style="${style}"
|
||||||
@mousedown="${this.handleMouseDown}"
|
@mousedown="${this.handleMouseDown}"
|
||||||
>
|
>
|
||||||
<button class="close-button" @click="${this.handleClose}">×</button>
|
<button class="close-button" @click="${this.handleClose}">×</button>
|
||||||
<div class="fps-line">
|
<div class="performance-line">
|
||||||
FPS:
|
FPS:
|
||||||
<span class="${this.getFPSColor(this.currentFPS)}"
|
<span class="${this.getPerformanceColor(this.currentFPS)}"
|
||||||
>${this.currentFPS}</span
|
>${this.currentFPS}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="fps-line">
|
<div class="performance-line">
|
||||||
Avg (60s):
|
Avg (60s):
|
||||||
<span class="${this.getFPSColor(this.averageFPS)}"
|
<span class="${this.getPerformanceColor(this.averageFPS)}"
|
||||||
>${this.averageFPS}</span
|
>${this.averageFPS}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="fps-line">
|
<div class="performance-line">
|
||||||
Frame:
|
Frame:
|
||||||
<span class="${this.getFPSColor(1000 / this.frameTime)}"
|
<span class="${this.getPerformanceColor(1000 / this.frameTime)}"
|
||||||
>${this.frameTime}ms</span
|
>${this.frameTime}ms</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="performance-line">
|
||||||
|
Tick Exec:
|
||||||
|
<span>${this.tickExecutionAvg.toFixed(2)}ms</span>
|
||||||
|
(max: <span>${this.tickExecutionMax}ms</span>)
|
||||||
|
</div>
|
||||||
|
<div class="performance-line">
|
||||||
|
Tick Delay:
|
||||||
|
<span>${this.tickDelayAvg.toFixed(2)}ms</span>
|
||||||
|
(max: <span>${this.tickDelayMax}ms</span>)
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -405,7 +405,7 @@
|
|||||||
<news-modal></news-modal>
|
<news-modal></news-modal>
|
||||||
<game-left-sidebar></game-left-sidebar>
|
<game-left-sidebar></game-left-sidebar>
|
||||||
<flag-input-modal></flag-input-modal>
|
<flag-input-modal></flag-input-modal>
|
||||||
<fps-display></fps-display>
|
<performance-overlay></performance-overlay>
|
||||||
<div
|
<div
|
||||||
id="language-modal"
|
id="language-modal"
|
||||||
class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex justify-center items-center"
|
class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex justify-center items-center"
|
||||||
|
|||||||
@@ -130,9 +130,13 @@ export class GameRunner {
|
|||||||
this.currTurn++;
|
this.currTurn++;
|
||||||
|
|
||||||
let updates: GameUpdates;
|
let updates: GameUpdates;
|
||||||
|
let tickExecutionDuration: number = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const startTime = performance.now();
|
||||||
updates = this.game.executeNextTick();
|
updates = this.game.executeNextTick();
|
||||||
|
const endTime = performance.now();
|
||||||
|
tickExecutionDuration = endTime - startTime;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error("Game tick error:", error.message);
|
console.error("Game tick error:", error.message);
|
||||||
@@ -173,6 +177,7 @@ export class GameRunner {
|
|||||||
packedTileUpdates: new BigUint64Array(packedTileUpdates),
|
packedTileUpdates: new BigUint64Array(packedTileUpdates),
|
||||||
updates: updates,
|
updates: updates,
|
||||||
playerNameViewData: this.playerViewData,
|
playerNameViewData: this.playerViewData,
|
||||||
|
tickExecutionDuration: tickExecutionDuration,
|
||||||
});
|
});
|
||||||
this.isExecuting = false;
|
this.isExecuting = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface GameUpdateViewData {
|
|||||||
updates: GameUpdates;
|
updates: GameUpdates;
|
||||||
packedTileUpdates: BigUint64Array;
|
packedTileUpdates: BigUint64Array;
|
||||||
playerNameViewData: Record<string, NameViewData>;
|
playerNameViewData: Record<string, NameViewData>;
|
||||||
|
tickExecutionDuration?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ErrorUpdate {
|
export interface ErrorUpdate {
|
||||||
|
|||||||
Reference in New Issue
Block a user