mirror of
https://gitee.com/dgflash/oops-plugin-framework.git
synced 2026-06-23 19:22:47 +08:00
优化ECS
This commit is contained in:
@@ -1,348 +1,348 @@
|
||||
import { ECSComp } from './ECSComp';
|
||||
import { ECSEntity } from './ECSEntity';
|
||||
import { ECSMask } from './ECSMask';
|
||||
import { ECSMatcher } from './ECSMatcher';
|
||||
import type { CompCtor, CompType, EntityCtor } from './ECSModel';
|
||||
import { ECSModel } from './ECSModel';
|
||||
import { ECSComblockSystem, ECSRootSystem, ECSSystem } from './ECSSystem';
|
||||
import { ecsPoolCoordinator } from './ECSPoolManager';
|
||||
|
||||
/**
|
||||
* ECSEntity对象在destroy后,会回收到ECSPoolManager动态对象池中
|
||||
* ECSComp对象从ECSEntity.remove后,数据组件会回收到ECSPoolManager动态对象池中
|
||||
*/
|
||||
|
||||
/**
|
||||
* Entity-Component-System(实体-组件-系统)框架
|
||||
* 文档:https://gitee.com/dgflash/oops-framework/wikis/pages?sort_id=12033388&doc_id=2873565
|
||||
*/
|
||||
export namespace ecs {
|
||||
/** 实体 - 一个概念上的定义,指的是游戏世界中的一个独特物体,是一系列组件的集合 */
|
||||
export type Entity = ECSEntity;
|
||||
/** 组件 - 一堆数据的集合,即不存在任何的行为,只用来存储状态 */
|
||||
export type Comp = ECSComp;
|
||||
/** 系统 - 关注实体上组件数据变化,处理游戏逻辑 */
|
||||
export type System = ECSSystem;
|
||||
/** 根系统 - 驱动游戏中所有系统工作 */
|
||||
export type RootSystem = ECSRootSystem;
|
||||
/** 处理游戏逻辑系统对象 - 继承此对象实现自定义业务逻辑 */
|
||||
export type ComblockSystem = ECSComblockSystem;
|
||||
|
||||
/** 实体 - 一个概念上的定义,指的是游戏世界中的一个独特物体,是一系列组件的集合 */
|
||||
export const Entity = ECSEntity;
|
||||
/** 组件 - 一堆数据的集合,即不存在任何的行为,只用来存储状态 */
|
||||
export const Comp = ECSComp;
|
||||
/** 系统 - 关注实体上组件数据变化,处理游戏逻辑 */
|
||||
export const System = ECSSystem;
|
||||
/** 根系统 - 驱动游戏中所有系统工作 */
|
||||
export const RootSystem = ECSRootSystem;
|
||||
/** 处理游戏逻辑系统对象 - 继承此对象实现自定义业务逻辑 */
|
||||
export const ComblockSystem = ECSComblockSystem;
|
||||
|
||||
//#region 接口
|
||||
|
||||
/** 组件接口 */
|
||||
export interface IComp {
|
||||
canRecycle: boolean;
|
||||
ent: Entity;
|
||||
tid: number;
|
||||
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
/** 实体匹配器接口 */
|
||||
export interface IMatcher {
|
||||
mid: number;
|
||||
indices: number[];
|
||||
key: string;
|
||||
isMatch(entity: Entity): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听组件首次添加到实体上时,在ComblockSystem上实现这个接口
|
||||
* 1. entityEnter会在update方法之前执行,实体进入后,不会再次进入entityEnter方法中
|
||||
* 2. 当实体从当前System移除,下次再次符合条件进入System也会执行上述流程
|
||||
* @example
|
||||
export class RoleUpgradeSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem {
|
||||
filter(): ecs.IMatcher {
|
||||
return ecs.allOf(RoleUpgradeComp, RoleModelLevelComp);
|
||||
}
|
||||
|
||||
entityEnter(e: Role): void {
|
||||
e.remove(RoleUpgradeComp);
|
||||
}
|
||||
}
|
||||
*/
|
||||
export interface IEntityEnterSystem<E extends Entity = Entity> {
|
||||
entityEnter(entity: E): void;
|
||||
}
|
||||
|
||||
/** 监听组件从实体上移除时,在ComblockSystem上实现这个接口 */
|
||||
export interface IEntityRemoveSystem<E extends Entity = Entity> {
|
||||
entityRemove(entity: E): void;
|
||||
}
|
||||
|
||||
/** 监听系统第一次执行update处理实体时,在ComblockSystem上实现这个接口 */
|
||||
export interface ISystemFirstUpdate<E extends Entity = Entity> {
|
||||
firstUpdate(entity: E): void;
|
||||
}
|
||||
|
||||
/** 监听系统执行update处理实体时,在ComblockSystem上实现这个接口 */
|
||||
export interface ISystemUpdate<E extends Entity = Entity> {
|
||||
update(entity: E): void;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* 注册组件到ecs系统中
|
||||
* @param name 由于js打包会改变类名,所以这里必须手动传入组件的名称
|
||||
* @param canNew 标识是否可以new对象。想继承自Cocos Creator的组件就不能去new,需要写成@ecs.register('name', false)
|
||||
* @example
|
||||
// 注册实体
|
||||
@ecs.register('Role')
|
||||
export class Role extends ecs.Entity {
|
||||
|
||||
}
|
||||
|
||||
// 注册数据组件
|
||||
@ecs.register('RoleModel')
|
||||
export class RoleModelComp extends ecs.Comp {
|
||||
id: number = -1;
|
||||
|
||||
reset() {
|
||||
this.id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 注册系统组件
|
||||
@ecs.register('Initialize')
|
||||
export class InitResSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem {
|
||||
|
||||
}
|
||||
|
||||
// 注册显示对象组件
|
||||
@ccclass('RoleViewComp')
|
||||
@ecs.register('RoleView', false)
|
||||
export class RoleViewComp extends CCComp {
|
||||
onLoad(){
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
export function register<T extends Entity | IComp | ComblockSystem>(name: string, canNew = true) {
|
||||
return function (ctor: EntityCtor<T> | CompCtor<T> | (new () => T)) {
|
||||
const ctorAny = ctor as unknown as {
|
||||
s?: boolean;
|
||||
tid?: number;
|
||||
compName?: string;
|
||||
};
|
||||
|
||||
// 注册系统
|
||||
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 as new () => ComblockSystem)());
|
||||
}
|
||||
// 注册实体
|
||||
else if (ctorAny.tid === undefined) {
|
||||
ECSModel.entityCtors.set(ctor as EntityCtor<ECSEntity>, name);
|
||||
}
|
||||
// 注册组件
|
||||
else {
|
||||
if (ctorAny.tid === -1) {
|
||||
ctorAny.tid = ECSModel.compTid++;
|
||||
ctorAny.compName = name;
|
||||
ECSModel.compCtors.push(ctor as CompCtor<IComp>);
|
||||
ECSModel.compAddOrRemove.set(ctorAny.tid, []);
|
||||
}
|
||||
else {
|
||||
throw new Error(`ECS 组件重复注册: ${name}.`);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的实体对象或从缓存中获取一个实体对象(使用动态池)
|
||||
* @param ctor 实体类
|
||||
*/
|
||||
export function getEntity<T extends Entity>(ctor: EntityCtor<T>): T {
|
||||
// 获取实体对象名
|
||||
const entityName = ECSModel.entityCtors.get(ctor as EntityCtor<ECSEntity>);
|
||||
if (entityName === undefined) {
|
||||
console.error(`${ctor.name} 实体没有注册`);
|
||||
throw new Error(`${ctor.name} 实体没有注册`);
|
||||
}
|
||||
|
||||
// 使用动态池管理器
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
entityName,
|
||||
() => {
|
||||
const entity = new ctor();
|
||||
entity.eid = ECSModel.eid++; // 实体唯一编号
|
||||
entity.name = entityName;
|
||||
return entity;
|
||||
}
|
||||
);
|
||||
|
||||
const entity = pool.get();
|
||||
|
||||
// 触发实体初始化逻辑
|
||||
const entityWithInit = entity as ECSEntity & { init?: () => void };
|
||||
entity.isValid = true; // 无论新建还是复用,都标记为有效
|
||||
if (entityWithInit.init) entityWithInit.init();
|
||||
|
||||
ECSModel.eid2Entity.set((entity as ECSEntity).eid, entity as ECSEntity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态查询实体
|
||||
* @param matcher 匹配器
|
||||
* @example
|
||||
* ecs.query(ecs.allOf(Comp1, Comp2));
|
||||
*/
|
||||
export function query<E extends Entity = Entity>(matcher: IMatcher): E[] {
|
||||
let group = ECSModel.groups.get(matcher.mid);
|
||||
if (!group) {
|
||||
group = ECSModel.createGroup(matcher);
|
||||
ECSModel.eid2Entity.forEach(group.onComponentAddOrRemove, group);
|
||||
}
|
||||
return group.matchEntities as E[];
|
||||
}
|
||||
|
||||
/** 清理所有的实体 */
|
||||
export function clear(): void {
|
||||
ECSModel.eid2Entity.forEach((entity) => {
|
||||
entity.destroy();
|
||||
});
|
||||
ECSModel.groups.forEach((group) => {
|
||||
group.clear();
|
||||
});
|
||||
ECSModel.compAddOrRemove.forEach((callbackLst) => {
|
||||
callbackLst.length = 0;
|
||||
});
|
||||
ECSModel.eid2Entity.clear();
|
||||
ECSModel.groups.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有对象池 - 用于释放不再使用的缓存内存
|
||||
* 注意:此操作会清空所有实体池、组件池和 Mask 池,请在确保不再需要这些缓存时调用
|
||||
*/
|
||||
export function clearPools(): void {
|
||||
// 清理 Mask 对象池
|
||||
ECSMask.clearPool();
|
||||
|
||||
// 清理动态池管理器
|
||||
ecsPoolCoordinator.clearAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过实体唯一编号获得实体对象
|
||||
* @param eid 实体唯一编号
|
||||
*/
|
||||
export function getEntityByEid<E extends Entity = Entity>(eid: number): E | undefined {
|
||||
return ECSModel.eid2Entity.get(eid) as E | undefined;
|
||||
}
|
||||
|
||||
/** 当前活动中的实体数量 */
|
||||
export function activeEntityCount(): number {
|
||||
return ECSModel.eid2Entity.size;
|
||||
}
|
||||
|
||||
/** 创建实体 */
|
||||
function createEntity<E extends Entity = Entity>(): E {
|
||||
const entity = new Entity();
|
||||
entity.eid = ECSModel.eid++; // 实体id也是有限的资源
|
||||
ECSModel.eid2Entity.set((entity as ECSEntity).eid, entity as ECSEntity);
|
||||
return entity as E;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定一个组件创建实体,返回组件对象。
|
||||
* @param ctor
|
||||
*/
|
||||
function createEntityWithComp<T extends IComp>(ctor: CompCtor<T>): T {
|
||||
const entity = createEntity();
|
||||
return entity.add(ctor);
|
||||
}
|
||||
|
||||
//#region 过滤器
|
||||
/**
|
||||
* 表示只关心这些组件的添加和删除动作。虽然实体可能有这些组件之外的组件,但是它们的添加和删除没有被关注,所以不会存在对关注之外的组件
|
||||
* 进行添加操作引发Group重复添加实体。
|
||||
* @param args
|
||||
* @example
|
||||
* ecs.allOf(AComponent, BComponent, CComponent);
|
||||
*/
|
||||
export function allOf(...args: CompType<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().allOf(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件间是或的关系,表示关注拥有任意一个这些组件的实体
|
||||
* @param args 组件类
|
||||
* @example
|
||||
* ecs.anyOf(AComponent, BComponent);
|
||||
*/
|
||||
export function anyOf(...args: CompType<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().anyOf(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示关注只拥有这些组件的实体
|
||||
* 注:不是特殊情况不建议使用onlyOf。因为onlyOf会监听所有组件的添加和删除事件
|
||||
* @param args 组件类
|
||||
* @example
|
||||
// 不包含CComponent或者DComponent
|
||||
ecs.allOf(AComponent, BComponent).excludeOf(CComponent, DComponent);
|
||||
|
||||
// 不同时包含CComponent和DComponent
|
||||
ecs.allOf(AComponent, BComponent).excludeOf(CComponent).excludeOf(DComponent);
|
||||
*/
|
||||
export function onlyOf(...args: CompType<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().onlyOf(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不包含指定的任意一个组件
|
||||
* @param args 组件类
|
||||
* @example
|
||||
// 表示不包含组件A或者组件B
|
||||
ecs.excludeOf(A, B);
|
||||
*/
|
||||
export function excludeOf(...args: CompType<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().excludeOf(...args);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 单例组件
|
||||
/**
|
||||
* 获取单例组件
|
||||
* @param ctor 组件类
|
||||
*/
|
||||
export function getSingleton<T extends IComp>(ctor: CompCtor<T>): T {
|
||||
if (!ECSModel.tid2comp.has(ctor.tid)) {
|
||||
const comp = createEntityWithComp(ctor);
|
||||
ECSModel.tid2comp.set(ctor.tid, comp);
|
||||
}
|
||||
return ECSModel.tid2comp.get(ctor.tid) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册单例组件 - 主要用于那些不能手动创建对象的组件
|
||||
* @param obj
|
||||
*/
|
||||
export function addSingleton(obj: IComp): void {
|
||||
const tid = (obj.constructor as CompCtor<IComp>).tid;
|
||||
if (!ECSModel.tid2comp.has(tid)) {
|
||||
ECSModel.tid2comp.set(tid, obj);
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
import { ECSComp } from './ECSComp';
|
||||
import { ECSEntity } from './ECSEntity';
|
||||
import { ECSMask } from './ECSMask';
|
||||
import { ECSMatcher } from './ECSMatcher';
|
||||
import type { CompCtor, CompType, EntityCtor } from './ECSModel';
|
||||
import { ECSModel } from './ECSModel';
|
||||
import { ECSComblockSystem, ECSRootSystem, ECSSystem } from './ECSSystem';
|
||||
import { ecsPoolCoordinator } from './pool';
|
||||
|
||||
/**
|
||||
* ECSEntity对象在destroy后,会回收到ECSPoolManager动态对象池中
|
||||
* ECSComp对象从ECSEntity.remove后,数据组件会回收到ECSPoolManager动态对象池中
|
||||
*/
|
||||
|
||||
/**
|
||||
* Entity-Component-System(实体-组件-系统)框架
|
||||
* 文档:https://gitee.com/dgflash/oops-framework/wikis/pages?sort_id=12033388&doc_id=2873565
|
||||
*/
|
||||
export namespace ecs {
|
||||
/** 实体 - 一个概念上的定义,指的是游戏世界中的一个独特物体,是一系列组件的集合 */
|
||||
export type Entity = ECSEntity;
|
||||
/** 组件 - 一堆数据的集合,即不存在任何的行为,只用来存储状态 */
|
||||
export type Comp = ECSComp;
|
||||
/** 系统 - 关注实体上组件数据变化,处理游戏逻辑 */
|
||||
export type System = ECSSystem;
|
||||
/** 根系统 - 驱动游戏中所有系统工作 */
|
||||
export type RootSystem = ECSRootSystem;
|
||||
/** 处理游戏逻辑系统对象 - 继承此对象实现自定义业务逻辑 */
|
||||
export type ComblockSystem = ECSComblockSystem;
|
||||
|
||||
/** 实体 - 一个概念上的定义,指的是游戏世界中的一个独特物体,是一系列组件的集合 */
|
||||
export const Entity = ECSEntity;
|
||||
/** 组件 - 一堆数据的集合,即不存在任何的行为,只用来存储状态 */
|
||||
export const Comp = ECSComp;
|
||||
/** 系统 - 关注实体上组件数据变化,处理游戏逻辑 */
|
||||
export const System = ECSSystem;
|
||||
/** 根系统 - 驱动游戏中所有系统工作 */
|
||||
export const RootSystem = ECSRootSystem;
|
||||
/** 处理游戏逻辑系统对象 - 继承此对象实现自定义业务逻辑 */
|
||||
export const ComblockSystem = ECSComblockSystem;
|
||||
|
||||
//#region 接口
|
||||
|
||||
/** 组件接口 */
|
||||
export interface IComp {
|
||||
canRecycle: boolean;
|
||||
ent: Entity;
|
||||
tid: number;
|
||||
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
/** 实体匹配器接口 */
|
||||
export interface IMatcher {
|
||||
mid: number;
|
||||
indices: number[];
|
||||
key: string;
|
||||
isMatch(entity: Entity): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听组件首次添加到实体上时,在ComblockSystem上实现这个接口
|
||||
* 1. entityEnter会在update方法之前执行,实体进入后,不会再次进入entityEnter方法中
|
||||
* 2. 当实体从当前System移除,下次再次符合条件进入System也会执行上述流程
|
||||
* @example
|
||||
export class RoleUpgradeSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem {
|
||||
filter(): ecs.IMatcher {
|
||||
return ecs.allOf(RoleUpgradeComp, RoleModelLevelComp);
|
||||
}
|
||||
|
||||
entityEnter(e: Role): void {
|
||||
e.remove(RoleUpgradeComp);
|
||||
}
|
||||
}
|
||||
*/
|
||||
export interface IEntityEnterSystem<E extends Entity = Entity> {
|
||||
entityEnter(entity: E): void;
|
||||
}
|
||||
|
||||
/** 监听组件从实体上移除时,在ComblockSystem上实现这个接口 */
|
||||
export interface IEntityRemoveSystem<E extends Entity = Entity> {
|
||||
entityRemove(entity: E): void;
|
||||
}
|
||||
|
||||
/** 监听系统第一次执行update处理实体时,在ComblockSystem上实现这个接口 */
|
||||
export interface ISystemFirstUpdate<E extends Entity = Entity> {
|
||||
firstUpdate(entity: E): void;
|
||||
}
|
||||
|
||||
/** 监听系统执行update处理实体时,在ComblockSystem上实现这个接口 */
|
||||
export interface ISystemUpdate<E extends Entity = Entity> {
|
||||
update(entity: E): void;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* 注册组件到ecs系统中
|
||||
* @param name 由于js打包会改变类名,所以这里必须手动传入组件的名称
|
||||
* @param canNew 标识是否可以new对象。想继承自Cocos Creator的组件就不能去new,需要写成@ecs.register('name', false)
|
||||
* @example
|
||||
// 注册实体
|
||||
@ecs.register('Role')
|
||||
export class Role extends ecs.Entity {
|
||||
|
||||
}
|
||||
|
||||
// 注册数据组件
|
||||
@ecs.register('RoleModel')
|
||||
export class RoleModelComp extends ecs.Comp {
|
||||
id: number = -1;
|
||||
|
||||
reset() {
|
||||
this.id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 注册系统组件
|
||||
@ecs.register('Initialize')
|
||||
export class InitResSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem {
|
||||
|
||||
}
|
||||
|
||||
// 注册显示对象组件
|
||||
@ccclass('RoleViewComp')
|
||||
@ecs.register('RoleView', false)
|
||||
export class RoleViewComp extends CCComp {
|
||||
onLoad(){
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
export function register<T extends Entity | IComp | ComblockSystem>(name: string, canNew = true) {
|
||||
return function (ctor: EntityCtor<T> | CompCtor<T> | (new () => T)) {
|
||||
const ctorAny = ctor as unknown as {
|
||||
s?: boolean;
|
||||
tid?: number;
|
||||
compName?: string;
|
||||
};
|
||||
|
||||
// 注册系统
|
||||
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 as new () => ComblockSystem)());
|
||||
}
|
||||
// 注册实体
|
||||
else if (ctorAny.tid === undefined) {
|
||||
ECSModel.entityCtors.set(ctor as EntityCtor<ECSEntity>, name);
|
||||
}
|
||||
// 注册组件
|
||||
else {
|
||||
if (ctorAny.tid === -1) {
|
||||
ctorAny.tid = ECSModel.compTid++;
|
||||
ctorAny.compName = name;
|
||||
ECSModel.compCtors.push(ctor as CompCtor<IComp>);
|
||||
ECSModel.compAddOrRemove.set(ctorAny.tid, []);
|
||||
}
|
||||
else {
|
||||
throw new Error(`ECS 组件重复注册: ${name}.`);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的实体对象或从缓存中获取一个实体对象(使用动态池)
|
||||
* @param ctor 实体类
|
||||
*/
|
||||
export function getEntity<T extends Entity>(ctor: EntityCtor<T>): T {
|
||||
// 获取实体对象名
|
||||
const entityName = ECSModel.entityCtors.get(ctor as EntityCtor<ECSEntity>);
|
||||
if (entityName === undefined) {
|
||||
console.error(`${ctor.name} 实体没有注册`);
|
||||
throw new Error(`${ctor.name} 实体没有注册`);
|
||||
}
|
||||
|
||||
// 使用动态池管理器
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
entityName,
|
||||
() => {
|
||||
const entity = new ctor();
|
||||
entity.eid = ECSModel.eid++; // 实体唯一编号
|
||||
entity.name = entityName;
|
||||
return entity;
|
||||
}
|
||||
);
|
||||
|
||||
const entity = pool.get();
|
||||
|
||||
// 触发实体初始化逻辑
|
||||
const entityWithInit = entity as ECSEntity & { init?: () => void };
|
||||
entity.isValid = true; // 无论新建还是复用,都标记为有效
|
||||
if (entityWithInit.init) entityWithInit.init();
|
||||
|
||||
ECSModel.eid2Entity.set((entity as ECSEntity).eid, entity as ECSEntity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态查询实体
|
||||
* @param matcher 匹配器
|
||||
* @example
|
||||
* ecs.query(ecs.allOf(Comp1, Comp2));
|
||||
*/
|
||||
export function query<E extends Entity = Entity>(matcher: IMatcher): E[] {
|
||||
let group = ECSModel.groups.get(matcher.mid);
|
||||
if (!group) {
|
||||
group = ECSModel.createGroup(matcher);
|
||||
ECSModel.eid2Entity.forEach(group.onComponentAddOrRemove, group);
|
||||
}
|
||||
return group.matchEntities as E[];
|
||||
}
|
||||
|
||||
/** 清理所有的实体 */
|
||||
export function clear(): void {
|
||||
ECSModel.eid2Entity.forEach((entity) => {
|
||||
entity.destroy();
|
||||
});
|
||||
ECSModel.groups.forEach((group) => {
|
||||
group.clear();
|
||||
});
|
||||
ECSModel.compAddOrRemove.forEach((callbackLst) => {
|
||||
callbackLst.length = 0;
|
||||
});
|
||||
ECSModel.eid2Entity.clear();
|
||||
ECSModel.groups.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有对象池 - 用于释放不再使用的缓存内存
|
||||
* 注意:此操作会清空所有实体池、组件池和 Mask 池,请在确保不再需要这些缓存时调用
|
||||
*/
|
||||
export function clearPools(): void {
|
||||
// 清理 Mask 对象池
|
||||
ECSMask.clearPool();
|
||||
|
||||
// 清理动态池管理器
|
||||
ecsPoolCoordinator.clearAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过实体唯一编号获得实体对象
|
||||
* @param eid 实体唯一编号
|
||||
*/
|
||||
export function getEntityByEid<E extends Entity = Entity>(eid: number): E | undefined {
|
||||
return ECSModel.eid2Entity.get(eid) as E | undefined;
|
||||
}
|
||||
|
||||
/** 当前活动中的实体数量 */
|
||||
export function activeEntityCount(): number {
|
||||
return ECSModel.eid2Entity.size;
|
||||
}
|
||||
|
||||
/** 创建实体 */
|
||||
function createEntity<E extends Entity = Entity>(): E {
|
||||
const entity = new Entity();
|
||||
entity.eid = ECSModel.eid++; // 实体id也是有限的资源
|
||||
ECSModel.eid2Entity.set((entity as ECSEntity).eid, entity as ECSEntity);
|
||||
return entity as E;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定一个组件创建实体,返回组件对象。
|
||||
* @param ctor
|
||||
*/
|
||||
function createEntityWithComp<T extends IComp>(ctor: CompCtor<T>): T {
|
||||
const entity = createEntity();
|
||||
return entity.add(ctor);
|
||||
}
|
||||
|
||||
//#region 过滤器
|
||||
/**
|
||||
* 表示只关心这些组件的添加和删除动作。虽然实体可能有这些组件之外的组件,但是它们的添加和删除没有被关注,所以不会存在对关注之外的组件
|
||||
* 进行添加操作引发Group重复添加实体。
|
||||
* @param args
|
||||
* @example
|
||||
* ecs.allOf(AComponent, BComponent, CComponent);
|
||||
*/
|
||||
export function allOf(...args: CompType<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().allOf(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件间是或的关系,表示关注拥有任意一个这些组件的实体
|
||||
* @param args 组件类
|
||||
* @example
|
||||
* ecs.anyOf(AComponent, BComponent);
|
||||
*/
|
||||
export function anyOf(...args: CompType<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().anyOf(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示关注只拥有这些组件的实体
|
||||
* 注:不是特殊情况不建议使用onlyOf。因为onlyOf会监听所有组件的添加和删除事件
|
||||
* @param args 组件类
|
||||
* @example
|
||||
// 不包含CComponent或者DComponent
|
||||
ecs.allOf(AComponent, BComponent).excludeOf(CComponent, DComponent);
|
||||
|
||||
// 不同时包含CComponent和DComponent
|
||||
ecs.allOf(AComponent, BComponent).excludeOf(CComponent).excludeOf(DComponent);
|
||||
*/
|
||||
export function onlyOf(...args: CompType<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().onlyOf(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不包含指定的任意一个组件
|
||||
* @param args 组件类
|
||||
* @example
|
||||
// 表示不包含组件A或者组件B
|
||||
ecs.excludeOf(A, B);
|
||||
*/
|
||||
export function excludeOf(...args: CompType<IComp>[]): IMatcher {
|
||||
return new ECSMatcher().excludeOf(...args);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 单例组件
|
||||
/**
|
||||
* 获取单例组件
|
||||
* @param ctor 组件类
|
||||
*/
|
||||
export function getSingleton<T extends IComp>(ctor: CompCtor<T>): T {
|
||||
if (!ECSModel.tid2comp.has(ctor.tid)) {
|
||||
const comp = createEntityWithComp(ctor);
|
||||
ECSModel.tid2comp.set(ctor.tid, comp);
|
||||
}
|
||||
return ECSModel.tid2comp.get(ctor.tid) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册单例组件 - 主要用于那些不能手动创建对象的组件
|
||||
* @param obj
|
||||
*/
|
||||
export function addSingleton(obj: IComp): void {
|
||||
const tid = (obj.constructor as CompCtor<IComp>).tid;
|
||||
if (!ECSModel.tid2comp.has(tid)) {
|
||||
ECSModel.tid2comp.set(tid, obj);
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
@@ -1,470 +1,470 @@
|
||||
import type { ecs } from './ECS';
|
||||
import { ECSMask } from './ECSMask';
|
||||
import type { CompCtor, CompType } from './ECSModel';
|
||||
import { ECSModel } from './ECSModel';
|
||||
import { ecsPoolCoordinator } from './ECSPoolManager';
|
||||
|
||||
//#region 辅助方法
|
||||
|
||||
/**
|
||||
* 实体身上组件有增删操作,广播通知对应的观察者
|
||||
* @param entity 实体对象
|
||||
* @param componentTypeId 组件类型id
|
||||
*/
|
||||
function broadcastCompAddOrRemove(entity: ECSEntity, componentTypeId: number): void {
|
||||
const events = ECSModel.compAddOrRemove.get(componentTypeId);
|
||||
if (events) {
|
||||
for (let i = events.length - 1; i >= 0; i--) {
|
||||
events[i](entity);
|
||||
}
|
||||
}
|
||||
// 判断是不是删了单例组件
|
||||
if (ECSModel.tid2comp.has(componentTypeId)) {
|
||||
ECSModel.tid2comp.delete(componentTypeId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体上的组件属性(类型安全的动态属性访问)
|
||||
*/
|
||||
function setEntityComp<T extends ecs.IComp>(entity: ECSEntity, compName: string, comp: T): void {
|
||||
Reflect.set(entity, compName, comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体上的组件属性(类型安全的动态属性访问)
|
||||
*/
|
||||
function getEntityComp<T extends ecs.IComp>(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
|
||||
*/
|
||||
function createComp<T extends ecs.IComp>(ctor: CompCtor<T>): T {
|
||||
const cct = ECSModel.compCtors[ctor.tid];
|
||||
if (!cct) {
|
||||
throw Error(`没有找到该组件的构造函数,检查${ctor.compName}是否为不可构造的组件`);
|
||||
}
|
||||
|
||||
// 使用动态池管理器
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
ctor.compName,
|
||||
() => new (cct as CompCtor<T>)()
|
||||
);
|
||||
|
||||
const component = pool.get();
|
||||
return component as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁实体(使用动态池管理)
|
||||
*
|
||||
* 缓存销毁的实体,下次新建实体时会优先从缓存中拿
|
||||
* @param entity
|
||||
*/
|
||||
function destroyEntity(entity: ECSEntity): void {
|
||||
if (ECSModel.eid2Entity.has(entity.eid)) {
|
||||
// 使用动态池管理器
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
entity.name,
|
||||
() => new ECSEntity()
|
||||
);
|
||||
|
||||
// 清空mask并回收
|
||||
entity.getMask().clear();
|
||||
pool.recycle(entity);
|
||||
|
||||
ECSModel.eid2Entity.delete(entity.eid);
|
||||
}
|
||||
else {
|
||||
console.warn('试图销毁不存在的实体');
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
/** ECS实体对象 */
|
||||
export class ECSEntity {
|
||||
/** 实体唯一标识,不要手动修改 */
|
||||
eid = -1;
|
||||
/** 实体对象名 */
|
||||
name = '';
|
||||
/** 实体是否有效 */
|
||||
isValid = true;
|
||||
/** 组件过滤数据 */
|
||||
private mask: ECSMask = new ECSMask();
|
||||
/** 当前实体身上附加的组件构造函数 */
|
||||
private compTid2Ctor: Map<number, CompType<ecs.IComp>> = new Map();
|
||||
/**
|
||||
* 配合 entity.remove(Comp, false), 记录组件实例上的缓存数据,在添加时恢复原数据
|
||||
*
|
||||
* ⚠️ 核心机制:
|
||||
* - isRecycle=true:调用reset(),根据canRecycle决定是否回收到全局池
|
||||
* - isRecycle=false:不调用reset(),缓存在实体上
|
||||
*
|
||||
* ⚠️ 使用场景说明:
|
||||
* 1. UI显示/隐藏 - 保留UI状态数据
|
||||
* 2. 技能冷却 - 保留冷却时间
|
||||
* 3. 临时禁用功能 - 保留配置数据
|
||||
*
|
||||
* 💡 管理建议:
|
||||
* - 无任何限制,完全由用户自行管理
|
||||
* - 使用 ECSMemoryMonitor 监控缓存使用情况
|
||||
* - 定期调用 clearComponentCache() 或 clearAllComponentCache() 清理
|
||||
* - 在场景切换、内存警告时主动清理缓存
|
||||
*/
|
||||
private compTid2Obj: Map<number, ecs.IComp> = new Map();
|
||||
|
||||
/** 获取 mask 对象(内部使用) */
|
||||
getMask(): ECSMask {
|
||||
return this.mask;
|
||||
}
|
||||
|
||||
private _parent: ECSEntity | null = null;
|
||||
/** 父实体 */
|
||||
get parent(): ECSEntity | null {
|
||||
return this._parent;
|
||||
}
|
||||
set parent(value: ECSEntity | null) {
|
||||
this._parent = value;
|
||||
}
|
||||
|
||||
/** 子实体 */
|
||||
private childs: Map<number, ECSEntity> | null = null;
|
||||
|
||||
/** 获取子实体 */
|
||||
getChild<T extends ECSEntity>(eid: number): T | undefined {
|
||||
return this.childs?.get(eid) as T | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加子实体(带循环引用检测)
|
||||
* @param entity 被添加的实体对象
|
||||
* @returns 子实体的唯一编号, -1表示添加失败
|
||||
*/
|
||||
addChild(entity: ECSEntity): number {
|
||||
if (this.childs === null) {
|
||||
this.childs = new Map<number, ECSEntity>();
|
||||
}
|
||||
|
||||
if (this.childs.has(entity.eid)) {
|
||||
console.warn(`子实体${entity.name}已存在`);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 检测循环引用
|
||||
if (this.hasAncestor(entity)) {
|
||||
console.error(`检测到循环引用: ${this.name} -> ${entity.name}`);
|
||||
return -1;
|
||||
}
|
||||
|
||||
entity._parent = this;
|
||||
this.childs.set(entity.eid, entity);
|
||||
return entity.eid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否存在祖先关系(防止循环引用)
|
||||
*/
|
||||
private hasAncestor(entity: ECSEntity): boolean {
|
||||
let current: ECSEntity | null = this;
|
||||
while (current) {
|
||||
if (current === entity) return true;
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除子实体
|
||||
* @param entity 被移除的实体对象
|
||||
* @param isDestroy 被移除的实体是否释放,默认为释放
|
||||
* @returns
|
||||
*/
|
||||
removeChild(entity: ECSEntity, isDestroy = true): void {
|
||||
if (this.childs === null) return;
|
||||
|
||||
entity.parent = null;
|
||||
this.childs.delete(entity.eid);
|
||||
if (isDestroy) entity.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据组件类动态创建组件,并通知关心的系统。如果实体存在了这个组件,那么会先删除之前的组件然后添加新的
|
||||
*
|
||||
* 注意:不要直接new Component,new来的Component不会从Component的缓存池拿缓存的数据
|
||||
* @param componentTypeId 组件类
|
||||
* @param isReAdd true-表示用户指定这个实体可能已经存在了该组件,那么再次add组件的时候会先移除该组件然后再添加一遍。false-表示不重复添加组件
|
||||
*/
|
||||
add<T extends ecs.IComp>(obj: T): ECSEntity;
|
||||
add<T extends ecs.IComp>(ctor: CompType<T>, isReAdd?: boolean): T;
|
||||
add<T extends ecs.IComp>(ctor: CompType<T> | T, isReAdd = false): T | ECSEntity {
|
||||
if (typeof ctor === 'function') {
|
||||
const compTid = ctor.tid;
|
||||
if (ctor.tid === -1) {
|
||||
throw Error(`【${this.name}】实体【${ctor.compName}】组件未注册`);
|
||||
}
|
||||
if (this.compTid2Ctor.has(compTid)) { // 判断是否有该组件,如果有则先移除
|
||||
if (isReAdd) {
|
||||
this.remove(ctor);
|
||||
}
|
||||
else {
|
||||
console.log(`【${this.name}】实体【${ctor.compName}】组件已存在`);
|
||||
const existingComp = getEntityComp<T>(this, ctor.compName);
|
||||
if (existingComp) {
|
||||
return existingComp;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.mask.set(compTid);
|
||||
|
||||
let comp: T;
|
||||
if (this.compTid2Obj.has(compTid)) {
|
||||
comp = this.compTid2Obj.get(compTid) as T;
|
||||
this.compTid2Obj.delete(compTid);
|
||||
}
|
||||
else {
|
||||
// 创建组件对象
|
||||
comp = createComp(ctor);
|
||||
}
|
||||
|
||||
// 将组件对象直接附加到实体对象身上,方便直接获取
|
||||
setEntityComp(this, ctor.compName, comp);
|
||||
this.compTid2Ctor.set(compTid, ctor);
|
||||
comp.tid = compTid;
|
||||
comp.ent = this;
|
||||
// 广播实体添加组件的消息
|
||||
broadcastCompAddOrRemove(this, compTid);
|
||||
|
||||
return comp;
|
||||
}
|
||||
else {
|
||||
// 此时 ctor 是组件实例
|
||||
const compInstance = ctor as T;
|
||||
const tmpCtor = (compInstance.constructor as CompCtor<T>);
|
||||
const compTid = tmpCtor.tid;
|
||||
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);
|
||||
setEntityComp(this, tmpCtor.compName, compInstance);
|
||||
this.compTid2Ctor.set(compTid, tmpCtor);
|
||||
compInstance.tid = compTid;
|
||||
compInstance.canRecycle = false;
|
||||
compInstance.ent = this;
|
||||
broadcastCompAddOrRemove(this, compTid);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加组件
|
||||
* @param ctors 组件类
|
||||
* @returns
|
||||
*/
|
||||
addComponents<T extends ecs.IComp>(...ctors: CompType<T>[]): this {
|
||||
const len = ctors.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
this.add(ctors[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个组件实例
|
||||
* @param ctor 组件类
|
||||
*/
|
||||
get<T extends ecs.IComp>(ctor: CompCtor<T>): T | undefined {
|
||||
return getEntityComp<T>(this, ctor.compName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件是否在实体存在内
|
||||
* @param ctor 组件类
|
||||
*/
|
||||
has(ctor: CompType<ecs.IComp>): boolean {
|
||||
const tid = typeof ctor === 'number' ? ctor : ctor.tid;
|
||||
return this.compTid2Ctor.has(tid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从实体上删除指定组件
|
||||
* @param ctor 组件构造函数或者组件Tag
|
||||
* @param isRecycle 是否回收该组件对象
|
||||
*
|
||||
* **isRecycle=true(默认):**
|
||||
* - 调用组件的 reset() 方法清理数据
|
||||
* - 如果 canRecycle=true,回收到全局对象池供复用
|
||||
* - 如果 canRecycle=false,直接销毁
|
||||
* - 适用于大部分场景
|
||||
*
|
||||
* **isRecycle=false:**
|
||||
* - 不调用 reset(),数据完整保留
|
||||
* - 组件缓存在当前实体上,下次 add() 时恢复
|
||||
* - 无任何限制和检查,完全由用户自行管理
|
||||
* - 适用于需要保留状态的场景(UI切换、技能冷却等)
|
||||
*
|
||||
* ⚠️ isRecycle=false 使用场景:
|
||||
* - UI显示/隐藏:保留UI状态(如滚动位置、选中项)
|
||||
* - 技能冷却:保留冷却剩余时间
|
||||
* - 临时禁用:保留配置数据,稍后恢复
|
||||
*
|
||||
* 💡 管理建议:
|
||||
* - 使用 ECSMemoryMonitor 监控缓存使用情况
|
||||
* - 定期调用 clearComponentCache() 或 clearAllComponentCache() 清理
|
||||
* - 在场景切换、内存警告时主动清理缓存
|
||||
*/
|
||||
remove(ctor: CompType<ecs.IComp>, isRecycle = true): void {
|
||||
const componentTypeId = typeof ctor === 'number' ? ctor : ctor.tid;
|
||||
const compName = typeof ctor === 'number' ? '' : ctor.compName;
|
||||
|
||||
if (!this.mask.has(componentTypeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const comp = getEntityComp<ecs.IComp>(this, compName);
|
||||
if (!comp) {
|
||||
return;
|
||||
}
|
||||
|
||||
comp.ent = null!;
|
||||
|
||||
if (isRecycle) {
|
||||
comp.reset();
|
||||
|
||||
// 回收到全局池
|
||||
if (comp.canRecycle) {
|
||||
const ctor = ECSModel.compCtors[componentTypeId];
|
||||
if (ctor) {
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
ctor.compName,
|
||||
() => new (ctor as any)()
|
||||
);
|
||||
pool.recycle(comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// isRecycle=false:用户明确要求缓存,完全尊重用户意图
|
||||
// 不做任何检查和限制,由用户通过 ECSMemoryMonitor 自行管理
|
||||
this.compTid2Obj.set(componentTypeId, comp);
|
||||
}
|
||||
|
||||
// 删除实体上的组件逻辑
|
||||
deleteEntityComp(this, compName);
|
||||
this.mask.delete(componentTypeId);
|
||||
this.compTid2Ctor.delete(componentTypeId);
|
||||
broadcastCompAddOrRemove(this, componentTypeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理指定组件的缓存
|
||||
* @param ctor 组件构造函数
|
||||
*/
|
||||
clearComponentCache(ctor: CompType<ecs.IComp>): void {
|
||||
const componentTypeId = typeof ctor === 'number' ? ctor : ctor.tid;
|
||||
const comp = this.compTid2Obj.get(componentTypeId);
|
||||
|
||||
if (comp) {
|
||||
this.compTid2Obj.delete(componentTypeId);
|
||||
|
||||
const ctorObj = ECSModel.compCtors[componentTypeId];
|
||||
const compName = ctorObj?.compName || `tid:${componentTypeId}`;
|
||||
|
||||
comp.reset();
|
||||
if (comp.canRecycle) {
|
||||
if (ctorObj) {
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
ctorObj.compName,
|
||||
() => new (ctorObj as any)()
|
||||
);
|
||||
pool.recycle(comp);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[ECS] 实体 ${this.name} 清理组件缓存: ${compName}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有组件缓存
|
||||
*/
|
||||
clearAllComponentCache(): void {
|
||||
if (this.compTid2Obj.size === 0) return;
|
||||
|
||||
const count = this.compTid2Obj.size;
|
||||
|
||||
this.compTid2Obj.forEach((comp, tid) => {
|
||||
comp.reset();
|
||||
if (comp.canRecycle) {
|
||||
const ctor = ECSModel.compCtors[tid];
|
||||
if (ctor) {
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
ctor.compName,
|
||||
() => new (ctor as any)()
|
||||
);
|
||||
pool.recycle(comp);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.compTid2Obj.clear();
|
||||
|
||||
console.log(`[ECS] 实体 ${this.name} 清理所有缓存,共 ${count} 个组件`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存的组件数量
|
||||
*/
|
||||
getCachedComponentCount(): number {
|
||||
return this.compTid2Obj.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存的组件列表(用于监控)
|
||||
*/
|
||||
getCachedComponents(): Map<number, ecs.IComp> {
|
||||
return new Map(this.compTid2Obj);
|
||||
}
|
||||
|
||||
/** 销毁实体,实体会被回收到实体缓存池中 */
|
||||
destroy(): void {
|
||||
this.isValid = false;
|
||||
|
||||
// 如果有父模块,则移除父模块上记录的子模块
|
||||
if (this._parent) {
|
||||
this._parent.removeChild(this, false);
|
||||
this._parent = null;
|
||||
}
|
||||
|
||||
// 移除模块上所有子模块
|
||||
if (this.childs) {
|
||||
this.childs.forEach((e) => {
|
||||
this.removeChild(e);
|
||||
});
|
||||
this.childs.clear();
|
||||
this.childs = null;
|
||||
}
|
||||
|
||||
// 移除实体上所有组件
|
||||
this.compTid2Ctor.forEach(this._remove, this);
|
||||
destroyEntity(this);
|
||||
|
||||
// 清理缓存的组件对象,防止内存泄漏
|
||||
this.clearAllComponentCache();
|
||||
}
|
||||
|
||||
private _remove(comp: CompType<ecs.IComp>): void {
|
||||
this.remove(comp, true);
|
||||
}
|
||||
}
|
||||
import type { ecs } from './ECS';
|
||||
import { ECSMask } from './ECSMask';
|
||||
import type { CompCtor, CompType } from './ECSModel';
|
||||
import { ECSModel } from './ECSModel';
|
||||
import { ecsPoolCoordinator } from './pool';
|
||||
|
||||
//#region 辅助方法
|
||||
|
||||
/**
|
||||
* 实体身上组件有增删操作,广播通知对应的观察者
|
||||
* @param entity 实体对象
|
||||
* @param componentTypeId 组件类型id
|
||||
*/
|
||||
function broadcastCompAddOrRemove(entity: ECSEntity, componentTypeId: number): void {
|
||||
const events = ECSModel.compAddOrRemove.get(componentTypeId);
|
||||
if (events) {
|
||||
for (let i = events.length - 1; i >= 0; i--) {
|
||||
events[i](entity);
|
||||
}
|
||||
}
|
||||
// 判断是不是删了单例组件
|
||||
if (ECSModel.tid2comp.has(componentTypeId)) {
|
||||
ECSModel.tid2comp.delete(componentTypeId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体上的组件属性(类型安全的动态属性访问)
|
||||
*/
|
||||
function setEntityComp<T extends ecs.IComp>(entity: ECSEntity, compName: string, comp: T): void {
|
||||
Reflect.set(entity, compName, comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体上的组件属性(类型安全的动态属性访问)
|
||||
*/
|
||||
function getEntityComp<T extends ecs.IComp>(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
|
||||
*/
|
||||
function createComp<T extends ecs.IComp>(ctor: CompCtor<T>): T {
|
||||
const cct = ECSModel.compCtors[ctor.tid];
|
||||
if (!cct) {
|
||||
throw Error(`没有找到该组件的构造函数,检查${ctor.compName}是否为不可构造的组件`);
|
||||
}
|
||||
|
||||
// 使用动态池管理器
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
ctor.compName,
|
||||
() => new (cct as CompCtor<T>)()
|
||||
);
|
||||
|
||||
const component = pool.get();
|
||||
return component as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁实体(使用动态池管理)
|
||||
*
|
||||
* 缓存销毁的实体,下次新建实体时会优先从缓存中拿
|
||||
* @param entity
|
||||
*/
|
||||
function destroyEntity(entity: ECSEntity): void {
|
||||
if (ECSModel.eid2Entity.has(entity.eid)) {
|
||||
// 使用动态池管理器
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
entity.name,
|
||||
() => new ECSEntity()
|
||||
);
|
||||
|
||||
// 清空mask并回收
|
||||
entity.getMask().clear();
|
||||
pool.recycle(entity);
|
||||
|
||||
ECSModel.eid2Entity.delete(entity.eid);
|
||||
}
|
||||
else {
|
||||
console.warn('试图销毁不存在的实体');
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
/** ECS实体对象 */
|
||||
export class ECSEntity {
|
||||
/** 实体唯一标识,不要手动修改 */
|
||||
eid = -1;
|
||||
/** 实体对象名 */
|
||||
name = '';
|
||||
/** 实体是否有效 */
|
||||
isValid = true;
|
||||
/** 组件过滤数据 */
|
||||
private mask: ECSMask = new ECSMask();
|
||||
/** 当前实体身上附加的组件构造函数 */
|
||||
private compTid2Ctor: Map<number, CompType<ecs.IComp>> = new Map();
|
||||
/**
|
||||
* 配合 entity.remove(Comp, false), 记录组件实例上的缓存数据,在添加时恢复原数据
|
||||
*
|
||||
* ⚠️ 核心机制:
|
||||
* - isRecycle=true:调用reset(),根据canRecycle决定是否回收到全局池
|
||||
* - isRecycle=false:不调用reset(),缓存在实体上
|
||||
*
|
||||
* ⚠️ 使用场景说明:
|
||||
* 1. UI显示/隐藏 - 保留UI状态数据
|
||||
* 2. 技能冷却 - 保留冷却时间
|
||||
* 3. 临时禁用功能 - 保留配置数据
|
||||
*
|
||||
* 💡 管理建议:
|
||||
* - 无任何限制,完全由用户自行管理
|
||||
* - 使用 ECSMemoryMonitor 监控缓存使用情况
|
||||
* - 定期调用 clearComponentCache() 或 clearAllComponentCache() 清理
|
||||
* - 在场景切换、内存警告时主动清理缓存
|
||||
*/
|
||||
private compTid2Obj: Map<number, ecs.IComp> = new Map();
|
||||
|
||||
/** 获取 mask 对象(内部使用) */
|
||||
getMask(): ECSMask {
|
||||
return this.mask;
|
||||
}
|
||||
|
||||
private _parent: ECSEntity | null = null;
|
||||
/** 父实体 */
|
||||
get parent(): ECSEntity | null {
|
||||
return this._parent;
|
||||
}
|
||||
set parent(value: ECSEntity | null) {
|
||||
this._parent = value;
|
||||
}
|
||||
|
||||
/** 子实体 */
|
||||
private childs: Map<number, ECSEntity> | null = null;
|
||||
|
||||
/** 获取子实体 */
|
||||
getChild<T extends ECSEntity>(eid: number): T | undefined {
|
||||
return this.childs?.get(eid) as T | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加子实体(带循环引用检测)
|
||||
* @param entity 被添加的实体对象
|
||||
* @returns 子实体的唯一编号, -1表示添加失败
|
||||
*/
|
||||
addChild(entity: ECSEntity): number {
|
||||
if (this.childs === null) {
|
||||
this.childs = new Map<number, ECSEntity>();
|
||||
}
|
||||
|
||||
if (this.childs.has(entity.eid)) {
|
||||
console.warn(`子实体${entity.name}已存在`);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 检测循环引用
|
||||
if (this.hasAncestor(entity)) {
|
||||
console.error(`检测到循环引用: ${this.name} -> ${entity.name}`);
|
||||
return -1;
|
||||
}
|
||||
|
||||
entity._parent = this;
|
||||
this.childs.set(entity.eid, entity);
|
||||
return entity.eid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否存在祖先关系(防止循环引用)
|
||||
*/
|
||||
private hasAncestor(entity: ECSEntity): boolean {
|
||||
let current: ECSEntity | null = this;
|
||||
while (current) {
|
||||
if (current === entity) return true;
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除子实体
|
||||
* @param entity 被移除的实体对象
|
||||
* @param isDestroy 被移除的实体是否释放,默认为释放
|
||||
* @returns
|
||||
*/
|
||||
removeChild(entity: ECSEntity, isDestroy = true): void {
|
||||
if (this.childs === null) return;
|
||||
|
||||
entity.parent = null;
|
||||
this.childs.delete(entity.eid);
|
||||
if (isDestroy) entity.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据组件类动态创建组件,并通知关心的系统。如果实体存在了这个组件,那么会先删除之前的组件然后添加新的
|
||||
*
|
||||
* 注意:不要直接new Component,new来的Component不会从Component的缓存池拿缓存的数据
|
||||
* @param componentTypeId 组件类
|
||||
* @param isReAdd true-表示用户指定这个实体可能已经存在了该组件,那么再次add组件的时候会先移除该组件然后再添加一遍。false-表示不重复添加组件
|
||||
*/
|
||||
add<T extends ecs.IComp>(obj: T): ECSEntity;
|
||||
add<T extends ecs.IComp>(ctor: CompType<T>, isReAdd?: boolean): T;
|
||||
add<T extends ecs.IComp>(ctor: CompType<T> | T, isReAdd = false): T | ECSEntity {
|
||||
if (typeof ctor === 'function') {
|
||||
const compTid = ctor.tid;
|
||||
if (ctor.tid === -1) {
|
||||
throw Error(`【${this.name}】实体【${ctor.compName}】组件未注册`);
|
||||
}
|
||||
if (this.compTid2Ctor.has(compTid)) { // 判断是否有该组件,如果有则先移除
|
||||
if (isReAdd) {
|
||||
this.remove(ctor);
|
||||
}
|
||||
else {
|
||||
console.log(`【${this.name}】实体【${ctor.compName}】组件已存在`);
|
||||
const existingComp = getEntityComp<T>(this, ctor.compName);
|
||||
if (existingComp) {
|
||||
return existingComp;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.mask.set(compTid);
|
||||
|
||||
let comp: T;
|
||||
if (this.compTid2Obj.has(compTid)) {
|
||||
comp = this.compTid2Obj.get(compTid) as T;
|
||||
this.compTid2Obj.delete(compTid);
|
||||
}
|
||||
else {
|
||||
// 创建组件对象
|
||||
comp = createComp(ctor);
|
||||
}
|
||||
|
||||
// 将组件对象直接附加到实体对象身上,方便直接获取
|
||||
setEntityComp(this, ctor.compName, comp);
|
||||
this.compTid2Ctor.set(compTid, ctor);
|
||||
comp.tid = compTid;
|
||||
comp.ent = this;
|
||||
// 广播实体添加组件的消息
|
||||
broadcastCompAddOrRemove(this, compTid);
|
||||
|
||||
return comp;
|
||||
}
|
||||
else {
|
||||
// 此时 ctor 是组件实例
|
||||
const compInstance = ctor as T;
|
||||
const tmpCtor = (compInstance.constructor as CompCtor<T>);
|
||||
const compTid = tmpCtor.tid;
|
||||
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);
|
||||
setEntityComp(this, tmpCtor.compName, compInstance);
|
||||
this.compTid2Ctor.set(compTid, tmpCtor);
|
||||
compInstance.tid = compTid;
|
||||
compInstance.canRecycle = false;
|
||||
compInstance.ent = this;
|
||||
broadcastCompAddOrRemove(this, compTid);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加组件
|
||||
* @param ctors 组件类
|
||||
* @returns
|
||||
*/
|
||||
addComponents<T extends ecs.IComp>(...ctors: CompType<T>[]): this {
|
||||
const len = ctors.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
this.add(ctors[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个组件实例
|
||||
* @param ctor 组件类
|
||||
*/
|
||||
get<T extends ecs.IComp>(ctor: CompCtor<T>): T | undefined {
|
||||
return getEntityComp<T>(this, ctor.compName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件是否在实体存在内
|
||||
* @param ctor 组件类
|
||||
*/
|
||||
has(ctor: CompType<ecs.IComp>): boolean {
|
||||
const tid = typeof ctor === 'number' ? ctor : ctor.tid;
|
||||
return this.compTid2Ctor.has(tid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从实体上删除指定组件
|
||||
* @param ctor 组件构造函数或者组件Tag
|
||||
* @param isRecycle 是否回收该组件对象
|
||||
*
|
||||
* **isRecycle=true(默认):**
|
||||
* - 调用组件的 reset() 方法清理数据
|
||||
* - 如果 canRecycle=true,回收到全局对象池供复用
|
||||
* - 如果 canRecycle=false,直接销毁
|
||||
* - 适用于大部分场景
|
||||
*
|
||||
* **isRecycle=false:**
|
||||
* - 不调用 reset(),数据完整保留
|
||||
* - 组件缓存在当前实体上,下次 add() 时恢复
|
||||
* - 无任何限制和检查,完全由用户自行管理
|
||||
* - 适用于需要保留状态的场景(UI切换、技能冷却等)
|
||||
*
|
||||
* ⚠️ isRecycle=false 使用场景:
|
||||
* - UI显示/隐藏:保留UI状态(如滚动位置、选中项)
|
||||
* - 技能冷却:保留冷却剩余时间
|
||||
* - 临时禁用:保留配置数据,稍后恢复
|
||||
*
|
||||
* 💡 管理建议:
|
||||
* - 使用 ECSMemoryMonitor 监控缓存使用情况
|
||||
* - 定期调用 clearComponentCache() 或 clearAllComponentCache() 清理
|
||||
* - 在场景切换、内存警告时主动清理缓存
|
||||
*/
|
||||
remove(ctor: CompType<ecs.IComp>, isRecycle = true): void {
|
||||
const componentTypeId = typeof ctor === 'number' ? ctor : ctor.tid;
|
||||
const compName = typeof ctor === 'number' ? '' : ctor.compName;
|
||||
|
||||
if (!this.mask.has(componentTypeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const comp = getEntityComp<ecs.IComp>(this, compName);
|
||||
if (!comp) {
|
||||
return;
|
||||
}
|
||||
|
||||
comp.ent = null!;
|
||||
|
||||
if (isRecycle) {
|
||||
comp.reset();
|
||||
|
||||
// 回收到全局池
|
||||
if (comp.canRecycle) {
|
||||
const ctor = ECSModel.compCtors[componentTypeId];
|
||||
if (ctor) {
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
ctor.compName,
|
||||
() => new (ctor as any)()
|
||||
);
|
||||
pool.recycle(comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// isRecycle=false:用户明确要求缓存,完全尊重用户意图
|
||||
// 不做任何检查和限制,由用户通过 ECSMemoryMonitor 自行管理
|
||||
this.compTid2Obj.set(componentTypeId, comp);
|
||||
}
|
||||
|
||||
// 删除实体上的组件逻辑
|
||||
deleteEntityComp(this, compName);
|
||||
this.mask.delete(componentTypeId);
|
||||
this.compTid2Ctor.delete(componentTypeId);
|
||||
broadcastCompAddOrRemove(this, componentTypeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理指定组件的缓存
|
||||
* @param ctor 组件构造函数
|
||||
*/
|
||||
clearComponentCache(ctor: CompType<ecs.IComp>): void {
|
||||
const componentTypeId = typeof ctor === 'number' ? ctor : ctor.tid;
|
||||
const comp = this.compTid2Obj.get(componentTypeId);
|
||||
|
||||
if (comp) {
|
||||
this.compTid2Obj.delete(componentTypeId);
|
||||
|
||||
const ctorObj = ECSModel.compCtors[componentTypeId];
|
||||
const compName = ctorObj?.compName || `tid:${componentTypeId}`;
|
||||
|
||||
comp.reset();
|
||||
if (comp.canRecycle) {
|
||||
if (ctorObj) {
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
ctorObj.compName,
|
||||
() => new (ctorObj as any)()
|
||||
);
|
||||
pool.recycle(comp);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[ECS] 实体 ${this.name} 清理组件缓存: ${compName}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有组件缓存
|
||||
*/
|
||||
clearAllComponentCache(): void {
|
||||
if (this.compTid2Obj.size === 0) return;
|
||||
|
||||
const count = this.compTid2Obj.size;
|
||||
|
||||
this.compTid2Obj.forEach((comp, tid) => {
|
||||
comp.reset();
|
||||
if (comp.canRecycle) {
|
||||
const ctor = ECSModel.compCtors[tid];
|
||||
if (ctor) {
|
||||
const pool = ecsPoolCoordinator.getPool(
|
||||
ctor.compName,
|
||||
() => new (ctor as any)()
|
||||
);
|
||||
pool.recycle(comp);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.compTid2Obj.clear();
|
||||
|
||||
console.log(`[ECS] 实体 ${this.name} 清理所有缓存,共 ${count} 个组件`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存的组件数量
|
||||
*/
|
||||
getCachedComponentCount(): number {
|
||||
return this.compTid2Obj.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存的组件列表(用于监控)
|
||||
*/
|
||||
getCachedComponents(): Map<number, ecs.IComp> {
|
||||
return new Map(this.compTid2Obj);
|
||||
}
|
||||
|
||||
/** 销毁实体,实体会被回收到实体缓存池中 */
|
||||
destroy(): void {
|
||||
this.isValid = false;
|
||||
|
||||
// 如果有父模块,则移除父模块上记录的子模块
|
||||
if (this._parent) {
|
||||
this._parent.removeChild(this, false);
|
||||
this._parent = null;
|
||||
}
|
||||
|
||||
// 移除模块上所有子模块
|
||||
if (this.childs) {
|
||||
this.childs.forEach((e) => {
|
||||
this.removeChild(e);
|
||||
});
|
||||
this.childs.clear();
|
||||
this.childs = null;
|
||||
}
|
||||
|
||||
// 移除实体上所有组件
|
||||
this.compTid2Ctor.forEach(this._remove, this);
|
||||
destroyEntity(this);
|
||||
|
||||
// 清理缓存的组件对象,防止内存泄漏
|
||||
this.clearAllComponentCache();
|
||||
}
|
||||
|
||||
private _remove(comp: CompType<ecs.IComp>): void {
|
||||
this.remove(comp, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { ECSModel } from './ECSModel';
|
||||
import { ecsPoolCoordinator } from './ECSPoolManager';
|
||||
import { ecsPoolCoordinator } from './pool';
|
||||
|
||||
/** 监控数据项 */
|
||||
interface MonitorItem {
|
||||
|
||||
@@ -1,324 +0,0 @@
|
||||
/*
|
||||
* 对象池管理系统
|
||||
*/
|
||||
|
||||
//#region 类型定义
|
||||
|
||||
/** 池统计指标 */
|
||||
interface PoolMetrics {
|
||||
/** 创建次数 */
|
||||
createCount: number;
|
||||
/** 回收次数 */
|
||||
recycleCount: number;
|
||||
/** 命中次数(从池中获取) */
|
||||
hitCount: number;
|
||||
/** 未命中次数(需要新建) */
|
||||
missCount: number;
|
||||
/** 当前池大小 */
|
||||
currentSize: number;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 动态对象池
|
||||
|
||||
/**
|
||||
* 动态对象池
|
||||
*/
|
||||
class DynamicPool<T> {
|
||||
private pool: T[] = [];
|
||||
private metrics: PoolMetrics;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param typeName 池类型名称
|
||||
* @param factory 对象工厂函数
|
||||
*/
|
||||
constructor(
|
||||
private typeName: string,
|
||||
private factory: () => T
|
||||
) {
|
||||
this.metrics = {
|
||||
createCount: 0,
|
||||
recycleCount: 0,
|
||||
hitCount: 0,
|
||||
missCount: 0,
|
||||
currentSize: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从池中获取对象
|
||||
* @returns 池中的对象或新创建的对象
|
||||
*/
|
||||
get(): T {
|
||||
if (this.pool.length > 0) {
|
||||
this.metrics.hitCount++;
|
||||
const item = this.pool.pop()!;
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
return item;
|
||||
}
|
||||
|
||||
this.metrics.missCount++;
|
||||
this.metrics.createCount++;
|
||||
|
||||
return this.factory();
|
||||
}
|
||||
|
||||
/**
|
||||
* 回收对象到池中
|
||||
* @param item 要回收的对象
|
||||
*/
|
||||
recycle(item: T): void {
|
||||
this.pool.push(item);
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预热池,提前创建指定数量的对象
|
||||
* @param count 要预热的对象数量
|
||||
*/
|
||||
preWarm(count: number): void {
|
||||
while (this.pool.length < count) {
|
||||
this.pool.push(this.factory());
|
||||
this.metrics.createCount++;
|
||||
}
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池的统计信息
|
||||
* @returns 只读的统计指标对象
|
||||
*/
|
||||
getMetrics(): Readonly<PoolMetrics> {
|
||||
return { ...this.metrics };
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空池中的所有对象
|
||||
*/
|
||||
clear(): void {
|
||||
this.pool.length = 0;
|
||||
this.metrics.currentSize = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动缩减池大小到指定容量
|
||||
* @param targetSize 目标池大小
|
||||
* @returns 移除的对象数量
|
||||
*/
|
||||
shrinkTo(targetSize: number): number {
|
||||
let removed = 0;
|
||||
|
||||
while (this.pool.length > targetSize) {
|
||||
this.pool.pop();
|
||||
removed++;
|
||||
}
|
||||
|
||||
if (removed > 0) {
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 池管理器
|
||||
|
||||
/**
|
||||
* 池管理器
|
||||
*/
|
||||
class PoolManager {
|
||||
private pools: Map<string, DynamicPool<any>> = new Map();
|
||||
|
||||
/**
|
||||
* 获取或创建池
|
||||
* @param typeName 池类型名称
|
||||
* @param factory 对象工厂函数
|
||||
* @returns 动态对象池实例
|
||||
*/
|
||||
getPool<T>(typeName: string, factory: () => T): DynamicPool<T> {
|
||||
if (!this.pools.has(typeName)) {
|
||||
const pool = new DynamicPool(typeName, factory);
|
||||
this.pools.set(typeName, pool);
|
||||
}
|
||||
|
||||
return this.pools.get(typeName)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有池
|
||||
*/
|
||||
clearAll(): void {
|
||||
this.pools.forEach(pool => pool.clear());
|
||||
this.pools.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景切换时预热池
|
||||
* @param sceneType 场景类型标识
|
||||
* @param hints 类型名称到预热数量的映射
|
||||
*/
|
||||
onSceneChange(sceneType: string, hints: Map<string, number>): void {
|
||||
hints.forEach((count, typeName) => {
|
||||
const pool = this.pools.get(typeName);
|
||||
if (pool) {
|
||||
pool.preWarm(count);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有池的统计信息
|
||||
* @returns 类型名称到统计指标的映射
|
||||
*/
|
||||
getAllMetrics(): Map<string, PoolMetrics> {
|
||||
const result = new Map<string, PoolMetrics>();
|
||||
this.pools.forEach((pool, typeName) => {
|
||||
result.set(typeName, pool.getMetrics());
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动缩减所有池到指定百分比
|
||||
* @param percent 目标百分比(0-1之间)
|
||||
* @returns 总共移除的对象数量
|
||||
*/
|
||||
shrinkAllTo(percent: number): number {
|
||||
let totalRemoved = 0;
|
||||
this.pools.forEach(pool => {
|
||||
const metrics = pool.getMetrics();
|
||||
const targetSize = Math.floor(metrics.currentSize * percent);
|
||||
totalRemoved += pool.shrinkTo(targetSize);
|
||||
});
|
||||
return totalRemoved;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的池
|
||||
* @param typeName 池类型名称
|
||||
* @returns 池实例,如果不存在则返回undefined
|
||||
*/
|
||||
getPoolByName(typeName: string): DynamicPool<any> | undefined {
|
||||
return this.pools.get(typeName);
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 全局协调器
|
||||
|
||||
/**
|
||||
* 全局池协调器 - 提供手动管理API
|
||||
*/
|
||||
class GlobalPoolCoordinator {
|
||||
private poolManager: PoolManager;
|
||||
|
||||
constructor() {
|
||||
this.poolManager = new PoolManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建池
|
||||
* @param typeName 池类型名称
|
||||
* @param factory 对象工厂函数
|
||||
* @returns 动态对象池实例
|
||||
*/
|
||||
getPool<T>(typeName: string, factory: () => T): DynamicPool<T> {
|
||||
return this.poolManager.getPool(typeName, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池管理器实例
|
||||
* @returns 池管理器
|
||||
*/
|
||||
getPoolManager(): PoolManager {
|
||||
return this.poolManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景切换时预热池
|
||||
* @param sceneType 场景类型标识
|
||||
* @param hints 类型名称到预热数量的映射
|
||||
*/
|
||||
onSceneChange(sceneType: string, hints: Map<string, number>): void {
|
||||
this.poolManager.onSceneChange(sceneType, hints);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动缩减所有池到指定百分比
|
||||
* @param percent 目标百分比(0-1之间)
|
||||
* @returns 总共移除的对象数量
|
||||
*/
|
||||
shrinkAll(percent: number): number {
|
||||
return this.poolManager.shrinkAllTo(percent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有池中的对象
|
||||
*/
|
||||
clearAll(): void {
|
||||
this.poolManager.clearAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空指定池中的对象
|
||||
* @param typeName 池类型名称
|
||||
*/
|
||||
clearPool(typeName: string): void {
|
||||
const pool = this.poolManager.getPoolByName(typeName);
|
||||
if (pool) {
|
||||
pool.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动缩减指定池到目标大小
|
||||
* @param typeName 池类型名称
|
||||
* @param targetSize 目标池大小
|
||||
* @returns 移除的对象数量
|
||||
*/
|
||||
shrinkPool(typeName: string, targetSize: number): number {
|
||||
const pool = this.poolManager.getPoolByName(typeName);
|
||||
return pool ? pool.shrinkTo(targetSize) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有池的统计信息
|
||||
* @returns 类型名称到统计指标的映射
|
||||
*/
|
||||
getAllMetrics(): Map<string, PoolMetrics> {
|
||||
return this.poolManager.getAllMetrics();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定池的统计信息
|
||||
* @param typeName 池类型名称
|
||||
* @returns 统计指标对象,如果池不存在则返回undefined
|
||||
*/
|
||||
getPoolMetrics(typeName: string): PoolMetrics | undefined {
|
||||
const pool = this.poolManager.getPoolByName(typeName);
|
||||
return pool ? pool.getMetrics() : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* 全局池协调器实例
|
||||
*
|
||||
* 用于管理 ECS 框架中的对象池,主要包括:
|
||||
* 1. ECS 实体对象 (ECSEntity) - 实体销毁后回收复用
|
||||
* 2. ECS 组件对象 (IComp) - 组件移除后回收复用
|
||||
* 3. 其他自定义对象 - 支持任意类型的对象池化
|
||||
*
|
||||
* 核心功能:
|
||||
* - 对象复用:减少频繁创建销毁带来的性能开销
|
||||
* - 统计监控:跟踪命中率、创建次数等指标
|
||||
* - 池管理:支持预热、缩减、清空等操作
|
||||
* - 场景优化:场景切换时自动预热相关对象池
|
||||
*/
|
||||
export const ecsPoolCoordinator = new GlobalPoolCoordinator();
|
||||
9
assets/libs/ecs/pool.meta
Normal file
9
assets/libs/ecs/pool.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "84525bc8-4095-4145-b04b-621c673eee5d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
108
assets/libs/ecs/pool/ECSDynamicPool.ts
Normal file
108
assets/libs/ecs/pool/ECSDynamicPool.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 动态对象池
|
||||
*/
|
||||
|
||||
import { IECSPoolMetrics } from './IECSPoolMetrics';
|
||||
|
||||
/**
|
||||
* 动态对象池
|
||||
* @template T 池中对象的类型
|
||||
*/
|
||||
export class ECSDynamicPool<T> {
|
||||
private pool: T[] = [];
|
||||
private metrics: IECSPoolMetrics;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param typeName 池类型名称
|
||||
* @param factory 对象工厂函数
|
||||
*/
|
||||
constructor(
|
||||
private typeName: string,
|
||||
private factory: () => T
|
||||
) {
|
||||
this.metrics = {
|
||||
createCount: 0,
|
||||
recycleCount: 0,
|
||||
hitCount: 0,
|
||||
missCount: 0,
|
||||
currentSize: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从池中获取对象
|
||||
* @returns 池中的对象或新创建的对象
|
||||
*/
|
||||
get(): T {
|
||||
if (this.pool.length > 0) {
|
||||
this.metrics.hitCount++;
|
||||
const item = this.pool.pop()!;
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
return item;
|
||||
}
|
||||
|
||||
this.metrics.missCount++;
|
||||
this.metrics.createCount++;
|
||||
|
||||
return this.factory();
|
||||
}
|
||||
|
||||
/**
|
||||
* 回收对象到池中
|
||||
* @param item 要回收的对象
|
||||
*/
|
||||
recycle(item: T): void {
|
||||
this.pool.push(item);
|
||||
this.metrics.recycleCount++;
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预热池,提前创建指定数量的对象
|
||||
* @param count 要预热的对象数量
|
||||
*/
|
||||
preWarm(count: number): void {
|
||||
while (this.pool.length < count) {
|
||||
this.pool.push(this.factory());
|
||||
this.metrics.createCount++;
|
||||
}
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池的统计信息
|
||||
* @returns 只读的统计指标对象
|
||||
*/
|
||||
getMetrics(): Readonly<IECSPoolMetrics> {
|
||||
return { ...this.metrics };
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空池中的所有对象
|
||||
*/
|
||||
clear(): void {
|
||||
this.pool.length = 0;
|
||||
this.metrics.currentSize = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动缩减池大小到指定容量
|
||||
* @param targetSize 目标池大小
|
||||
* @returns 移除的对象数量
|
||||
*/
|
||||
shrinkTo(targetSize: number): number {
|
||||
let removed = 0;
|
||||
|
||||
while (this.pool.length > targetSize) {
|
||||
this.pool.pop();
|
||||
removed++;
|
||||
}
|
||||
|
||||
if (removed > 0) {
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
9
assets/libs/ecs/pool/ECSDynamicPool.ts.meta
Normal file
9
assets/libs/ecs/pool/ECSDynamicPool.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "e18efb47-61df-4094-8457-d330b7847318",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
127
assets/libs/ecs/pool/ECSPoolManager.ts
Normal file
127
assets/libs/ecs/pool/ECSPoolManager.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 池管理器
|
||||
*/
|
||||
|
||||
import { ECSDynamicPool } from './ECSDynamicPool';
|
||||
import type { IECSPoolMetrics } from './IECSPoolMetrics';
|
||||
|
||||
/**
|
||||
* 池管理器 - 统一管理所有对象池
|
||||
*/
|
||||
export class ECSPoolManager {
|
||||
/** 所有对象池的映射 */
|
||||
private pools: Map<string, ECSDynamicPool<unknown>> = new Map();
|
||||
|
||||
/**
|
||||
* 获取或创建池
|
||||
* @param typeName 池类型名称
|
||||
* @param factory 对象工厂函数
|
||||
* @returns 动态对象池实例
|
||||
*/
|
||||
getPool<T>(typeName: string, factory: () => T): ECSDynamicPool<T> {
|
||||
if (!this.pools.has(typeName)) {
|
||||
const pool = new ECSDynamicPool<unknown>(typeName, factory);
|
||||
this.pools.set(typeName, pool);
|
||||
}
|
||||
|
||||
return this.pools.get(typeName)! as ECSDynamicPool<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有池
|
||||
*/
|
||||
clearAll(): void {
|
||||
this.pools.forEach(pool => pool.clear());
|
||||
this.pools.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景切换时预热池
|
||||
* @param sceneType 场景类型标识
|
||||
* @param hints 类型名称到预热数量的映射
|
||||
*/
|
||||
onSceneChange(sceneType: string, hints: Map<string, number>): void {
|
||||
hints.forEach((count, typeName) => {
|
||||
const pool = this.pools.get(typeName);
|
||||
if (pool) {
|
||||
pool.preWarm(count);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有池的统计信息
|
||||
* @returns 类型名称到统计指标的映射
|
||||
*/
|
||||
getAllMetrics(): Map<string, IECSPoolMetrics> {
|
||||
const result = new Map<string, IECSPoolMetrics>();
|
||||
this.pools.forEach((pool, typeName) => {
|
||||
result.set(typeName, pool.getMetrics());
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动缩减所有池到指定百分比
|
||||
* @param percent 目标百分比(0-1之间)
|
||||
* @returns 总共移除的对象数量
|
||||
*/
|
||||
shrinkAllTo(percent: number): number {
|
||||
let totalRemoved = 0;
|
||||
this.pools.forEach(pool => {
|
||||
const metrics = pool.getMetrics();
|
||||
const targetSize = Math.floor(metrics.currentSize * percent);
|
||||
totalRemoved += pool.shrinkTo(targetSize);
|
||||
});
|
||||
return totalRemoved;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的池
|
||||
* @param typeName 池类型名称
|
||||
* @returns 池实例,如果不存在则返回undefined
|
||||
*/
|
||||
getPoolByName<T>(typeName: string): ECSDynamicPool<T> | undefined {
|
||||
return this.pools.get(typeName) as ECSDynamicPool<T> | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有池的名称
|
||||
* @returns 池名称数组
|
||||
*/
|
||||
getPoolNames(): string[] {
|
||||
return Array.from(this.pools.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空指定池中的对象
|
||||
* @param typeName 池类型名称
|
||||
*/
|
||||
clearPool(typeName: string): void {
|
||||
const pool = this.pools.get(typeName);
|
||||
if (pool) {
|
||||
pool.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动缩减指定池到目标大小
|
||||
* @param typeName 池类型名称
|
||||
* @param targetSize 目标池大小
|
||||
* @returns 移除的对象数量
|
||||
*/
|
||||
shrinkPool(typeName: string, targetSize: number): number {
|
||||
const pool = this.pools.get(typeName);
|
||||
return pool ? pool.shrinkTo(targetSize) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定池的统计信息
|
||||
* @param typeName 池类型名称
|
||||
* @returns 统计指标对象,如果池不存在则返回undefined
|
||||
*/
|
||||
getPoolMetrics(typeName: string): IECSPoolMetrics | undefined {
|
||||
const pool = this.pools.get(typeName);
|
||||
return pool ? pool.getMetrics() : undefined;
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,8 @@
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "88db857b-ee25-442e-aef3-64a3b1e474de",
|
||||
"uuid": "7ee37e6e-4033-4bb0-99c6-ba2cde97e5bf",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"simulateGlobals": []
|
||||
}
|
||||
"userData": {}
|
||||
}
|
||||
17
assets/libs/ecs/pool/IECSPoolMetrics.ts
Normal file
17
assets/libs/ecs/pool/IECSPoolMetrics.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 池统计指标
|
||||
*/
|
||||
|
||||
/** 池统计指标 */
|
||||
export interface IECSPoolMetrics {
|
||||
/** 创建次数 */
|
||||
createCount: number;
|
||||
/** 回收次数 */
|
||||
recycleCount: number;
|
||||
/** 命中次数(从池中获取) */
|
||||
hitCount: number;
|
||||
/** 未命中次数(需要新建) */
|
||||
missCount: number;
|
||||
/** 当前池大小 */
|
||||
currentSize: number;
|
||||
}
|
||||
9
assets/libs/ecs/pool/IECSPoolMetrics.ts.meta
Normal file
9
assets/libs/ecs/pool/IECSPoolMetrics.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "575be89a-5bfa-4f9e-80f1-88f43edaa1bc",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
25
assets/libs/ecs/pool/index.ts
Normal file
25
assets/libs/ecs/pool/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 对象池管理系统
|
||||
*
|
||||
* 用于管理 ECS 框架中的对象池,主要包括:
|
||||
* 1. ECS 实体对象 (ECSEntity) - 实体销毁后回收复用
|
||||
* 2. ECS 组件对象 (IComp) - 组件移除后回收复用
|
||||
* 3. 其他自定义对象 - 支持任意类型的对象池化
|
||||
*
|
||||
* 核心功能:
|
||||
* - 对象复用:减少频繁创建销毁带来的性能开销
|
||||
* - 统计监控:跟踪命中率、创建次数等指标
|
||||
* - 池管理:支持预热、缩减、清空等操作
|
||||
* - 场景优化:场景切换时自动预热相关对象池
|
||||
*/
|
||||
|
||||
export { IECSPoolMetrics } from './IECSPoolMetrics';
|
||||
export { ECSDynamicPool } from './ECSDynamicPool';
|
||||
export { ECSPoolManager } from './ECSPoolManager';
|
||||
|
||||
import { ECSPoolManager } from './ECSPoolManager';
|
||||
|
||||
/**
|
||||
* 全局池协调器实例
|
||||
*/
|
||||
export const ecsPoolCoordinator = new ECSPoolManager();
|
||||
9
assets/libs/ecs/pool/index.ts.meta
Normal file
9
assets/libs/ecs/pool/index.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "72c10359-0587-429d-957f-743e7f97673d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user