diff --git a/packages-user/client-base/src/index.ts b/packages-user/client-base/src/index.ts index afd1253..e72f62d 100644 --- a/packages-user/client-base/src/index.ts +++ b/packages-user/client-base/src/index.ts @@ -6,3 +6,6 @@ export function create() { export * from './load'; export * from './material'; +export * from './save'; + +export * from './types'; diff --git a/packages-user/client-base/src/save/index.ts b/packages-user/client-base/src/save/index.ts new file mode 100644 index 0000000..213e667 --- /dev/null +++ b/packages-user/client-base/src/save/index.ts @@ -0,0 +1,2 @@ +export * from './system'; +export * from './types'; diff --git a/packages-user/data-common/src/save/system.ts b/packages-user/client-base/src/save/system.ts similarity index 98% rename from packages-user/data-common/src/save/system.ts rename to packages-user/client-base/src/save/system.ts index 1501a70..08e6cc6 100644 --- a/packages-user/data-common/src/save/system.ts +++ b/packages-user/client-base/src/save/system.ts @@ -4,11 +4,10 @@ import { IGlobalTrasaction, ISaveRead, ISaveSystem, - ISaveSystemConfig, - ISaveableContent, - SaveCompression + ISaveSystemConfig } from './types'; import { isNil } from 'lodash-es'; +import { SaveCompression, ISaveableContent } from '@user/data-common'; interface ISaveRecord { /** 存档 id */ diff --git a/packages-user/client-base/src/save/types.ts b/packages-user/client-base/src/save/types.ts new file mode 100644 index 0000000..fef7ffd --- /dev/null +++ b/packages-user/client-base/src/save/types.ts @@ -0,0 +1,142 @@ +import { Dexie, Table } from 'dexie'; +import { SaveCompression, ISaveableContent } from '@user/data-common'; + +export interface IGlobalTrasaction { + /** 全局存储对应的表 */ + readonly table: Table; + + /** + * 获取指定键值对应的数据 + * @param key 全局键值 + */ + get(key: string): Promise; + + /** + * 设置指定键值存储的数据 + * @param key 全局键值 + * @param value 存储数据 + */ + set(key: string, value: unknown): Promise; +} + +export interface ISaveSystemConfig { + /** 自动存档使用的压缩等级 */ + autosaveLevel: SaveCompression; + /** 普通存档使用的压缩等级 */ + commonSaveLevel: SaveCompression; + /** 可容忍的最大存档耗时,超过此值会抛出警告 */ + saveTimeTolerance: number; + /** 可容忍的最大自动存档耗时,超过此值会抛出警告 */ + autosaveTimeTolerance: number; + /** 自动存档栈最大大小 */ + autosaveStackSize: number; +} + +export interface ISaveRead { + /** 该存档的压缩等级 */ + readonly compression: SaveCompression; + /** 该存档的数据 */ + readonly data: Map; +} + +export interface ISaveSystem { + /** Dexie 数据库 */ + readonly db: Dexie; + + /** + * 初始化存档数据库 + * @param name 数据库名称 + */ + init(name: string): void; + + /** + * 配置此存档系统 + * @param config 配置对象 + */ + config(config: Readonly>): void; + + /** + * 从 `undo` 栈读取上一个自动存档,然后将当前状态加入 `redo` 栈 + * @param current 当前游戏状态,需要加入 `redo` 栈 + */ + undoAutosave( + current: Map> + ): ISaveRead | null; + + /** + * 从 `redo` 栈读取自动存档,并将当前状态加入 `undo` 栈 + * @param current 当前游戏状态,需要加入 `undo` 栈 + */ + redoAutosave( + current: Map> + ): ISaveRead | null; + + /** + * 获取当前的撤回栈 + */ + getUndoStack(): ISaveRead[]; + + /** + * 获取当前的重做栈 + */ + getRedoStack(): ISaveRead[]; + + /** + * 进行自动存档,加入撤回栈 + * @param state 状态对象 + */ + autosave(state: Map>): void; + + /** + * 将 `undo` 栈顶的自动存档真正存入 `IndexedDB` + */ + saveAutosaveToDB(): Promise; + + /** + * 将状态对象存入存档 + * @param id 存档 id,用于建立存档索引及查询 + * @param state 状态对象 + */ + save( + id: number, + state: Map> + ): Promise; + + /** + * 根据 id 读取指定存档 + * @param id 存档 id + */ + load(id: number): Promise; + + /** + * 删除指定存档 + * @param id 存档 id + */ + deleteSave(id: number): Promise; + + /** + * 获取最后一次存档的存档栏位 + */ + getLastSlot(): Promise; + + /** + * 获取指定键值对应的全局存储 + * @param key 全局键值 + */ + getGlobal(key: string): Promise; + + /** + * 设置指定键值对应的全局存储 + * @param key 全局键值 + * @param value 存储数据 + */ + setGlobal(key: string, value: unknown): Promise; + + /** + * 进行全局存储的事务处理,适用于多内容查询与设置,当出现错误时其中的任何写入操作都不会真正存储 + * @param handle 事务处理函数 + */ + startGlobalTransaction( + handle: (transaction: IGlobalTrasaction) => PromiseLike + ): Promise; +} diff --git a/packages-user/client-base/src/types.ts b/packages-user/client-base/src/types.ts new file mode 100644 index 0000000..6878bf8 --- /dev/null +++ b/packages-user/client-base/src/types.ts @@ -0,0 +1,12 @@ +import { ISaveSystem } from '@user/data-common'; +import { ICoreState } from '@user/data-state'; + +export interface IClientBase extends ICoreState { + /** 存档系统 */ + readonly save: ISaveSystem; +} + +export interface IClientBaseExtended { + /** 当前对象的渲染端基本层对象(Layer 4 对象) */ + readonly state: IClientBase; +} diff --git a/packages-user/client-modules/src/client.ts b/packages-user/client-modules/src/client.ts index 14b538a..1a579aa 100644 --- a/packages-user/client-modules/src/client.ts +++ b/packages-user/client-modules/src/client.ts @@ -1,4 +1,4 @@ -import { ICoreState } from '@user/data-state'; +import { CoreState } from '@user/data-state'; import { IClientCore } from './types'; import { IMotaAudioContext, @@ -15,7 +15,9 @@ import { IAutotileProcessor, MotaAssetsLoader, MaterialManager, - AutotileProcessor + AutotileProcessor, + ISaveSystem, + SaveSystem } from '@user/client-base'; import { IMapRenderer, @@ -41,9 +43,12 @@ import { import { loading } from '@user/data-base'; import { fallbackLoad } from './fallback/load'; -export class ClientCore implements IClientCore { - readonly loader: IMotaAssetsLoader; +export class ClientCore extends CoreState implements IClientCore { + // Layer 4 渲染基础层 + readonly save: ISaveSystem; + // Layer 5 渲染顶层 + readonly loader: IMotaAssetsLoader; readonly materials: IMaterialManager; readonly autotile: IAutotileProcessor; @@ -57,7 +62,14 @@ export class ClientCore implements IClientCore { readonly soundPlayer: ISoundPlayer; readonly bgmPlayer: IBGMPlayer; - constructor(public data: ICoreState) { + constructor() { + super(); + + //#region Layer 4 + + this.save = new SaveSystem(); + this.save.init(`@game/${core.firstData.name}`); + //#region 音频系统 this.audioContext = new MotaAudioContext(); @@ -74,8 +86,8 @@ export class ClientCore implements IClientCore { //#endregion this.loader = new MotaAssetsLoader( - data.loadProgress, - data.dataLoader, + this.loadProgress, + this.dataLoader, this.audioContext, this.soundPlayer, this.materials @@ -114,12 +126,12 @@ export class ClientCore implements IClientCore { // 使用分频器,用户可以在设置中调整,如果设备性能较差调高分频有助于提高性能表现 excitaion: excitationDivider }); - this.mainMapRenderer = new MapRenderer(this.materials, data.maps); + this.mainMapRenderer = new MapRenderer(this.materials); this.mainMapExtension = new MapExtensionManager(this.mainMapRenderer); // 兼容层 loading.once('assetBuilt', () => { - this.createMainExtension(); + this.initMapExtensions(); }); //#endregion @@ -128,24 +140,16 @@ export class ClientCore implements IClientCore { /** * 进行地图渲染拓展初始化 */ - private async createMainExtension() { + private async initMapExtensions() { // 算是一种妥协吧,等之后加载系统重构之后应该会清晰很多 await this.materials.trackedAsset.then(); this.mainMapRenderer.useAsset(this.materials.trackedAsset); - const layer = this.data.maps.getLayerByAlias('event'); - if (layer) { - this.mainMapExtension.addHero(this.data.hero.mover, layer); - this.mainMapExtension.addDoor(layer); - } + // const layer = this.maps.getLayerByAlias('event'); + // if (layer) { + // this.mainMapExtension.addHero(this.hero.mover, layer); + // this.mainMapExtension.addDoor(layer); + // } this.mainMapExtension.addText(); } - - bindDataState(state: ICoreState): void { - this.data = state; - } - - getDataState(): ICoreState { - return this.data; - } } diff --git a/packages-user/client-modules/src/core.ts b/packages-user/client-modules/src/core.ts index 8878f28..8cc1770 100644 --- a/packages-user/client-modules/src/core.ts +++ b/packages-user/client-modules/src/core.ts @@ -1,7 +1,6 @@ -import { state } from '@user/data-state'; import { ClientCore } from './client'; // TODO: 逐渐弱化 ClientCore 的单例概念,每个接口都通过参数传入 IClientCore 对象 /** 客户端实例 */ -export const client = new ClientCore(state); +export const client = new ClientCore(); diff --git a/packages-user/client-modules/src/render/map/renderer.ts b/packages-user/client-modules/src/render/map/renderer.ts index d086922..0694b9b 100644 --- a/packages-user/client-modules/src/render/map/renderer.ts +++ b/packages-user/client-modules/src/render/map/renderer.ts @@ -78,9 +78,9 @@ export class MapRenderer assetWidth: number = 4096; assetHeight: number = 4096; - layerState: IGameMap; + map: IGameMap | null = null; /** 地图状态钩子控制器 */ - private layerStateHook: IHookController; + private mapHook: IHookController | null = null; /** 排序后的图层 */ private sortedLayers: IMapLayer[] = []; @@ -205,13 +205,8 @@ export class MapRenderer /** * 创建地图渲染器 * @param manager 素材管理器 - * @param gl 画布 WebGL2 上下文 - * @param transform 视角变换矩阵 */ - constructor( - readonly manager: IMaterialManager, - layerState: IGameMap - ) { + constructor(readonly manager: IMaterialManager) { this.movingIndexPool.push( ...Array.from({ length: this.movingCount }, (_, i) => i).reverse() ); @@ -219,11 +214,6 @@ export class MapRenderer this.gl = this.canvas.getContext('webgl2')!; this.transform = new Transform(); this.transform.bind(this); - this.layerState = layerState; - this.layerStateHook = layerState.addHook( - new RendererLayerStateHook(this) - ); - this.layerStateHook.load(); // 上下文初始化要依赖于 offsetPool,因此提前调用 const offsetPool = this.getOffsetPool(); this.offsetPool = offsetPool; @@ -414,40 +404,42 @@ export class MapRenderer * 图层排序 */ private sortLayer() { - this.sortedLayers = [...this.layerState.layerList].sort((a, b) => { + if (!this.map) return; + this.sortedLayers = [...this.map.layerList].sort((a, b) => { return a.zIndex - b.zIndex; }); this.sortedLayers.forEach((v, i) => this.layerIndexMap.set(v, i)); } updateLayerList() { + if (!this.map) return; this.sortLayer(); this.resizeLayer(); - this.layerCount = this.layerState.layerList.size; + this.layerCount = this.map.layerList.size; this.vertex.updateLayerArray(); } - setLayerState(layerState: IGameMap): void { - if (layerState === this.layerState) return; - this.layerStateHook.unload(); - this.layerState = layerState; - this.layerStateHook = layerState.addHook( - new RendererLayerStateHook(this) - ); - this.layerStateHook.load(); + setLayerState(map: IGameMap): void { + if (map === this.map) return; + this.mapHook?.unload(); + this.map = map; + this.mapHook = map.addHook(new RendererLayerStateHook(this)); + this.mapHook.load(); this.sortLayer(); this.resizeLayer(); - this.layerCount = layerState.layerList.size; + this.layerCount = map.layerList.size; this.vertex.updateLayerArray(); this.vertex.resizeMap(); } getLayer(identifier: string): IMapLayer | null { - return this.layerState.getLayerByAlias(identifier) ?? null; + if (!this.map) return null; + return this.map.getLayerByAlias(identifier) ?? null; } hasLayer(layer: IMapLayer): boolean { - return this.layerState.hasLayer(layer); + if (!this.map) return false; + return this.map.hasLayer(layer); } getSortedLayer(): IMapLayer[] { diff --git a/packages-user/client-modules/src/render/map/types.ts b/packages-user/client-modules/src/render/map/types.ts index 3bff2df..4d2486b 100644 --- a/packages-user/client-modules/src/render/map/types.ts +++ b/packages-user/client-modules/src/render/map/types.ts @@ -339,7 +339,7 @@ export interface IMapRenderer { /** 顶点数组生成器 */ readonly vertex: IMapVertexGenerator; /** 使用的地图状态对象 */ - readonly layerState: IGameMap; + readonly map: IGameMap | null; /** 地图宽度 */ readonly mapWidth: number; diff --git a/packages-user/client-modules/src/types.ts b/packages-user/client-modules/src/types.ts index 13a3a62..de5ef4a 100644 --- a/packages-user/client-modules/src/types.ts +++ b/packages-user/client-modules/src/types.ts @@ -1,18 +1,16 @@ import { IRenderTreeRoot } from '@motajs/render'; -import { ICoreState } from '@user/data-state'; import { IMapExtensionManager, IMapRenderer } from './render/map'; import { IBGMPlayer, IMotaAudioContext, ISoundPlayer } from '@motajs/audio'; import { IAutotileProcessor, IMaterialManager, - IMotaAssetsLoader + IMotaAssetsLoader, + IClientBase } from '@user/client-base'; import { IExcitation, IExcitationDivider } from '@motajs/animate'; -export interface IClientCore { - /** 数据端状态对象 */ - readonly data: ICoreState; - +export interface IClientCore extends IClientBase { + /** 渲染端加载对象 */ readonly loader: IMotaAssetsLoader; /** 素材管理器 */ @@ -39,15 +37,4 @@ export interface IClientCore { readonly soundPlayer: ISoundPlayer; /** BGM 播放器 */ readonly bgmPlayer: IBGMPlayer; - - /** - * 绑定数据端状态 - * @param state 数据端状态 - */ - bindDataState(state: ICoreState): void; - - /** - * 获取当前绑定的数据端状态对象 - */ - getDataState(): ICoreState; } diff --git a/packages-user/data-common/src/save/index.ts b/packages-user/data-common/src/save/index.ts index 213e667..fcb073f 100644 --- a/packages-user/data-common/src/save/index.ts +++ b/packages-user/data-common/src/save/index.ts @@ -1,2 +1 @@ -export * from './system'; export * from './types'; diff --git a/packages-user/data-common/src/save/types.ts b/packages-user/data-common/src/save/types.ts index 669a750..06b37a6 100644 --- a/packages-user/data-common/src/save/types.ts +++ b/packages-user/data-common/src/save/types.ts @@ -1,5 +1,3 @@ -import { Dexie, Table } from 'dexie'; - export const enum SaveCompression { /** 不进行压缩,仅提取必要数据 */ NoCompression, @@ -23,143 +21,3 @@ export interface ISaveableContent { */ loadState(state: T, compression: SaveCompression): void; } - -export interface IGlobalTrasaction { - /** 全局存储对应的表 */ - readonly table: Table; - - /** - * 获取指定键值对应的数据 - * @param key 全局键值 - */ - get(key: string): Promise; - - /** - * 设置指定键值存储的数据 - * @param key 全局键值 - * @param value 存储数据 - */ - set(key: string, value: unknown): Promise; -} - -export interface ISaveSystemConfig { - /** 自动存档使用的压缩等级 */ - autosaveLevel: SaveCompression; - /** 普通存档使用的压缩等级 */ - commonSaveLevel: SaveCompression; - /** 可容忍的最大存档耗时,超过此值会抛出警告 */ - saveTimeTolerance: number; - /** 可容忍的最大自动存档耗时,超过此值会抛出警告 */ - autosaveTimeTolerance: number; - /** 自动存档栈最大大小 */ - autosaveStackSize: number; -} - -export interface ISaveRead { - /** 该存档的压缩等级 */ - readonly compression: SaveCompression; - /** 该存档的数据 */ - readonly data: Map; -} - -export interface ISaveSystem { - /** Dexie 数据库 */ - readonly db: Dexie; - - /** - * 初始化存档数据库 - * @param name 数据库名称 - */ - init(name: string): void; - - /** - * 配置此存档系统 - * @param config 配置对象 - */ - config(config: Readonly>): void; - - /** - * 从 `undo` 栈读取上一个自动存档,然后将当前状态加入 `redo` 栈 - * @param current 当前游戏状态,需要加入 `redo` 栈 - */ - undoAutosave( - current: Map> - ): ISaveRead | null; - - /** - * 从 `redo` 栈读取自动存档,并将当前状态加入 `undo` 栈 - * @param current 当前游戏状态,需要加入 `undo` 栈 - */ - redoAutosave( - current: Map> - ): ISaveRead | null; - - /** - * 获取当前的撤回栈 - */ - getUndoStack(): ISaveRead[]; - - /** - * 获取当前的重做栈 - */ - getRedoStack(): ISaveRead[]; - - /** - * 进行自动存档,加入撤回栈 - * @param state 状态对象 - */ - autosave(state: Map>): void; - - /** - * 将 `undo` 栈顶的自动存档真正存入 `IndexedDB` - */ - saveAutosaveToDB(): Promise; - - /** - * 将状态对象存入存档 - * @param id 存档 id,用于建立存档索引及查询 - * @param state 状态对象 - */ - save( - id: number, - state: Map> - ): Promise; - - /** - * 根据 id 读取指定存档 - * @param id 存档 id - */ - load(id: number): Promise; - - /** - * 删除指定存档 - * @param id 存档 id - */ - deleteSave(id: number): Promise; - - /** - * 获取最后一次存档的存档栏位 - */ - getLastSlot(): Promise; - - /** - * 获取指定键值对应的全局存储 - * @param key 全局键值 - */ - getGlobal(key: string): Promise; - - /** - * 设置指定键值对应的全局存储 - * @param key 全局键值 - * @param value 存储数据 - */ - setGlobal(key: string, value: unknown): Promise; - - /** - * 进行全局存储的事务处理,适用于多内容查询与设置,当出现错误时其中的任何写入操作都不会真正存储 - * @param handle 事务处理函数 - */ - startGlobalTransaction( - handle: (transaction: IGlobalTrasaction) => PromiseLike - ): Promise; -}