diff --git a/assets/libs/ecs/ECS.ts b/assets/libs/ecs/ECS.ts index 4528d44..e528ef2 100644 --- a/assets/libs/ecs/ECS.ts +++ b/assets/libs/ecs/ECS.ts @@ -186,16 +186,14 @@ export namespace ecs { // 缓存中没有同类实体,则创建一个新的 if (!entity) { entity = new ctor(); - (entity as ECSEntity).eid = ECSModel.eid++; // 实体唯一编号 - (entity as ECSEntity).name = entityName; + entity.eid = ECSModel.eid++; // 实体唯一编号 + entity.name = entityName; } // 触发实体初始化逻辑 const entityWithInit = entity as ECSEntity & { init?: () => void }; - if (entityWithInit.init) { - entityWithInit.isValid = true; - entityWithInit.init(); - } + entity.isValid = true; // 无论新建还是复用,都标记为有效 + if (entityWithInit.init) entityWithInit.init(); ECSModel.eid2Entity.set((entity as ECSEntity).eid, entity as ECSEntity); return entity; @@ -267,7 +265,7 @@ export namespace ecs { /** 创建实体 */ function createEntity(): E { const entity = new Entity(); - (entity as ECSEntity).eid = ECSModel.eid++; // 实体id也是有限的资源 + entity.eid = ECSModel.eid++; // 实体id也是有限的资源 ECSModel.eid2Entity.set((entity as ECSEntity).eid, entity as ECSEntity); return entity as E; } @@ -278,7 +276,7 @@ export namespace ecs { */ function createEntityWithComp(ctor: CompCtor): T { const entity = createEntity(); - return (entity as ECSEntity).add(ctor); + return entity.add(ctor); } //#region 过滤器 diff --git a/assets/libs/ecs/ECSEntity.ts b/assets/libs/ecs/ECSEntity.ts index 3e6171a..f96c06e 100644 --- a/assets/libs/ecs/ECSEntity.ts +++ b/assets/libs/ecs/ECSEntity.ts @@ -76,8 +76,14 @@ function destroyEntity(entity: ECSEntity): void { } // 限制对象池大小,防止内存无限增长 if (entitys.length < ECSModel.MAX_ENTITY_POOL_SIZE) { + // 池未满:保留 Uint32Array,仅清空位图,复用时零开销 + entity.getMask().clear(); entitys.push(entity); } + else { + // 池已满:实体真正丢弃,将 Uint32Array 回收到 MaskPool 供后续复用 + entity.getMask().destroy(); + } ECSModel.eid2Entity.delete(entity.eid); } else { @@ -342,9 +348,6 @@ export class ECSEntity { // 清理缓存的组件对象,防止内存泄漏 this.compTid2Obj.clear(); - - // 回收 mask 到对象池 - this.mask.destroy(); } private _remove(comp: CompType): void { diff --git a/assets/libs/model-view/VMBase.ts b/assets/libs/model-view/VMBase.ts index 6be9c07..63f6f76 100644 --- a/assets/libs/model-view/VMBase.ts +++ b/assets/libs/model-view/VMBase.ts @@ -28,7 +28,7 @@ export class VMBase extends Component { templateMode = false; /** watch 多路径 */ - protected watchPathArr: string[] = []; + watchPathArr: string[] = []; /** 储存模板多路径的值 */ protected templateValueArr: any[] = []; diff --git a/assets/module/common/CCView.ts b/assets/module/common/CCView.ts index 06aa6db..6e498f8 100644 --- a/assets/module/common/CCView.ts +++ b/assets/module/common/CCView.ts @@ -83,18 +83,24 @@ export abstract class CCView extends GameComponent implement * 需要绑定的私有数据 * 注意:子类应该显式初始化此属性 */ - protected data?: any; + protected data?: object; /** * 组件加载时调用 * 注意:如果子类需要覆盖此方法,必须调用 super.onLoad() */ onLoad() { - // 只有启用 MVVM 且数据存在时才初始化 VM - // 使用位运算优化布尔判断(虽然现代引擎已优化,但这是极致优化) - if (this.mvvm && this.data !== undefined && this.data !== null) { - this.initializeVM(); + if (!this.mvvm) return; + + // onBind 语义为"绑定初始化",与 data 是否存在解耦,始终调用 + this.onBind(); + + if (this.data === undefined || this.data === null) { + console.warn(`[CCView] ${this.constructor.name}: mvvm=true 但 data 未定义,VM 绑定已跳过`); + return; } + + this.initializeVM(); } /** @@ -102,19 +108,13 @@ export abstract class CCView extends GameComponent implement * @private */ private initializeVM() { - this.onBind(); - - // 优化:使用模板字符串(现代引擎优化更好),并缓存 uuid + // 使用 split/join 替换 uuid 中所有的点,避免 VM.add 内部校验失败 const uuid = this.node.uuid; - // 优化:只在必要时替换点号,使用更快的 replaceAll(如果支持) - this.tag = `_temp<${uuid.replace('.', '')}>`; + this.tag = `_temp<${uuid.split('.').join('')}>`; VM.add(this.data!, this.tag); - // 搜寻所有节点:找到 watch path const comps = this.getVMComponents(); const len = comps.length; - - // 优化:避免属性查找,缓存 tag const tag = this.tag; for (let i = 0; i < len; i++) { this.replaceVMPath(comps[i], tag); @@ -139,70 +139,37 @@ export abstract class CCView extends GameComponent implement * 替换 VM 组件的路径 * @private */ - private replaceVMPath(comp: Component, tag: string) { - // 优化:使用 any 类型避免多次类型转换 - const vmComp: any = comp; - const path: string = vmComp.watchPath; - - // 优化:使用严格相等避免类型转换 - if (vmComp.templateMode === true) { - const pathArr: string[] = vmComp.watchPathArr; - if (pathArr) { - const len = pathArr.length; - // 优化:避免在循环中重复声明变量 - for (let i = 0; i < len; i++) { - // 优化:直接修改数组元素,避免中间变量 - pathArr[i] = pathArr[i].replace('*', tag); - } - } - } - else if (path) { - // 优化:使用 startsWith 比 split 更快 - // 优化:避免不必要的 split 操作 - if (path.charCodeAt(0) === 42) { // 42 是 '*' 的字符码 - vmComp.watchPath = path.replace('*', tag); + private replaceVMPath(comp: VMBase, tag: string) { + if (comp.templateMode) { + const pathArr = comp.watchPathArr; + const len = pathArr.length; + for (let i = 0; i < len; i++) { + pathArr[i] = pathArr[i].replace('*', tag); } + } else if (comp.watchPath.charCodeAt(0) === 42) { // 42 是 '*' 的字符码 + comp.watchPath = comp.watchPath.replace('*', tag); } } /** - * 优化的遍历节点,获取 VM 组件 + * 获取当前节点下属于本 CCView 管辖的 VMBase 组件(排除嵌套启用 MVVM 的子 CCView 管辖范围) * @private */ - private getVMComponents(): Component[] { + private getVMComponents(): VMBase[] { const comps = this.node.getComponentsInChildren(VMBase); + if (comps.length === 0) return comps; - // 优化:提前返回,避免不必要的计算 - if (comps.length === 0) { - return comps; - } - - // 优化:只在有嵌套 CCView 时才获取 parents const parents = this.node.getComponentsInChildren(CCView); - - // 优化:使用数组长度判断,避免创建新数组 - let hasNested = false; - const len = parents.length; const myUuid = this.uuid; + // 单次遍历:同时判断是否有嵌套 MVVM CCView,并构建过滤集合 + const filterSet = new Set(); + const len = parents.length; + let hasNested = false; for (let i = 0; i < len; i++) { const p = parents[i]; if (p.uuid !== myUuid && p.mvvm) { hasNested = true; - break; - } - } - - // 如果没有嵌套的启用了 MVVM 的 CCView,直接返回所有组件 - if (!hasNested) { - return comps; - } - - // 优化:使用 Set 过滤,但避免多次遍历 - const filterSet = new Set(); - for (let i = 0; i < len; i++) { - const p = parents[i]; - if (p.uuid !== myUuid && p.mvvm) { const childComps = p.node.getComponentsInChildren(VMBase); const childLen = childComps.length; for (let j = 0; j < childLen; j++) { @@ -211,15 +178,15 @@ export abstract class CCView extends GameComponent implement } } - // 优化:使用传统 for 循环比 filter 更快 - const result: Component[] = []; + if (!hasNested) return comps; + + const result: VMBase[] = []; const compsLen = comps.length; for (let i = 0; i < compsLen; i++) { if (!filterSet.has(comps[i])) { result.push(comps[i]); } } - return result; } //#endregion @@ -261,7 +228,6 @@ export abstract class CCView extends GameComponent implement this.tag = undefined; } - // @ts-ignore - 优化:显式清空引用,帮助 GC this.data = undefined; } diff --git a/assets/module/config/GameConfig.ts b/assets/module/config/GameConfig.ts index cf7fe49..f6835a5 100644 --- a/assets/module/config/GameConfig.ts +++ b/assets/module/config/GameConfig.ts @@ -16,6 +16,65 @@ export enum GameConfigCustomType { Prod = 'prod', } +/** 框架基础环境配置(dev/test/prod 三类字段相同,仅值不同) */ +export interface IConfigEnvironmentBase { + /** 客户端版本号 */ + version: string; + /** 本地存储内容加密 key */ + localDataKey: string; + /** 本地存储内容加密 iv */ + localDataIv: string; + /** 游戏每秒传输帧数 */ + frameRate: number; + /** 加载界面资源超时提示(毫秒) */ + loadingTimeoutGui: number; + /** 是否开启移动设备安全区域适配 */ + mobileSafeArea: boolean; + /** 是否显示统计信息 */ + stats: boolean; + /** Http 服务器地址 */ + httpServer: string; + /** Http 请求超时时间(毫秒) */ + httpTimeout: number; + /** WebSocket 服务器地址 */ + webSocketServer: string; + /** WebSocket 心跳间隔时间(毫秒) */ + webSocketHeartTime: number; + /** WebSocket 指定时间没收到消息就断开连接(毫秒) */ + webSocketReceiveTime: number; + /** WebSocket 重连间隔时间(毫秒) */ + webSocketReconnetTimeOut: number; +} + +/** 环境配置类型(游戏项目可通过模块增强扩展自定义字段) */ +export interface IConfigEnvironment extends IConfigEnvironmentBase {} + +/** config.json 完整配置结构 */ +export interface IConfigJson { + /** 当前使用的环境类型(dev/test/prod) */ + type: GameConfigCustomType; + /** 各环境配置数据 */ + config: Record; + /** 界面层级配置 */ + gui: Array<{ name: string; type: string }>; + /** 多语言配置 */ + language: { + /** 默认语言 */ + default: string; + /** 支持的语言类型列表 */ + type: string[]; + /** 语言资源路径 */ + path: { json: string; texture: string; spine?: string }; + }; + /** 远程资源包配置 */ + bundle: { default: string }; +} + +/** 资源配置加载器传入的 config 结构 */ +export interface IConfigResource { + json: IConfigJson; +} + /* 游戏配置解析,对应 resources/config/config.json 配置 */ export class GameConfig { /** 客户端版本号配置 */ @@ -43,7 +102,7 @@ export class GameConfig { return this.data.loadingTimeoutGui || 1000; } /** 是否显示统计信息 */ - get stats(): number { + get stats(): boolean { return this.data.stats; } /** Http 服务器地址 */ @@ -93,17 +152,17 @@ export class GameConfig { return this._data.bundle.default; } - private _data: any = null; + private _data!: IConfigJson; /** 游戏配置数据 */ - get data(): any { + get data(): IConfigEnvironment { return this._data.config[this._configType]; } /** 当前游戏配置分组类型 */ private _configType: GameConfigCustomType = GameConfigCustomType.Prod; - constructor(config: any) { - this._data = Object.freeze(config.json); + constructor(config: IConfigResource) { + this._data = Object.freeze(config.json) as IConfigJson; this.setConfigType(this._data.type); oops.log.logConfig(this._data, '游戏配置'); } @@ -115,4 +174,4 @@ export class GameConfig { setConfigType(type: GameConfigCustomType) { this._configType = type; } -} +} diff --git a/assets/module/decorator/GamePrefabDecorator.ts b/assets/module/decorator/GamePrefabDecorator.ts index 4a00495..4c7bc90 100644 --- a/assets/module/decorator/GamePrefabDecorator.ts +++ b/assets/module/decorator/GamePrefabDecorator.ts @@ -25,8 +25,9 @@ export namespace prefab { export function register(path: string, bundleName?: string): (ctor: GameComponentCtor) => void { return function (ctor: GameComponentCtor): void { const bundle = bundleName || resLoader.defaultBundleName; - (ctor as any).GAME_PREFAB_PATH = path; - (ctor as any).GAME_PREFAB_BUNDLE = bundle; + const c = ctor as any; + c.GAME_PREFAB_PATH = path; + c.GAME_PREFAB_BUNDLE = bundle; }; } }