From 6a16d39d8868cfcad769df923a6b56dfe08d1c3b Mon Sep 17 00:00:00 2001 From: "azykov@mail.ru" Date: Sat, 13 Jun 2026 18:34:54 +0300 Subject: [PATCH] faster game state --- webclient/src/state/gameState.ts | 64 ++++++++++++++++++------------- webclient/src/state/rootState.ts | 1 + webclient/src/state/worldState.ts | 38 ++++++++++++------ 3 files changed, 66 insertions(+), 37 deletions(-) diff --git a/webclient/src/state/gameState.ts b/webclient/src/state/gameState.ts index 9faada7..a868922 100644 --- a/webclient/src/state/gameState.ts +++ b/webclient/src/state/gameState.ts @@ -1,6 +1,6 @@ -import { makeAutoObservable, reaction, toJS } from "mobx"; +import { autorun, makeAutoObservable, toJS } from "mobx"; import type { WorldState } from "./worldState"; -import type { Game, Pos3, RuntimeGameScene, ScriptContext, ScriptCoroutineContext } from "../types"; +import type { Game, GameObjectInstance, Pos3, RuntimeGameScene, ScriptContext, ScriptCoroutineContext } from "../types"; import type { CameraProps } from "@react-three/fiber"; import type { ScriptGameEventBus } from "../scripts/eventBus"; import { GameFactory } from "../model/gameFactory"; @@ -18,12 +18,31 @@ export class GameState { private coroutines = new ScriptCoroutines(); private eventBus: ScriptGameEventBus; + private _buildGameObjects() { + return Object.fromEntries( + Object.entries(this.scene.objects).map(([id, obj]) => [id, { + id: obj.id, + typeId: obj.typeId, + physics: obj.physics, + gravityScale: obj.gravityScale, + position: toJS(obj.position), + rotation: toJS(obj.rotation), + scale: toJS(obj.scale), + version: obj.version, + linearVelocity: toJS(obj.linearVelocity), + angularVelocity: toJS(obj.angularVelocity), + } satisfies GameObjectInstance]) + ) as unknown as RuntimeGameScene['objects']; + } + private startAutoSave() { - return reaction( - () => (this.asGame), - (data) => this.saveData(data), - { delay: 5000 } - ); + return autorun(() => { + GameFactory.save({ + paused: this.isPaused, + time: this.time, + scene: { playerObjectId: this.scene.playerObjectId, objects: this._buildGameObjects() }, + }); + }, { delay: 5000 }); } // @ts-expect-error -- for future use @@ -46,12 +65,16 @@ export class GameState { this.isFirstTick = true; this.scene = game.scene; - this._stopAutoSave = this.startAutoSave(); makeAutoObservable(this); + this._stopAutoSave = this.startAutoSave(); this.eventBus = GameFactory.evalGameScripts(rawWorld, this.scene, this.coroutines); } + public dispose(): void { + this._stopAutoSave(); + } + public emitEvent(event: string, context: Omit = {}) { this.eventBus.emit( event, @@ -63,14 +86,6 @@ export class GameState { ); } - public get asGame(): Game { - return { - paused: toJS(this.isPaused), - time: toJS(this.time), - scene: toJS(this.scene), - }; - } - public setGame(value: Game) { this.isPaused = value.paused; this.time = value.time; @@ -101,16 +116,13 @@ export class GameState { // }); // } - private saveData(data: Game): void { - console.log('Saving game...'); - const stack = new Error('Saving game...').stack!.split('\n').slice(1); - const { ...debug } = toJS(data); - console.dir({ stack, debug }); - GameFactory.save(data); - } - public save(): void { - this.saveData(this.asGame); + console.log('Saving game...'); + GameFactory.save({ + paused: this.isPaused, + time: this.time, + scene: { playerObjectId: this.scene.playerObjectId, objects: this._buildGameObjects() }, + }); } public resume(): void { @@ -138,4 +150,4 @@ export class GameState { this.emitEvent('gameStart'); } } -} \ No newline at end of file +} diff --git a/webclient/src/state/rootState.ts b/webclient/src/state/rootState.ts index caf6abe..5e5d4a2 100644 --- a/webclient/src/state/rootState.ts +++ b/webclient/src/state/rootState.ts @@ -44,6 +44,7 @@ export class RootState { return; this.game.pause(); + this.game.dispose(); this.game = undefined; } diff --git a/webclient/src/state/worldState.ts b/webclient/src/state/worldState.ts index 9c1da66..ee8a679 100644 --- a/webclient/src/state/worldState.ts +++ b/webclient/src/state/worldState.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, reaction, toJS } from "mobx"; +import { autorun, makeAutoObservable, toJS } from "mobx"; import { WorldFactory } from "../model/worldFactory"; import { type ObjectInstance, type ObjectType, type World, type VoxelType, type RuntimeWorld } from "../types"; import { state } from "./rootState"; @@ -10,8 +10,32 @@ import { populateRuntimeWorld } from "../utils/runtime"; export class WorldState { public data: RuntimeWorld = WorldFactory.create(); + private _buildSave(): World { + const d = this.data; + return { + objectTypes: toJS(d.objectTypes), + voxelTypes: toJS(d.voxelTypes), + editorCamera: toJS(d.editorCamera), + gameRules: toJS(d.gameRules), + initialScene: { + playerObjectId: d.initialScene.playerObjectId, + objects: Object.fromEntries( + Object.entries(d.initialScene.objects).map(([id, obj]) => [id, { + id: obj.id, + typeId: obj.typeId, + physics: obj.physics, + gravityScale: obj.gravityScale, + position: toJS(obj.position), + rotation: toJS(obj.rotation), + scale: toJS(obj.scale), + } satisfies ObjectInstance]) + ), + }, + }; + } + private startAutoSave() { - return reaction(() => toJS(this.data), (data) => this.saveData(data), { delay: 500 }); + return autorun(() => WorldFactory.save(this._buildSave()), { delay: 500 }); } private withoutAutoSave(fn: () => void) { @@ -103,16 +127,8 @@ export class WorldState { state.worldEditor.resetSelection(); } - private saveData(data: World): void { - console.log('Saving world...'); - const stack = new Error('Saving world...').stack!.split('\n').slice(1); - const { voxelTypes: _vts, initialScene: _is, ...debug } = toJS(data); - console.dir({ stack, debug }); - WorldFactory.save(toJS(this.data)); - } - public save(): void { - this.saveData(this.data); + WorldFactory.save(this._buildSave()); } public getObjectTypeById(id: string): ObjectType | undefined {