mirror of
https://gitee.com/dgflash/oops-plugin-framework.git
synced 2026-06-23 19:22:47 +08:00
ecs 命名空间:统一导出所有 API,业务只需 import { ecs } from './ECS'
核心类型别名:ecs.Entity / ecs.Comp / ecs.RootSystem / ecs.ComblockSystem
接口再导出:ecs.IComp / ecs.IMatcher / ecs.IEntityEnterSystem / ecs.IEntityRemoveSystem / ecs.ISystemFirstUpdate / ecs.ISystemUpdate
实体工厂:ecs.getEntity(ctor, world?) — 创建或从对象池获取实体
动态查询:ecs.query(matcher, world?) — 按匹配器查询实体
过滤器工厂:ecs.allOf() / ecs.anyOf() / ecs.onlyOf() / ecs.excludeOf() — 组合匹配规则
单例组件:ecs.getSingleton() / ecs.addSingleton() — 全局唯一组件访问
子模块门面:ecs.world / ecs.pool / ecs.system / ecs.storage / ecs.network / ecs.serialize / ecs.entityRef
2. 驱动器 — ECSDriver.ts
封装默认世界的 init / execute / destroy 生命周期
add(system) — 向默认世界添加业务系统
init() — 初始化 ECS(并入 @ecs.register 注册的系统、拓扑排序、init)
execute(dt) — 驱动默认世界一帧
destroy() — 清理所有子系统
3. 实体 — ECSEntity.ts
组件容器:位掩码 mask + 按 tid 索引的密集数组,get/has 为 O(1)
双重重载 add():
add(组件类) — 从对象池取实例或恢复软移除缓存
add(组件实例) — 挂载外部实例(如 cc.Component),设 canRecycle=false
remove(ctor, isRecycle?):
isRecycle=true(默认):reset + 回池/释放 SoA 槽位
isRecycle=false:软移除,数据暂存 compTid2Obj,下次 add 同 tid 恢复
父子层级:addChild / removeChild,带循环引用检测
destroy():断开父子 → 移除全部组件 → 清理软移除缓存 → 回收实体 + 释放 eid
forEachComponent(cb):遍历当前挂载的所有组件
4. 组件 — ECSComp.ts
抽象基类,子类须实现 reset() 方法
canRecycle — 是否可回收(外部创建的组件设 false)
ent — 拥有该组件的实体引用
变更检测:markDirty() / isChangedSince(sinceEpoch) / lastWriteEpoch — 基于 epoch 的帧级脏标记
5. 位掩码 — ECSMask.ts
基于 Uint32Array 的位运算,支持 set / has / delete / and / or / bitCount
对象池复用,clearPool() 清理
6. 注册系统 — ECSRegister.ts + ECSTypeRegistry.ts
@ecs.register('Name') 类装饰器:自动识别注册类型
组件:分配 tid,写入 compCtors 表
实体:记录 ctor → name 映射
系统:注册到全局表或指定世界
ECSTypeRegistry:全局类型注册表(跨世界共享),记录组件/实体/系统元数据
7. 查询 — ECSMatcher.ts + ECSGroup.ts
四种规则:AllOf / AnyOf / OnlyOf / ExcludeOf,规则间为"与"关系
flyweight 缓存:相同组合共享同一 Matcher 实例
小规则优化:tid 数 ≤ 4 时直接 mask.has,否则分配 ECSMask
ECSGroup:SparseSet 实现,O(1) 增删(swap-pop),稳定快照遍历
进入/离开追踪:watchEntityEnterAndRemove 供系统帧内查询变化
8. 系统 — ECSComblockSystem.ts + ECSRootSystem.ts
ComblockSystem:业务系统基类
生命周期:init → entityEnter → firstUpdate → update → entityRemove → onDestroy
filter() — 声明实体匹配规则
interval — 执行间隔(0=每帧,>0=固定间隔)
被动系统:isPassiveSystem() 返回 true 时不参与每帧 update
构造时探测子类钩子,选定 execute 变体(零开销分支)
RootSystem:根系统,一世界一个
init() — 并入 @ecs.register 系统 + 拓扑排序 + 绑定世界 + init
execute(dt) — 切换当前世界 → 递增 epoch → tick 各系统 → flush 命令缓冲
9. 系统调度 — SystemScheduler.ts
声明式执行顺序:@ecs.system.executeBefore / @ecs.system.executeAfter / @ecs.system.inSet
拓扑排序(Kahn 算法):无约束系统保持原始顺序,循环依赖抛 CycleDependencyError
集合机制:inSet('名') 把系统加入虚拟分组,其他系统用 executeAfter('set:名') 批量依赖
10. 世界 — ECSWorld.ts + ECSWorldManager.ts
ECSWorld:运行期数据容器
entities — 活动实体表(eid → 实体)
groups — 响应式查询分组
singletons — 单例组件表
refs — @entityRef 引用追踪
commands — 延迟结构变更命令队列
epoch — 世代号(每帧递增,变更检测用)
getEntity(ctor) — 创建/从池获取实体
assignEid(entity, eid) — 反序列化时保持 eid 一致
ECSWorldManager(ecs.world):多世界管理
get(name?) / default() / current — 获取/切换世界
use(world) — 切换当前世界,返回切换前的世界
inWorld(world, fn) — 在指定世界中执行,自动还原
createSystems(world, ...ctors) — 批量装配系统并 init
defer(fn) / flushCommands() — 延迟结构变更
11. 命令缓冲 — ECSCommandBuffer.ts
延迟结构变更队列,帧末由 RootSystem.execute 统一 flush
push(fn) 入队,flush() 先快照再执行(避免本帧新入队命令在本帧执行)
12. 对象池 — ECSPoolManager.ts + ECSDynamicPool.ts
ecs.pool:统一管理实体/组件动态对象池
getPool(name, factory) — 获取或创建池
clearAll() / clearPools() — 清空池缓存(不触碰存活数据)
getAllMetrics() — 获取各池统计信息(命中/未命中/缓存/创建数)
13. 实体引用 — ECSEntityRef.ts + ECSReferenceTracker.ts
@ecs.entityRef() 属性装饰器:标记组件属性为实体引用
目标实体销毁时自动置 null,避免悬空引用
组件回收时 clearComponentEntityRefs 清理所有引用
14. SoA 列存储 — StorageSoA.ts + StorageDecorators.ts
@ecs.storage.enableSoA 类装饰器:为组件启用 SoA 列存储(默认 AoS,opt-in)
字段装饰器:float64 / float32 / int32 / uint32 / int16 / uint16 / int8 / uint8
数值字段按列存入 TypedArray,按 eid 分配槽位
acquire 返回 Proxy 视图:SoA 字段读写直接落到 TypedArray,非 SoA 字段走后备实例
槽位超出容量时 2 倍扩容,释放时入空闲栈
自注册机制:模块加载时注册到核心,删除 storage/ 目录后基础 ECS 仍可运行
15. 网络同步 — NetworkSync.ts + SyncDecorators.ts + SyncCodec.ts + ByteBuffer.ts
@ecs.network.sync(类型) 字段装饰器:标记组件字段参与网络同步
ecs.network.net 门面:
encodeWorld(op?) — 编码当前世界为紧凑二进制(Full 全量 / Delta 增量)
applyToWorld(bytes, onMissing?) — 将二进制同步数据应用到世界
track(entity, markAll?) — 为实体初始化变更追踪器
clearDirty() — 清除所有脏标记
SyncCodec:实体/组件级编解码器
ByteWriter / ByteReader:二进制读写缓冲(变长整数等)
ChangeTracker:字段级脏标记追踪
16. 序列化 — Serialization.ts + Incremental.ts
@ecs.serialize() 字段装饰器:标记组件需要持久化的字段
全量序列化:
ecs.serialize.serializeWorld(pretty?) — 序列化当前世界为 JSON
ecs.serialize.deserializeWorld(json) — 从 JSON 反序列化到当前世界
增量序列化:
ecs.serialize.snapshot() — 对当前世界拍快照(基线)
ecs.serialize.computeDelta(base) — 计算相对基线的增量(added / removed / changed)
ecs.serialize.applyDelta(delta) — 将增量应用到当前世界
保留实体层级(parentEid),两阶段重建(先建实体+组件,再重建父子)
17. 监控日志 — ECSMonitorLogger.ts
零入侵监控模块,通过控制台命令触发
注册全局命令:
ecsLog() — 打印所有监控表格
ecsWorldSummary() — 各世界实体/系统/组件缓存统计
ecsSummary() — 总体统计(所有世界合计)
ecsEntityPools() — 实体池明细(命中/未命中/缓存)
ecsCompPools() — 组件池明细(按 M/B/V/VC 分类排序)
ecsEntityCaches() — 实体软移除组件缓存明细
ecsHelp() — 显示帮助信息
401 lines
12 KiB
TypeScript
401 lines
12 KiB
TypeScript
import type { ecs } from '../ECS';
|
||
import { registry } from '../registry/ECSTypeRegistry';
|
||
import type { ECSEntity } from '../entity/ECSEntity';
|
||
import { ECSMask } from '../component/ECSMask';
|
||
import type { CompCtor, CompType } from '../registry/ECSTypes';
|
||
|
||
/** 匹配器自增 id(用于 groups 缓存 key) */
|
||
let macherId = 1;
|
||
|
||
/** 小规则阈值:tid 数不超过此值时不分配 ECSMask,直接走 mask.has */
|
||
const SMALL_RULE_MAX = 4;
|
||
|
||
/**
|
||
* 筛选规则间是“与”的关系
|
||
* 比如:ecs.Macher.allOf(...).excludeOf(...)表达的是allOf && excludeOf,即实体有“这些组件” 并且 “没有这些组件”
|
||
*/
|
||
export class ECSMatcher implements ecs.IMatcher {
|
||
/** 全局 flyweight 缓存(相同 key 共享 mid,各世界 groups 仍按 mid 隔离) */
|
||
private static readonly _cache = new Map<string, ECSMatcher>();
|
||
|
||
/** 筛选规则列表(规则间为“与”关系) */
|
||
protected rules: BaseOf[] = [];
|
||
/** 匹配器关注的组件索引(懒加载) */
|
||
protected _indices: number[] | null = null;
|
||
/** 实体匹配判定函数(按规则数量绑定不同实现) */
|
||
isMatch!: (entity: ECSEntity) => boolean;
|
||
/** 匹配器唯一 id */
|
||
mid = -1;
|
||
/** 已入全局缓存的匹配器不可原地追加规则(链式调用会 fork 出新实例) */
|
||
private _sealed = false;
|
||
|
||
/** 规则组合 key 缓存 */
|
||
private _key: string | null = null;
|
||
/** 规则组合的唯一字符串 key(用于调试与缓存) */
|
||
get key(): string {
|
||
if (!this._key) {
|
||
const keys: string[] = [];
|
||
const len = this.rules.length;
|
||
for (let i = 0; i < len; i++) {
|
||
keys.push(this.rules[i].getKey());
|
||
}
|
||
this._key = keys.join(' && ');
|
||
}
|
||
return this._key;
|
||
}
|
||
|
||
/** 分配匹配器 id */
|
||
constructor() {
|
||
this.mid = macherId++;
|
||
}
|
||
|
||
/**
|
||
* 获取或创建缓存匹配器(`ecs.allOf` 等工厂入口使用)。
|
||
* @param build 在未密封的临时匹配器上组装规则
|
||
*/
|
||
static compose(build: (m: ECSMatcher) => ECSMatcher): ECSMatcher {
|
||
const probe = new ECSMatcher();
|
||
const built = build(probe);
|
||
const k = built.key;
|
||
const hit = ECSMatcher._cache.get(k);
|
||
if (hit) {
|
||
built._disposeRules();
|
||
return hit;
|
||
}
|
||
built._sealed = true;
|
||
ECSMatcher._cache.set(k, built);
|
||
return built;
|
||
}
|
||
|
||
/** 清空全局匹配器缓存(热更重载等场景可选调用) */
|
||
static clearCache(): void {
|
||
ECSMatcher._cache.forEach((m) => m._disposeRules());
|
||
ECSMatcher._cache.clear();
|
||
}
|
||
|
||
/**
|
||
* 匹配器关注的组件索引。在创建Group时,Context根据组件id去给Group关联组件的添加和移除事件。
|
||
*/
|
||
get indices() {
|
||
if (this._indices === null) {
|
||
this._indices = [];
|
||
this.rules.forEach((rule) => {
|
||
Array.prototype.push.apply(this._indices, rule.indices);
|
||
});
|
||
}
|
||
return this._indices;
|
||
}
|
||
|
||
/**
|
||
* 组件间是或的关系,表示关注拥有任意一个这些组件的实体。
|
||
* @param args 组件索引
|
||
*/
|
||
anyOf(...args: CompType<ecs.IComp>[]): ECSMatcher {
|
||
if (this._sealed) {
|
||
return ECSMatcher.compose((m) => {
|
||
this._replayRules(m);
|
||
return m.anyOf(...args);
|
||
});
|
||
}
|
||
this.rules.push(new AnyOf(...args));
|
||
this._invalidateKey();
|
||
this.bindMatchMethod();
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 组件间是与的关系,表示关注拥有所有这些组件的实体。
|
||
* @param args 组件索引
|
||
*/
|
||
allOf(...args: CompType<ecs.IComp>[]): ECSMatcher {
|
||
if (this._sealed) {
|
||
return ECSMatcher.compose((m) => {
|
||
this._replayRules(m);
|
||
return m.allOf(...args);
|
||
});
|
||
}
|
||
this.rules.push(new AllOf(...args));
|
||
this._invalidateKey();
|
||
this.bindMatchMethod();
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 表示关注只拥有这些组件的实体(仅监听 args 对应 tid,不扫描全注册表)。
|
||
* @param args 组件索引
|
||
*/
|
||
onlyOf(...args: CompType<ecs.IComp>[]): ECSMatcher {
|
||
if (this._sealed) {
|
||
return ECSMatcher.compose((m) => {
|
||
this._replayRules(m);
|
||
return m.onlyOf(...args);
|
||
});
|
||
}
|
||
this.rules.push(new OnlyOf(...args));
|
||
this._invalidateKey();
|
||
this.bindMatchMethod();
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 不包含指定的任意一个组件
|
||
* @param args
|
||
*/
|
||
excludeOf(...args: CompType<ecs.IComp>[]): ECSMatcher {
|
||
if (this._sealed) {
|
||
return ECSMatcher.compose((m: ECSMatcher): ECSMatcher => {
|
||
this._replayRules(m);
|
||
return m.excludeOf(...args);
|
||
});
|
||
}
|
||
this.rules.push(new ExcludeOf(...args));
|
||
this._invalidateKey();
|
||
this.bindMatchMethod();
|
||
return this;
|
||
}
|
||
|
||
/** 内部:追加已构造规则(replay / fork 用) */
|
||
_pushRule(rule: BaseOf): void {
|
||
this.rules.push(rule);
|
||
this._indices = null;
|
||
this._invalidateKey();
|
||
}
|
||
|
||
/** 根据当前规则数量绑定 isMatch 实现,减少运行时分支。 */
|
||
private bindMatchMethod() {
|
||
if (this.rules.length === 1) {
|
||
this.isMatch = this.isMatch1;
|
||
}
|
||
else if (this.rules.length === 2) {
|
||
this.isMatch = this.isMatch2;
|
||
}
|
||
else {
|
||
this.isMatch = this.isMatchMore;
|
||
}
|
||
}
|
||
|
||
/** 单条规则时的匹配判定 */
|
||
private isMatch1(entity: ECSEntity): boolean {
|
||
return this.rules[0].isMatch(entity);
|
||
}
|
||
|
||
/** 两条规则时的匹配判定(与) */
|
||
private isMatch2(entity: ECSEntity): boolean {
|
||
return this.rules[0].isMatch(entity) && this.rules[1].isMatch(entity);
|
||
}
|
||
|
||
/** 多条规则时的匹配判定(与) */
|
||
private isMatchMore(entity: ECSEntity): boolean {
|
||
for (const rule of this.rules) {
|
||
if (!rule.isMatch(entity)) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/** 将已密封匹配器的规则复制到目标匹配器 */
|
||
private _replayRules(target: ECSMatcher): void {
|
||
for (let i = 0; i < this.rules.length; i++) {
|
||
target._pushRule(this.rules[i].fork());
|
||
}
|
||
target.bindMatchMethod();
|
||
}
|
||
|
||
private _invalidateKey(): void {
|
||
this._key = null;
|
||
this._indices = null;
|
||
}
|
||
|
||
/** 释放本匹配器全部规则 */
|
||
private _disposeRules(): void {
|
||
for (let i = 0; i < this.rules.length; i++) {
|
||
this.rules[i].destroy();
|
||
}
|
||
this.rules.length = 0;
|
||
this._invalidateKey();
|
||
}
|
||
|
||
/** 克隆当前匹配器(新 mid,规则 fork 副本) */
|
||
clone(): ECSMatcher {
|
||
const newMatcher = new ECSMatcher();
|
||
for (let i = 0; i < this.rules.length; i++) {
|
||
newMatcher._pushRule(this.rules[i].fork());
|
||
}
|
||
newMatcher.bindMatchMethod();
|
||
return newMatcher;
|
||
}
|
||
}
|
||
|
||
/** 从组件参数解析 tid 列表(去重、升序) */
|
||
function collectTids(...args: CompType<ecs.IComp>[]): number[] {
|
||
const uniqueIds = new Set<number>();
|
||
const len = args.length;
|
||
for (let i = 0; i < len; i++) {
|
||
let tid = -1;
|
||
if (typeof args[i] === 'number') {
|
||
tid = args[i] as number;
|
||
}
|
||
else {
|
||
tid = (args[i] as CompCtor<ecs.IComp>).tid;
|
||
}
|
||
if (tid === -1) {
|
||
throw Error('存在没有注册的组件!');
|
||
}
|
||
uniqueIds.add(tid);
|
||
}
|
||
return Array.from(uniqueIds).sort((a, b) => a - b);
|
||
}
|
||
|
||
/** 筛选规则基类 */
|
||
abstract class BaseOf {
|
||
/** 本规则涉及的组件 type id 列表 */
|
||
indices: number[] = [];
|
||
/** 大规则(tid 数 > SMALL_RULE_MAX)使用的位掩码;小规则为 null */
|
||
protected mask: ECSMask | null = null;
|
||
/** getKey/toString 用 key 缓存 */
|
||
private _keyCache: string | null = null;
|
||
|
||
/**
|
||
* @param args 组件构造器或 tid
|
||
*/
|
||
constructor(...args: CompType<ecs.IComp>[]) {
|
||
this.indices = collectTids(...args);
|
||
if (this.indices.length > SMALL_RULE_MAX) {
|
||
this.mask = new ECSMask(registry.maskWordCount);
|
||
for (let i = 0; i < this.indices.length; i++) {
|
||
this.mask.set(this.indices[i]);
|
||
}
|
||
}
|
||
}
|
||
|
||
/** 复制一条等价规则(链式 fork / clone 用) */
|
||
fork(): BaseOf {
|
||
const copy = Object.create(Object.getPrototypeOf(this)) as BaseOf;
|
||
copy.indices = this.indices.slice();
|
||
copy._keyCache = null;
|
||
if (this.mask) {
|
||
copy.mask = new ECSMask(registry.maskWordCount);
|
||
for (let i = 0; i < this.indices.length; i++) {
|
||
copy.mask.set(this.indices[i]);
|
||
}
|
||
}
|
||
else {
|
||
copy.mask = null;
|
||
}
|
||
return copy;
|
||
}
|
||
|
||
/** 清理资源,防止内存泄漏 */
|
||
destroy(): void {
|
||
if (this.mask) {
|
||
this.mask.destroy();
|
||
this.mask = null;
|
||
}
|
||
this.indices.length = 0;
|
||
this._keyCache = null;
|
||
}
|
||
|
||
/** 返回 indices 拼接字符串(带缓存) */
|
||
toString(): string {
|
||
if (!this._keyCache) {
|
||
this._keyCache = this.indices.join('-');
|
||
}
|
||
return this._keyCache;
|
||
}
|
||
|
||
/** 规则的唯一 key 前缀形式 */
|
||
abstract getKey(): string;
|
||
|
||
/** 判断实体是否满足本规则 */
|
||
abstract isMatch(entity: ECSEntity): boolean;
|
||
}
|
||
|
||
/**
|
||
* 用于描述包含任意一个这些组件的实体
|
||
*/
|
||
class AnyOf extends BaseOf {
|
||
/** 实体 mask 与本规则 mask 有任意交集即匹配 */
|
||
isMatch(entity: ECSEntity): boolean {
|
||
const em = (entity as ECSEntityInternal).getMask();
|
||
if (this.mask) {
|
||
return this.mask.or(em);
|
||
}
|
||
for (let i = 0; i < this.indices.length; i++) {
|
||
if (em.has(this.indices[i])) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/** @returns anyOf:indices 形式 key */
|
||
getKey(): string {
|
||
return 'anyOf:' + this.toString();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 用于描述包含了"这些"组件的实体,这个实体除了包含这些组件还可以包含其他组件
|
||
*/
|
||
class AllOf extends BaseOf {
|
||
/** 实体 mask 包含本规则 mask 的全部位即匹配 */
|
||
isMatch(entity: ECSEntity): boolean {
|
||
const em = (entity as ECSEntityInternal).getMask();
|
||
if (this.mask) {
|
||
return this.mask.and(em);
|
||
}
|
||
for (let i = 0; i < this.indices.length; i++) {
|
||
if (!em.has(this.indices[i])) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/** @returns allOf:indices 形式 key */
|
||
getKey(): string {
|
||
return 'allOf:' + this.toString();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 只拥有指定组件(无额外组件);仅监听 indices 对应 tid 的增删。
|
||
*/
|
||
class OnlyOf extends BaseOf {
|
||
isMatch(entity: ECSEntity): boolean {
|
||
const em = (entity as ECSEntityInternal).getMask();
|
||
for (let i = 0; i < this.indices.length; i++) {
|
||
if (!em.has(this.indices[i])) return false;
|
||
}
|
||
return em.bitCount() === this.indices.length;
|
||
}
|
||
|
||
getKey(): string {
|
||
return 'onlyOf:' + this.toString();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 不包含指定的任意一个组件
|
||
*/
|
||
class ExcludeOf extends BaseOf {
|
||
/** @returns excludeOf:indices 形式 key */
|
||
getKey(): string {
|
||
return 'excludeOf:' + this.toString();
|
||
}
|
||
|
||
/** 实体 mask 与本规则 mask 无交集即匹配 */
|
||
isMatch(entity: ECSEntity): boolean {
|
||
const em = (entity as ECSEntityInternal).getMask();
|
||
if (this.mask) {
|
||
return !this.mask.or(em);
|
||
}
|
||
for (let i = 0; i < this.indices.length; i++) {
|
||
if (em.has(this.indices[i])) return false;
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/** 内部接口,用于访问 ECSEntity 的私有成员 */
|
||
interface ECSEntityInternal {
|
||
getMask(): ECSMask;
|
||
}
|