mirror of
https://gitee.com/dgflash/oops-plugin-framework.git
synced 2026-06-07 18:52:23 +08:00
!26 优化 ECS 和 MVVM 相关代码的性能与可读性
Merge pull request !26 from dgflash/develop
This commit is contained in:
@@ -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 extends Entity = Entity>(): 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<T extends IComp>(ctor: CompCtor<T>): T {
|
||||
const entity = createEntity();
|
||||
return (entity as ECSEntity).add(ctor);
|
||||
return entity.add(ctor);
|
||||
}
|
||||
|
||||
//#region 过滤器
|
||||
|
||||
@@ -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<ecs.IComp>): void {
|
||||
|
||||
@@ -28,7 +28,7 @@ export class VMBase extends Component {
|
||||
templateMode = false;
|
||||
|
||||
/** watch 多路径 */
|
||||
protected watchPathArr: string[] = [];
|
||||
watchPathArr: string[] = [];
|
||||
|
||||
/** 储存模板多路径的值 */
|
||||
protected templateValueArr: any[] = [];
|
||||
|
||||
@@ -83,18 +83,24 @@ export abstract class CCView<T extends CCEntity> 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<T extends CCEntity> 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<T extends CCEntity> 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<VMBase>();
|
||||
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<Component>();
|
||||
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<T extends CCEntity> 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<T extends CCEntity> extends GameComponent implement
|
||||
this.tag = undefined;
|
||||
}
|
||||
|
||||
// @ts-ignore - 优化:显式清空引用,帮助 GC
|
||||
this.data = undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<GameConfigCustomType, IConfigEnvironment>;
|
||||
/** 界面层级配置 */
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user