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() — 显示帮助信息
224 lines
8.4 KiB
TypeScript
224 lines
8.4 KiB
TypeScript
import type { ECSComblockSystem } from './ECSComblockSystem';
|
||
|
||
/**
|
||
* 系统调度 —— 声明式系统执行顺序。
|
||
*
|
||
* 通过 {@link executeBefore} / {@link executeAfter} / {@link inSet} 在系统类上声明约束,
|
||
* 由 {@link sortSystemsByDependencies} 在 RootSystem 构建执行流时做拓扑排序(Kahn 算法),
|
||
* 并在出现循环依赖时抛错。无任何声明时排序为空操作,保持原添加顺序与行为。
|
||
*
|
||
* 说明:依赖以"系统名"为节点标识,名取自系统类名(构造函数 name)。
|
||
* 装饰器入参可传类引用或字符串;传类引用更安全(避免手写错字),但都依赖类名,
|
||
* 若构建开启了类名混淆需改用字符串显式名并保持一致。
|
||
*/
|
||
|
||
/** 调度元数据存放在系统类原型上的键 */
|
||
const SCHEDULING_METADATA = Symbol('ecsSchedulingMetadata');
|
||
|
||
/** 集合节点前缀,用于区分虚拟集合节点与真实系统节点 */
|
||
const SET_PREFIX = 'set:';
|
||
|
||
/** 系统调度元数据 */
|
||
interface SchedulingMetadata {
|
||
/** 本系统须在这些系统之前执行 */
|
||
before: string[];
|
||
/** 本系统须在这些系统之后执行 */
|
||
after: string[];
|
||
/** 本系统所属的集合(虚拟分组) */
|
||
sets: string[];
|
||
}
|
||
|
||
/** 装饰器入参:系统类引用或系统名字符串 */
|
||
type SystemRef = string | { name: string };
|
||
|
||
/** 将类引用/字符串统一解析为名称 */
|
||
function refName(ref: SystemRef): string {
|
||
return typeof ref === 'string' ? ref : ref.name;
|
||
}
|
||
|
||
/** 读取或在原型上创建调度元数据(own property,避免被父类共享污染) */
|
||
function getOrCreateMetadata(prototype: object): SchedulingMetadata {
|
||
const holder = prototype as Record<symbol, SchedulingMetadata | undefined>;
|
||
if (!Object.prototype.hasOwnProperty.call(prototype, SCHEDULING_METADATA) || !holder[SCHEDULING_METADATA]) {
|
||
holder[SCHEDULING_METADATA] = { before: [], after: [], sets: [] };
|
||
}
|
||
return holder[SCHEDULING_METADATA]!;
|
||
}
|
||
|
||
/** 沿原型链读取调度元数据(实例或类皆可) */
|
||
function getMetadata(target: object): SchedulingMetadata | undefined {
|
||
let proto: object | null = typeof target === 'function' ? (target as { prototype: object }).prototype : Object.getPrototypeOf(target);
|
||
while (proto) {
|
||
const holder = proto as Record<symbol, SchedulingMetadata | undefined>;
|
||
if (Object.prototype.hasOwnProperty.call(proto, SCHEDULING_METADATA) && holder[SCHEDULING_METADATA]) {
|
||
return holder[SCHEDULING_METADATA];
|
||
}
|
||
proto = Object.getPrototypeOf(proto);
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
/**
|
||
* 类装饰器:声明本系统须在指定系统之前执行。
|
||
* @param systems 目标系统(类引用或类名)
|
||
*/
|
||
export function executeBefore(...systems: SystemRef[]): ClassDecorator {
|
||
return function (target) {
|
||
const meta = getOrCreateMetadata((target as unknown as { prototype: object }).prototype);
|
||
for (let i = 0; i < systems.length; i++) meta.before.push(refName(systems[i]));
|
||
return target;
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 类装饰器:声明本系统须在指定系统之后执行。
|
||
* @param systems 目标系统(类引用或类名),可用 'set:集合名' 依赖整个集合
|
||
*/
|
||
export function executeAfter(...systems: SystemRef[]): ClassDecorator {
|
||
return function (target) {
|
||
const meta = getOrCreateMetadata((target as unknown as { prototype: object }).prototype);
|
||
for (let i = 0; i < systems.length; i++) meta.after.push(refName(systems[i]));
|
||
return target;
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 类装饰器:把本系统加入一个或多个集合(虚拟分组),便于批量声明依赖。
|
||
* 其他系统可用 `executeAfter('set:集合名')` 依赖整个集合。
|
||
* @param sets 集合名列表
|
||
*/
|
||
export function inSet(...sets: string[]): ClassDecorator {
|
||
return function (target) {
|
||
const meta = getOrCreateMetadata((target as unknown as { prototype: object }).prototype);
|
||
meta.sets.push(...sets);
|
||
return target;
|
||
};
|
||
}
|
||
|
||
/** 循环依赖错误 */
|
||
export class CycleDependencyError extends Error {
|
||
/** 参与循环的节点名 */
|
||
readonly involvedNodes: string[];
|
||
constructor(involvedNodes: string[]) {
|
||
super(`[ECS] 系统调度检测到循环依赖:${involvedNodes.join(' -> ')}`);
|
||
this.name = 'CycleDependencyError';
|
||
this.involvedNodes = involvedNodes;
|
||
Object.setPrototypeOf(this, new.target.prototype);
|
||
}
|
||
}
|
||
|
||
/** 依赖图节点 */
|
||
interface GraphNode {
|
||
/** 入边数(依赖本节点的前置节点数) */
|
||
inEdges: Set<string>;
|
||
/** 出边(本节点指向的后继节点) */
|
||
outEdges: Set<string>;
|
||
/** 是否为虚拟集合节点(不产出到结果) */
|
||
virtual: boolean;
|
||
}
|
||
|
||
/**
|
||
* 依据系统类上的调度声明对执行流做拓扑排序。
|
||
*
|
||
* 无任何调度声明时直接返回原数组(零开销、行为不变)。存在循环依赖时抛 {@link CycleDependencyError}。
|
||
* 排序对无约束的系统保持其原始相对顺序(稳定)。
|
||
*
|
||
* @param systems RootSystem 摊平后的可执行系统实例数组
|
||
* @returns 重新排序后的系统实例数组
|
||
*/
|
||
export function sortSystemsByDependencies(systems: ECSComblockSystem[]): ECSComblockSystem[] {
|
||
const metas: (SchedulingMetadata | undefined)[] = new Array(systems.length);
|
||
let hasAny = false;
|
||
for (let i = 0; i < systems.length; i++) {
|
||
const m = getMetadata(systems[i]);
|
||
metas[i] = m;
|
||
if (m && (m.before.length || m.after.length || m.sets.length)) hasAny = true;
|
||
}
|
||
if (!hasAny) return systems;
|
||
|
||
const nameToInstances = new Map<string, ECSComblockSystem[]>();
|
||
const orderedNames: string[] = [];
|
||
for (let i = 0; i < systems.length; i++) {
|
||
const name = systems[i].constructor.name;
|
||
let list = nameToInstances.get(name);
|
||
if (!list) {
|
||
list = [];
|
||
nameToInstances.set(name, list);
|
||
orderedNames.push(name);
|
||
}
|
||
list.push(systems[i]);
|
||
}
|
||
|
||
const nodes = new Map<string, GraphNode>();
|
||
const getNode = (id: string, virtual: boolean): GraphNode => {
|
||
let n = nodes.get(id);
|
||
if (!n) {
|
||
n = { inEdges: new Set(), outEdges: new Set(), virtual };
|
||
nodes.set(id, n);
|
||
}
|
||
return n;
|
||
};
|
||
const addEdge = (from: string, to: string): void => {
|
||
if (from === to) return;
|
||
getNode(from, from.startsWith(SET_PREFIX)).outEdges.add(to);
|
||
getNode(to, to.startsWith(SET_PREFIX)).inEdges.add(from);
|
||
};
|
||
|
||
for (let i = 0; i < orderedNames.length; i++) getNode(orderedNames[i], false);
|
||
|
||
for (let i = 0; i < systems.length; i++) {
|
||
const meta = metas[i];
|
||
if (!meta) continue;
|
||
const name = systems[i].constructor.name;
|
||
for (const setName of meta.sets) addEdge(SET_PREFIX + setName, name);
|
||
for (const target of meta.before) addEdge(name, target);
|
||
for (const target of meta.after) addEdge(target, name);
|
||
}
|
||
|
||
const inDegree = new Map<string, number>();
|
||
nodes.forEach((node, id) => inDegree.set(id, node.inEdges.size));
|
||
|
||
const queue: string[] = [];
|
||
nodes.forEach((_node, id) => {
|
||
if (inDegree.get(id) === 0) queue.push(id);
|
||
});
|
||
|
||
const sortedNames: string[] = [];
|
||
let head = 0;
|
||
let processed = 0;
|
||
while (head < queue.length) {
|
||
const id = queue[head++];
|
||
processed++;
|
||
const node = nodes.get(id)!;
|
||
if (!node.virtual) sortedNames.push(id);
|
||
node.outEdges.forEach((outId) => {
|
||
const d = (inDegree.get(outId) ?? 0) - 1;
|
||
inDegree.set(outId, d);
|
||
if (d === 0) queue.push(outId);
|
||
});
|
||
}
|
||
|
||
if (processed < nodes.size) {
|
||
const cycle: string[] = [];
|
||
inDegree.forEach((d, id) => { if (d > 0) cycle.push(id); });
|
||
throw new CycleDependencyError(cycle);
|
||
}
|
||
|
||
const result: ECSComblockSystem[] = [];
|
||
for (let i = 0; i < sortedNames.length; i++) {
|
||
const list = nameToInstances.get(sortedNames[i]);
|
||
if (list) for (let j = 0; j < list.length; j++) result.push(list[j]);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/** 系统调度装饰器集合(`ecs.system` 即此对象) */
|
||
export const ecsSystemScheduler = {
|
||
/** 类装饰器:声明本系统须在指定系统之前执行 */
|
||
executeBefore,
|
||
/** 类装饰器:声明本系统须在指定系统之后执行(可用 `set:名` 依赖集合) */
|
||
executeAfter,
|
||
/** 类装饰器:把本系统加入虚拟集合,供 `executeAfter('set:名')` 批量依赖 */
|
||
inSet,
|
||
};
|