From b51ec298f70145b1132406c85997f00503adfa2a Mon Sep 17 00:00:00 2001 From: dgflash Date: Fri, 16 Jan 2026 21:40:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96ECS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/libs/ecs/ECS.ts | 85 ++++++++------- assets/libs/ecs/ECSEntity.ts | 198 +++++++++++++++++++--------------- assets/libs/ecs/ECSGroup.ts | 14 +-- assets/libs/ecs/ECSMask.ts | 48 +++++++-- assets/libs/ecs/ECSMatcher.ts | 25 +++-- assets/libs/ecs/ECSModel.ts | 6 +- assets/libs/ecs/ECSSystem.ts | 56 ++++++---- 7 files changed, 261 insertions(+), 171 deletions(-) diff --git a/assets/libs/ecs/ECS.ts b/assets/libs/ecs/ECS.ts index 1a0c0e7..4528d44 100644 --- a/assets/libs/ecs/ECS.ts +++ b/assets/libs/ecs/ECS.ts @@ -128,31 +128,37 @@ export namespace ecs { } } */ - export function register(name: string, canNew = true) { - return function (ctor: any) { + export function register(name: string, canNew = true) { + return function (ctor: EntityCtor | CompCtor | (new () => T)) { + const ctorAny = ctor as unknown as { + s?: boolean; + tid?: number; + compName?: string; + }; + // 注册系统 - if (ctor.s) { + if (ctorAny.s) { let system = ECSModel.systems.get(name); if (system == null) { system = new ecs.System(); ECSModel.systems.set(name, system); } - system.add(new ctor); + system.add(new (ctor as new () => ComblockSystem)()); } // 注册实体 - else if (ctor.tid == undefined) { - ECSModel.entityCtors.set(ctor as EntityCtor, name); + else if (ctorAny.tid === undefined) { + ECSModel.entityCtors.set(ctor as EntityCtor, name); } // 注册组件 else { - if (ctor.tid === -1) { - ctor.tid = ECSModel.compTid++; - ctor.compName = name; - ECSModel.compCtors.push(ctor); // 注册不同类型的组件 + if (ctorAny.tid === -1) { + ctorAny.tid = ECSModel.compTid++; + ctorAny.compName = name; + ECSModel.compCtors.push(ctor as CompCtor); if (canNew) { - ECSModel.compPools.set(ctor.tid, []); + ECSModel.compPools.set(ctorAny.tid, []); } - ECSModel.compAddOrRemove.set(ctor.tid, []); + ECSModel.compAddOrRemove.set(ctorAny.tid, []); } else { throw new Error(`ECS 组件重复注册: ${name}.`); @@ -167,29 +173,32 @@ export namespace ecs { */ export function getEntity(ctor: EntityCtor): T { // 获取实体对象名 - const entityName = ECSModel.entityCtors.get(ctor); - if (entityName == undefined) + const entityName = ECSModel.entityCtors.get(ctor as EntityCtor); + if (entityName === undefined) { console.error(`${ctor.name} 实体没有注册`); + throw new Error(`${ctor.name} 实体没有注册`); + } // 获取实体对象池 - const entitys = ECSModel.entityPool.get(entityName!) || []; - let entity: any = entitys.pop(); + const entitys = ECSModel.entityPool.get(entityName) || []; + let entity: T | undefined = entitys.pop() as T | undefined; // 缓存中没有同类实体,则创建一个新的 if (!entity) { entity = new ctor(); - entity.eid = ECSModel.eid++; // 实体唯一编号 - entity.name = entityName; + (entity as ECSEntity).eid = ECSModel.eid++; // 实体唯一编号 + (entity as ECSEntity).name = entityName; } // 触发实体初始化逻辑 - if (entity.init) { - entity.isValid = true; - entity.init(); + const entityWithInit = entity as ECSEntity & { init?: () => void }; + if (entityWithInit.init) { + entityWithInit.isValid = true; + entityWithInit.init(); } - ECSModel.eid2Entity.set(entity.eid, entity); - return entity as T; + ECSModel.eid2Entity.set((entity as ECSEntity).eid, entity as ECSEntity); + return entity; } /** @@ -208,7 +217,7 @@ export namespace ecs { } /** 清理所有的实体 */ - export function clear() { + export function clear(): void { ECSModel.eid2Entity.forEach((entity) => { entity.destroy(); }); @@ -226,7 +235,7 @@ export namespace ecs { * 清理所有对象池 - 用于释放不再使用的缓存内存 * 注意:此操作会清空所有实体池、组件池和 Mask 池,请在确保不再需要这些缓存时调用 */ - export function clearPools() { + export function clearPools(): void { // 清理实体池 ECSModel.entityPool.forEach((pool) => { pool.length = 0; @@ -246,20 +255,20 @@ export namespace ecs { * 通过实体唯一编号获得实体对象 * @param eid 实体唯一编号 */ - export function getEntityByEid(eid: number): E { - return ECSModel.eid2Entity.get(eid) as E; + export function getEntityByEid(eid: number): E | undefined { + return ECSModel.eid2Entity.get(eid) as E | undefined; } /** 当前活动中的实体数量 */ - export function activeEntityCount() { + export function activeEntityCount(): number { return ECSModel.eid2Entity.size; } /** 创建实体 */ function createEntity(): E { const entity = new Entity(); - entity.eid = ECSModel.eid++; // 实体id也是有限的资源 - ECSModel.eid2Entity.set(entity.eid, entity); + (entity as ECSEntity).eid = ECSModel.eid++; // 实体id也是有限的资源 + ECSModel.eid2Entity.set((entity as ECSEntity).eid, entity as ECSEntity); return entity as E; } @@ -269,7 +278,7 @@ export namespace ecs { */ function createEntityWithComp(ctor: CompCtor): T { const entity = createEntity(); - return entity.add(ctor); + return (entity as ECSEntity).add(ctor); } //#region 过滤器 @@ -280,7 +289,7 @@ export namespace ecs { * @example * ecs.allOf(AComponent, BComponent, CComponent); */ - export function allOf(...args: CompType[]) { + export function allOf(...args: CompType[]): IMatcher { return new ECSMatcher().allOf(...args); } @@ -290,7 +299,7 @@ export namespace ecs { * @example * ecs.anyOf(AComponent, BComponent); */ - export function anyOf(...args: CompType[]) { + export function anyOf(...args: CompType[]): IMatcher { return new ECSMatcher().anyOf(...args); } @@ -305,7 +314,7 @@ export namespace ecs { // 不同时包含CComponent和DComponent ecs.allOf(AComponent, BComponent).excludeOf(CComponent).excludeOf(DComponent); */ - export function onlyOf(...args: CompType[]) { + export function onlyOf(...args: CompType[]): IMatcher { return new ECSMatcher().onlyOf(...args); } @@ -316,7 +325,7 @@ export namespace ecs { // 表示不包含组件A或者组件B ecs.excludeOf(A, B); */ - export function excludeOf(...args: CompType[]) { + export function excludeOf(...args: CompType[]): IMatcher { return new ECSMatcher().excludeOf(...args); } //#endregion @@ -326,9 +335,9 @@ export namespace ecs { * 获取单例组件 * @param ctor 组件类 */ - export function getSingleton(ctor: CompCtor) { + export function getSingleton(ctor: CompCtor): T { if (!ECSModel.tid2comp.has(ctor.tid)) { - const comp = createEntityWithComp(ctor) as T; + const comp = createEntityWithComp(ctor); ECSModel.tid2comp.set(ctor.tid, comp); } return ECSModel.tid2comp.get(ctor.tid) as T; @@ -338,7 +347,7 @@ export namespace ecs { * 注册单例组件 - 主要用于那些不能手动创建对象的组件 * @param obj */ - export function addSingleton(obj: IComp) { + export function addSingleton(obj: IComp): void { const tid = (obj.constructor as CompCtor).tid; if (!ECSModel.tid2comp.has(tid)) { ECSModel.tid2comp.set(tid, obj); diff --git a/assets/libs/ecs/ECSEntity.ts b/assets/libs/ecs/ECSEntity.ts index 1dc453d..99f1798 100644 --- a/assets/libs/ecs/ECSEntity.ts +++ b/assets/libs/ecs/ECSEntity.ts @@ -10,10 +10,12 @@ import { ECSModel } from './ECSModel'; * @param entity 实体对象 * @param componentTypeId 组件类型id */ -function broadcastCompAddOrRemove(entity: ECSEntity, componentTypeId: number) { +function broadcastCompAddOrRemove(entity: ECSEntity, componentTypeId: number): void { const events = ECSModel.compAddOrRemove.get(componentTypeId); - for (let i = events!.length - 1; i >= 0; i--) { - events![i](entity); + if (events) { + for (let i = events.length - 1; i >= 0; i--) { + events[i](entity); + } } // 判断是不是删了单例组件 if (ECSModel.tid2comp.has(componentTypeId)) { @@ -21,6 +23,27 @@ function broadcastCompAddOrRemove(entity: ECSEntity, componentTypeId: number) { } } +/** + * 设置实体上的组件属性(类型安全的动态属性访问) + */ +function setEntityComp(entity: ECSEntity, compName: string, comp: T): void { + Reflect.set(entity, compName, comp); +} + +/** + * 获取实体上的组件属性(类型安全的动态属性访问) + */ +function getEntityComp(entity: ECSEntity, compName: string): T | undefined { + return Reflect.get(entity, compName) as T | undefined; +} + +/** + * 删除实体上的组件属性(类型安全的动态属性访问) + */ +function deleteEntityComp(entity: ECSEntity, compName: string): void { + Reflect.set(entity, compName, null); +} + /** * 创建组件对象 * @param ctor @@ -30,8 +53,11 @@ function createComp(ctor: CompCtor): T { if (!cct) { throw Error(`没有找到该组件的构造函数,检查${ctor.compName}是否为不可构造的组件`); } - const comps = ECSModel.compPools.get(ctor.tid)!; - const component = comps.pop() || new (cct as CompCtor); + const comps = ECSModel.compPools.get(ctor.tid); + if (!comps) { + throw Error(`组件${ctor.compName}的对象池不存在`); + } + const component = comps.pop() || new (cct as CompCtor)(); return component as T; } @@ -41,10 +67,10 @@ function createComp(ctor: CompCtor): T { * 缓存销毁的实体,下次新建实体时会优先从缓存中拿 * @param entity */ -function destroyEntity(entity: ECSEntity) { +function destroyEntity(entity: ECSEntity): void { if (ECSModel.eid2Entity.has(entity.eid)) { let entitys = ECSModel.entityPool.get(entity.name); - if (entitys == null) { + if (entitys === undefined) { entitys = []; ECSModel.entityPool.set(entity.name, entitys); } @@ -70,7 +96,7 @@ export class ECSEntity { /** 实体是否有效 */ isValid = true; /** 组件过滤数据 */ - private mask = new ECSMask(); + private mask: ECSMask = new ECSMask(); /** 当前实体身上附加的组件构造函数 */ private compTid2Ctor: Map> = new Map(); /** 配合 entity.remove(Comp, false), 记录组件实例上的缓存数据,在添加时恢复原数据 */ @@ -78,6 +104,11 @@ export class ECSEntity { /** 组件缓存容量限制,防止内存泄漏 */ private static readonly MAX_CACHE_COMP = 10; + /** 获取 mask 对象(内部使用) */ + getMask(): ECSMask { + return this.mask; + } + private _parent: ECSEntity | null = null; /** 父实体 */ get parent(): ECSEntity | null { @@ -88,11 +119,11 @@ export class ECSEntity { } /** 子实体 */ - private childs: Map = null!; + private childs: Map | null = null; /** 获取子实体 */ - getChild(eid: number) { - return this.childs.get(eid) as T; + getChild(eid: number): T | undefined { + return this.childs?.get(eid) as T | undefined; } /** @@ -101,7 +132,9 @@ export class ECSEntity { * @returns 子实体的唯一编号, -1表示添加失败 */ addChild(entity: ECSEntity): number { - if (this.childs == null) this.childs = new Map(); + if (this.childs === null) { + this.childs = new Map(); + } if (this.childs.has(entity.eid)) { console.warn(`子实体${entity.name}已存在`); @@ -119,8 +152,8 @@ export class ECSEntity { * @param isDestroy 被移除的实体是否释放,默认为释放 * @returns */ - removeChild(entity: ECSEntity, isDestroy = true) { - if (this.childs == null) return; + removeChild(entity: ECSEntity, isDestroy = true): void { + if (this.childs === null) return; entity.parent = null; this.childs.delete(entity.eid); @@ -148,8 +181,10 @@ export class ECSEntity { } else { console.log(`【${this.name}】实体【${ctor.compName}】组件已存在`); - // @ts-ignore - return this[ctor.compName] as T; + const existingComp = getEntityComp(this, ctor.compName); + if (existingComp) { + return existingComp; + } } } this.mask.set(compTid); @@ -161,12 +196,11 @@ export class ECSEntity { } else { // 创建组件对象 - comp = createComp(ctor) as T; + comp = createComp(ctor); } // 将组件对象直接附加到实体对象身上,方便直接获取 - // @ts-ignore - this[ctor.compName] = comp; + setEntityComp(this, ctor.compName, comp); this.compTid2Ctor.set(compTid, ctor); comp.tid = compTid; comp.ent = this; @@ -176,23 +210,19 @@ export class ECSEntity { return comp; } else { - const tmpCtor = (ctor.constructor as CompCtor); + // 此时 ctor 是组件实例 + const compInstance = ctor as T; + const tmpCtor = (compInstance.constructor as CompCtor); const compTid = tmpCtor.tid; - // console.assert(compTid !== -1 || !compTid, '组件未注册!'); - // console.assert(this.compTid2Ctor.has(compTid), '已存在该组件!'); if (compTid === -1 || compTid == null) throw Error(`【${this.name}】实体【${tmpCtor.name}】组件未注册`); if (this.compTid2Ctor.has(compTid)) throw Error(`【${this.name}】实体【${tmpCtor.name}】组件已经存在`); this.mask.set(compTid); - //@ts-ignore - this[tmpCtor.compName] = ctor; + setEntityComp(this, tmpCtor.compName, compInstance); this.compTid2Ctor.set(compTid, tmpCtor); - //@ts-ignore - ctor.tid = compTid; - //@ts-ignore - ctor.canRecycle = false; - //@ts-ignore - ctor.ent = this; + compInstance.tid = compTid; + compInstance.canRecycle = false; + compInstance.ent = this; broadcastCompAddOrRemove(this, compTid); return this; @@ -204,9 +234,10 @@ export class ECSEntity { * @param ctors 组件类 * @returns */ - addComponents(...ctors: CompType[]) { - for (const ctor of ctors) { - this.add(ctor); + addComponents(...ctors: CompType[]): this { + const len = ctors.length; + for (let i = 0; i < len; i++) { + this.add(ctors[i]); } return this; } @@ -215,11 +246,8 @@ export class ECSEntity { * 获取一个组件实例 * @param ctor 组件类 */ - get(ctor: number): number; - get(ctor: CompCtor): T; - get(ctor: CompCtor | number): T { - // @ts-ignore - return this[ctor.compName]; + get(ctor: CompCtor): T | undefined { + return getEntityComp(this, ctor.compName); } /** @@ -227,12 +255,8 @@ export class ECSEntity { * @param ctor 组件类 */ has(ctor: CompType): boolean { - if (typeof ctor === 'number') { - return this.mask.has(ctor); - } - else { - return this.compTid2Ctor.has(ctor.tid); - } + const tid = typeof ctor === 'number' ? ctor : ctor.tid; + return this.compTid2Ctor.has(tid); } /** @@ -242,59 +266,58 @@ export class ECSEntity { * 设置该参数为false,这样该组件对象会缓存在实体身上,下次重新添加组件时会将该组件对象添加回来,不会重新从组件缓存 * 池中拿一个组件来用。 */ - remove(ctor: CompType, isRecycle = true) { - let hasComp = false; - //@ts-ignore - const componentTypeId = ctor.tid; - //@ts-ignore - const compName = ctor.compName; - if (this.mask.has(componentTypeId)) { - hasComp = true; - //@ts-ignore - const comp = this[ctor.compName]; - //@ts-ignore - comp.ent = null; - if (isRecycle) { - comp.reset(); + remove(ctor: CompType, isRecycle = true): void { + const componentTypeId = typeof ctor === 'number' ? ctor : ctor.tid; + const compName = typeof ctor === 'number' ? '' : ctor.compName; + + if (!this.mask.has(componentTypeId)) { + return; + } - // 回收组件到指定缓存池中,限制池大小 - if (comp.canRecycle) { - const compPoolsType = ECSModel.compPools.get(componentTypeId)!; - if (compPoolsType.length < ECSModel.MAX_COMP_POOL_SIZE) { - compPoolsType.push(comp); - } + const comp = getEntityComp(this, compName); + if (!comp) { + return; + } + + comp.ent = null!; + + if (isRecycle) { + comp.reset(); + + // 回收组件到指定缓存池中,限制池大小 + if (comp.canRecycle) { + const compPoolsType = ECSModel.compPools.get(componentTypeId); + if (compPoolsType && compPoolsType.length < ECSModel.MAX_COMP_POOL_SIZE) { + compPoolsType.push(comp); } } - else { - // 限制缓存组件数量,防止内存泄漏 - if (this.compTid2Obj.size < ECSEntity.MAX_CACHE_COMP) { - this.compTid2Obj.set(componentTypeId, comp); // 用于缓存显示对象组件 - } else { - // 超过限制,强制回收 - console.warn(`实体 ${this.name} 缓存组件数量超过限制,强制回收组件 ${compName}`); - comp.reset(); - if (comp.canRecycle) { - const compPoolsType = ECSModel.compPools.get(componentTypeId); - if (compPoolsType) { - compPoolsType.push(comp); - } + } + else { + // 限制缓存组件数量,防止内存泄漏 + if (this.compTid2Obj.size < ECSEntity.MAX_CACHE_COMP) { + this.compTid2Obj.set(componentTypeId, comp); // 用于缓存显示对象组件 + } else { + // 超过限制,强制回收 + console.warn(`实体 ${this.name} 缓存组件数量超过限制,强制回收组件 ${compName}`); + comp.reset(); + if (comp.canRecycle) { + const compPoolsType = ECSModel.compPools.get(componentTypeId); + if (compPoolsType && compPoolsType.length < ECSModel.MAX_COMP_POOL_SIZE) { + compPoolsType.push(comp); } } } } // 删除实体上的组件逻辑 - if (hasComp) { - //@ts-ignore - this[compName] = null; - this.mask.delete(componentTypeId); - this.compTid2Ctor.delete(componentTypeId); - broadcastCompAddOrRemove(this, componentTypeId); - } + deleteEntityComp(this, compName); + this.mask.delete(componentTypeId); + this.compTid2Ctor.delete(componentTypeId); + broadcastCompAddOrRemove(this, componentTypeId); } /** 销毁实体,实体会被回收到实体缓存池中 */ - destroy() { + destroy(): void { this.isValid = false; // 如果有父模块,则移除父模块上记录的子模块 @@ -308,7 +331,8 @@ export class ECSEntity { this.childs.forEach((e) => { this.removeChild(e); }); - this.childs = null!; + this.childs.clear(); + this.childs = null; } // 移除实体上所有组件 @@ -322,7 +346,7 @@ export class ECSEntity { this.mask.destroy(); } - private _remove(comp: CompType) { + private _remove(comp: CompType): void { this.remove(comp, true); } } diff --git a/assets/libs/ecs/ECSGroup.ts b/assets/libs/ecs/ECSGroup.ts index cb8bb79..cef3f43 100644 --- a/assets/libs/ecs/ECSGroup.ts +++ b/assets/libs/ecs/ECSGroup.ts @@ -9,9 +9,9 @@ import type { ECSEntity } from './ECSEntity'; export class ECSGroup { /** 实体筛选规则 */ - private matcher: ecs.IMatcher; + private readonly matcher: ecs.IMatcher; - private _matchEntities: Map = new Map(); + private readonly _matchEntities: Map = new Map(); private _entitiesCache: E[] = []; private _cacheValid = false; @@ -19,7 +19,7 @@ export class ECSGroup { /** * 符合规则的实体 */ - get matchEntities() { + get matchEntities(): E[] { if (!this._cacheValid) { // 复用数组,减少 GC 压力 const cache = this._entitiesCache; @@ -47,7 +47,7 @@ export class ECSGroup { count = 0; /** 获取matchEntities中第一个实体 */ - get entity(): E { + get entity(): E | undefined { return this.matchEntities[0]; } @@ -58,7 +58,7 @@ export class ECSGroup { this.matcher = matcher; } - onComponentAddOrRemove(entity: E) { + onComponentAddOrRemove(entity: E): void { if (this.matcher.isMatch(entity)) { // Group只关心指定组件在实体身上的添加和删除动作。 if (!this._matchEntities.has(entity.eid)) { this._matchEntities.set(entity.eid, entity); @@ -83,12 +83,12 @@ export class ECSGroup { } } - watchEntityEnterAndRemove(enteredEntities: Map, removedEntities: Map) { + watchEntityEnterAndRemove(enteredEntities: Map, removedEntities: Map): void { this._enteredEntities = enteredEntities; this._removedEntities = removedEntities; } - clear() { + clear(): void { this._matchEntities.clear(); this._entitiesCache.length = 0; this._cacheValid = false; diff --git a/assets/libs/ecs/ECSMask.ts b/assets/libs/ecs/ECSMask.ts index 1268466..15850ff 100644 --- a/assets/libs/ecs/ECSMask.ts +++ b/assets/libs/ecs/ECSMask.ts @@ -28,20 +28,27 @@ class MaskPool { return new Uint32Array(length); } - static recycle(mask: Uint32Array) { - // 只回收到池中,不超过最大容量 + static recycle(mask: Uint32Array | null): void { + // 验证参数有效性 + if (!mask) { + return; + } + // 只回收到池中,不超过最大容量,防止内存无限增长 if (this.pool.length < this.MAX_POOL_SIZE) { + // 清空数组,避免泄漏引用 + mask.fill(0); this.pool.push(mask); } } - static clear() { + static clear(): void { + // 清空对象池,释放内存 this.pool.length = 0; } } export class ECSMask { - private mask!: Uint32Array; + private mask: Uint32Array | null = null; private size = 0; constructor() { @@ -50,7 +57,11 @@ export class ECSMask { this.size = length; } - set(num: number) { + set(num: number): void { + if (!this.mask) { + console.error('ECSMask: 尝试在已销毁的 mask 上设置位'); + return; + } // https://stackoverflow.com/questions/34896909/is-it-correct-to-set-bit-31-in-javascript // this.mask[((num / 32) >>> 0)] |= ((1 << (num % 32)) >>> 0); const index = (num / 31) >>> 0; @@ -58,19 +69,31 @@ export class ECSMask { this.mask[index] |= (1 << bit); } - delete(num: number) { + delete(num: number): void { + if (!this.mask) { + console.error('ECSMask: 尝试在已销毁的 mask 上删除位'); + return; + } const index = (num / 31) >>> 0; const bit = num % 31; this.mask[index] &= ~(1 << bit); } has(num: number): boolean { + if (!this.mask) { + console.error('ECSMask: 尝试在已销毁的 mask 上检查位'); + return false; + } const index = (num / 31) >>> 0; const bit = num % 31; return !!(this.mask[index] & (1 << bit)); } or(other: ECSMask): boolean { + if (!this.mask || !other.mask) { + console.error('ECSMask: 尝试在已销毁的 mask 上执行 or 操作'); + return false; + } const size = this.size; const thisMask = this.mask; const otherMask = other.mask; @@ -85,6 +108,10 @@ export class ECSMask { } and(other: ECSMask): boolean { + if (!this.mask || !other.mask) { + console.error('ECSMask: 尝试在已销毁的 mask 上执行 and 操作'); + return false; + } const size = this.size; const thisMask = this.mask; const otherMask = other.mask; @@ -97,7 +124,10 @@ export class ECSMask { return true; } - clear() { + clear(): void { + if (!this.mask) { + return; + } // 使用 fill 方法更高效 this.mask.fill(0); } @@ -105,10 +135,10 @@ export class ECSMask { /** * 销毁并回收到对象池 */ - destroy() { + destroy(): void { if (this.mask) { MaskPool.recycle(this.mask); - this.mask = null!; + this.mask = null; this.size = 0; } } diff --git a/assets/libs/ecs/ECSMatcher.ts b/assets/libs/ecs/ECSMatcher.ts index 4e4b850..bd5e321 100644 --- a/assets/libs/ecs/ECSMatcher.ts +++ b/assets/libs/ecs/ECSMatcher.ts @@ -137,7 +137,7 @@ export class ECSMatcher implements ecs.IMatcher { abstract class BaseOf { indices: number[] = []; - protected mask = new ECSMask(); + protected mask: ECSMask = new ECSMask(); private _keyCache: string | null = null; // 缓存 key,避免重复生成 constructor(...args: CompType[]) { @@ -163,6 +163,13 @@ abstract class BaseOf { // 从 Set 转为排序数组 this.indices = Array.from(uniqueIds).sort((a, b) => a - b); } + + /** 清理资源,防止内存泄漏 */ + destroy(): void { + this.mask.destroy(); + this.indices.length = 0; + this._keyCache = null; + } toString(): string { // 使用缓存避免重复生成字符串 @@ -182,8 +189,7 @@ abstract class BaseOf { */ class AnyOf extends BaseOf { isMatch(entity: ECSEntity): boolean { - // @ts-ignore - return this.mask.or(entity.mask); + return this.mask.or((entity as ECSEntityInternal).getMask()); } getKey(): string { @@ -192,12 +198,11 @@ class AnyOf extends BaseOf { } /** - * 用于描述包含了“这些”组件的实体,这个实体除了包含这些组件还可以包含其他组件 + * 用于描述包含了"这些"组件的实体,这个实体除了包含这些组件还可以包含其他组件 */ class AllOf extends BaseOf { isMatch(entity: ECSEntity): boolean { - // @ts-ignore - return this.mask.and(entity.mask); + return this.mask.and((entity as ECSEntityInternal).getMask()); } getKey(): string { @@ -214,7 +219,11 @@ class ExcludeOf extends BaseOf { } isMatch(entity: ECSEntity): boolean { - // @ts-ignore - return !this.mask.or(entity.mask); + return !this.mask.or((entity as ECSEntityInternal).getMask()); } } + +/** 内部接口,用于访问 ECSEntity 的私有成员 */ +interface ECSEntityInternal { + getMask(): ECSMask; +} diff --git a/assets/libs/ecs/ECSModel.ts b/assets/libs/ecs/ECSModel.ts index 281a59e..eb24575 100644 --- a/assets/libs/ecs/ECSModel.ts +++ b/assets/libs/ecs/ECSModel.ts @@ -31,8 +31,8 @@ export interface CompCtor { export class ECSModel { /** 实体自增id */ static eid = 1; - /** 实体造函数 */ - static entityCtors: Map, string> = new Map(); + /** 实体构造函数 */ + static entityCtors: Map, string> = new Map(); /** 实体对象缓存池 */ static entityPool: Map = new Map(); /** 通过实体id查找实体对象 */ @@ -43,7 +43,7 @@ export class ECSModel { /** 组件缓存池 */ static compPools: Map = new Map(); /** 组件构造函数,用于ecs.register注册时,记录不同类型的组件 */ - static compCtors: (CompCtor | number)[] = []; + static compCtors: CompCtor[] = []; /** * 每个组件的添加和删除的动作都要派送到"关心"它们的group上。goup对当前拥有或者之前(删除前)拥有该组件的实体进行组件规则判断。判断该实体是否满足group * 所期望的组件组合。 diff --git a/assets/libs/ecs/ECSSystem.ts b/assets/libs/ecs/ECSSystem.ts index e61b130..8e5bf18 100644 --- a/assets/libs/ecs/ECSSystem.ts +++ b/assets/libs/ecs/ECSSystem.ts @@ -7,11 +7,11 @@ import { ECSModel } from './ECSModel'; export abstract class ECSComblockSystem { static s = true; - protected group: ECSGroup; + protected group!: ECSGroup; protected dt = 0; - private enteredEntities: Map = null!; - private removedEntities: Map = null!; + private enteredEntities: Map | null = null; + private removedEntities: Map | null = null; private hasEntityEnter = false; private hasEntityRemove = false; @@ -51,6 +51,13 @@ export abstract class ECSComblockSystem { this.execute = this.updateOnce; } } + + /** 清理系统资源 */ + protected cleanup(): void { + // 清理实体映射,防止内存泄漏 + this.enteredEntities?.clear(); + this.removedEntities?.clear(); + } /** 系统实始化 */ init(): void { @@ -72,7 +79,7 @@ export abstract class ECSComblockSystem { * @param dt * @returns */ - private updateOnce(dt: number) { + private updateOnce(dt: number): void { if (this.group.count === 0) { return; } @@ -80,8 +87,8 @@ export abstract class ECSComblockSystem { this.dt = dt; // 处理刚进来的实体 - 使用标准 for 循环优化性能 - if (this.enteredEntities.size > 0) { - const entityEnterFn = (this as unknown as ecs.IEntityEnterSystem).entityEnter; + if (this.enteredEntities && this.enteredEntities.size > 0) { + const entityEnterFn = (this as unknown as ecs.IEntityEnterSystem).entityEnter; const iterator = this.enteredEntities.values(); let result = iterator.next(); while (!result.done) { @@ -92,7 +99,7 @@ export abstract class ECSComblockSystem { } // 只执行firstUpdate - 使用标准 for 循环 - const firstUpdateFn = (this as unknown as ecs.ISystemFirstUpdate).firstUpdate; + const firstUpdateFn = (this as unknown as ecs.ISystemFirstUpdate).firstUpdate; const entities = this.group.matchEntities; const len = entities.length; for (let i = 0; i < len; i++) { @@ -116,7 +123,7 @@ export abstract class ECSComblockSystem { // 执行update - 使用标准 for 循环提升性能 if (this.hasUpdate) { - const updateFn = (this as unknown as ecs.ISystemUpdate).update; + const updateFn = (this as unknown as ecs.ISystemUpdate).update; const entities = this.group.matchEntities; const len = entities.length; for (let i = 0; i < len; i++) { @@ -132,9 +139,9 @@ export abstract class ECSComblockSystem { */ private execute1(dt: number): void { // 处理移除的实体 - 使用标准循环优化 - if (this.removedEntities.size > 0) { + if (this.removedEntities && this.removedEntities.size > 0) { if (this.hasEntityRemove) { - const entityRemoveFn = (this as unknown as ecs.IEntityRemoveSystem).entityRemove; + const entityRemoveFn = (this as unknown as ecs.IEntityRemoveSystem).entityRemove; const iterator = this.removedEntities.values(); let result = iterator.next(); while (!result.done) { @@ -150,22 +157,22 @@ export abstract class ECSComblockSystem { this.dt = dt; // 处理刚进来的实体 - 使用标准循环优化 - if (this.enteredEntities!.size > 0) { + if (this.enteredEntities && this.enteredEntities.size > 0) { if (this.hasEntityEnter) { - const entityEnterFn = (this as unknown as ecs.IEntityEnterSystem).entityEnter; - const iterator = this.enteredEntities!.values(); + const entityEnterFn = (this as unknown as ecs.IEntityEnterSystem).entityEnter; + const iterator = this.enteredEntities.values(); let result = iterator.next(); while (!result.done) { entityEnterFn.call(this, result.value); result = iterator.next(); } } - this.enteredEntities!.clear(); + this.enteredEntities.clear(); } // 执行update - 使用标准 for 循环提升性能 if (this.hasUpdate) { - const updateFn = (this as unknown as ecs.ISystemUpdate).update; + const updateFn = (this as unknown as ecs.ISystemUpdate).update; const entities = this.group.matchEntities; const len = entities.length; for (let i = 0; i < len; i++) { @@ -214,19 +221,25 @@ export class ECSRootSystem { } } - clear() { - this.executeSystemFlows.forEach((sys) => sys.onDestroy()); + clear(): void { + const len = this.executeSystemFlows.length; + for (let i = 0; i < len; i++) { + this.executeSystemFlows[i].onDestroy(); + } + this.executeSystemFlows.length = 0; + this.systemCnt = 0; } } /** 系统组合器,用于将多个相同功能模块的系统逻辑上放在一起,系统也可以嵌套系统 */ export class ECSSystem { private _comblockSystems: ECSComblockSystem[] = []; - get comblockSystems() { + + get comblockSystems(): ECSComblockSystem[] { return this._comblockSystems; } - add(system: ECSSystem | ECSComblockSystem) { + add(system: ECSSystem | ECSComblockSystem): this { if (system instanceof ECSSystem) { Array.prototype.push.apply(this._comblockSystems, system._comblockSystems); system._comblockSystems.length = 0; @@ -236,4 +249,9 @@ export class ECSSystem { } return this; } + + /** 清理系统资源 */ + clear(): void { + this._comblockSystems.length = 0; + } }