refactor: 存档系统挪至渲染端

This commit is contained in:
unanmed 2026-09-17 13:29:34 +08:00
parent 89434537c8
commit 94ceb50cfe
12 changed files with 212 additions and 215 deletions

View File

@ -6,3 +6,6 @@ export function create() {
export * from './load';
export * from './material';
export * from './save';
export * from './types';

View File

@ -0,0 +1,2 @@
export * from './system';
export * from './types';

View File

@ -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 */

View File

@ -0,0 +1,142 @@
import { Dexie, Table } from 'dexie';
import { SaveCompression, ISaveableContent } from '@user/data-common';
export interface IGlobalTrasaction {
/** 全局存储对应的表 */
readonly table: Table<unknown, string>;
/**
*
* @param key
*/
get<T>(key: string): Promise<T>;
/**
*
* @param key
* @param value
*/
set(key: string, value: unknown): Promise<void>;
}
export interface ISaveSystemConfig {
/** 自动存档使用的压缩等级 */
autosaveLevel: SaveCompression;
/** 普通存档使用的压缩等级 */
commonSaveLevel: SaveCompression;
/** 可容忍的最大存档耗时,超过此值会抛出警告 */
saveTimeTolerance: number;
/** 可容忍的最大自动存档耗时,超过此值会抛出警告 */
autosaveTimeTolerance: number;
/** 自动存档栈最大大小 */
autosaveStackSize: number;
}
export interface ISaveRead {
/** 该存档的压缩等级 */
readonly compression: SaveCompression;
/** 该存档的数据 */
readonly data: Map<string, unknown>;
}
export interface ISaveSystem {
/** Dexie 数据库 */
readonly db: Dexie;
/**
*
* @param name
*/
init(name: string): void;
/**
*
* @param config
*/
config(config: Readonly<Partial<ISaveSystemConfig>>): void;
/**
* `undo` `redo`
* @param current `redo`
*/
undoAutosave(
current: Map<string, ISaveableContent<unknown>>
): ISaveRead | null;
/**
* `redo` `undo`
* @param current `undo`
*/
redoAutosave(
current: Map<string, ISaveableContent<unknown>>
): ISaveRead | null;
/**
*
*/
getUndoStack(): ISaveRead[];
/**
*
*/
getRedoStack(): ISaveRead[];
/**
*
* @param state
*/
autosave(state: Map<string, ISaveableContent<unknown>>): void;
/**
* `undo` `IndexedDB`
*/
saveAutosaveToDB(): Promise<void>;
/**
*
* @param id id
* @param state
*/
save(
id: number,
state: Map<string, ISaveableContent<unknown>>
): Promise<void>;
/**
* id
* @param id id
*/
load(id: number): Promise<ISaveRead | null>;
/**
*
* @param id id
*/
deleteSave(id: number): Promise<void>;
/**
*
*/
getLastSlot(): Promise<number>;
/**
*
* @param key
*/
getGlobal<T>(key: string): Promise<T | null>;
/**
*
* @param key
* @param value
*/
setGlobal(key: string, value: unknown): Promise<void>;
/**
*
* @param handle
*/
startGlobalTransaction<R>(
handle: (transaction: IGlobalTrasaction) => PromiseLike<R>
): Promise<R>;
}

View File

@ -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;
}

View File

@ -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<SoundIds>;
readonly bgmPlayer: IBGMPlayer<BgmIds>;
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;
}
}

View File

@ -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();

View File

@ -78,9 +78,9 @@ export class MapRenderer
assetWidth: number = 4096;
assetHeight: number = 4096;
layerState: IGameMap;
map: IGameMap | null = null;
/** 地图状态钩子控制器 */
private layerStateHook: IHookController<IGameMapHooks>;
private mapHook: IHookController<IGameMapHooks> | 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[] {

View File

@ -339,7 +339,7 @@ export interface IMapRenderer {
/** 顶点数组生成器 */
readonly vertex: IMapVertexGenerator;
/** 使用的地图状态对象 */
readonly layerState: IGameMap;
readonly map: IGameMap | null;
/** 地图宽度 */
readonly mapWidth: number;

View File

@ -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<SoundIds>;
/** BGM 播放器 */
readonly bgmPlayer: IBGMPlayer<BgmIds>;
/**
*
* @param state
*/
bindDataState(state: ICoreState): void;
/**
*
*/
getDataState(): ICoreState;
}

View File

@ -1,2 +1 @@
export * from './system';
export * from './types';

View File

@ -1,5 +1,3 @@
import { Dexie, Table } from 'dexie';
export const enum SaveCompression {
/** 不进行压缩,仅提取必要数据 */
NoCompression,
@ -23,143 +21,3 @@ export interface ISaveableContent<T> {
*/
loadState(state: T, compression: SaveCompression): void;
}
export interface IGlobalTrasaction {
/** 全局存储对应的表 */
readonly table: Table<unknown, string>;
/**
*
* @param key
*/
get<T>(key: string): Promise<T>;
/**
*
* @param key
* @param value
*/
set(key: string, value: unknown): Promise<void>;
}
export interface ISaveSystemConfig {
/** 自动存档使用的压缩等级 */
autosaveLevel: SaveCompression;
/** 普通存档使用的压缩等级 */
commonSaveLevel: SaveCompression;
/** 可容忍的最大存档耗时,超过此值会抛出警告 */
saveTimeTolerance: number;
/** 可容忍的最大自动存档耗时,超过此值会抛出警告 */
autosaveTimeTolerance: number;
/** 自动存档栈最大大小 */
autosaveStackSize: number;
}
export interface ISaveRead {
/** 该存档的压缩等级 */
readonly compression: SaveCompression;
/** 该存档的数据 */
readonly data: Map<string, unknown>;
}
export interface ISaveSystem {
/** Dexie 数据库 */
readonly db: Dexie;
/**
*
* @param name
*/
init(name: string): void;
/**
*
* @param config
*/
config(config: Readonly<Partial<ISaveSystemConfig>>): void;
/**
* `undo` `redo`
* @param current `redo`
*/
undoAutosave(
current: Map<string, ISaveableContent<unknown>>
): ISaveRead | null;
/**
* `redo` `undo`
* @param current `undo`
*/
redoAutosave(
current: Map<string, ISaveableContent<unknown>>
): ISaveRead | null;
/**
*
*/
getUndoStack(): ISaveRead[];
/**
*
*/
getRedoStack(): ISaveRead[];
/**
*
* @param state
*/
autosave(state: Map<string, ISaveableContent<unknown>>): void;
/**
* `undo` `IndexedDB`
*/
saveAutosaveToDB(): Promise<void>;
/**
*
* @param id id
* @param state
*/
save(
id: number,
state: Map<string, ISaveableContent<unknown>>
): Promise<void>;
/**
* id
* @param id id
*/
load(id: number): Promise<ISaveRead | null>;
/**
*
* @param id id
*/
deleteSave(id: number): Promise<void>;
/**
*
*/
getLastSlot(): Promise<number>;
/**
*
* @param key
*/
getGlobal<T>(key: string): Promise<T | null>;
/**
*
* @param key
* @param value
*/
setGlobal(key: string, value: unknown): Promise<void>;
/**
*
* @param handle
*/
startGlobalTransaction<R>(
handle: (transaction: IGlobalTrasaction) => PromiseLike<R>
): Promise<R>;
}