Feature/editor optimization (#251)
* refactor: 编辑器/运行时架构拆分与构建系统升级 * feat(core): 层级系统重构与UI变换矩阵修复 * refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题 * fix(physics): 修复跨包组件类引用问题 * feat: 统一运行时架构与浏览器运行支持 * feat(asset): 实现浏览器运行时资产加载系统 * fix: 修复文档、CodeQL安全问题和CI类型检查错误 * fix: 修复文档、CodeQL安全问题和CI类型检查错误 * fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误 * test: 补齐核心模块测试用例,修复CI构建配置 * fix: 修复测试用例中的类型错误和断言问题 * fix: 修复 turbo build:npm 任务的依赖顺序问题 * fix: 修复 CI 构建错误并优化构建性能
This commit is contained in:
549
packages/core/src/ECS/Systems/HierarchySystem.ts
Normal file
549
packages/core/src/ECS/Systems/HierarchySystem.ts
Normal file
@@ -0,0 +1,549 @@
|
||||
import { Entity } from '../Entity';
|
||||
import { EntitySystem } from './EntitySystem';
|
||||
import { Matcher } from '../Utils/Matcher';
|
||||
import { HierarchyComponent } from '../Components/HierarchyComponent';
|
||||
|
||||
/**
|
||||
* 层级关系系统 - 管理实体间的父子关系
|
||||
*
|
||||
* 提供层级操作的统一 API,维护层级缓存(depth、activeInHierarchy)。
|
||||
* 所有层级操作应通过此系统进行,而非直接修改 HierarchyComponent。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const hierarchySystem = scene.getSystem(HierarchySystem);
|
||||
*
|
||||
* // 设置父子关系
|
||||
* hierarchySystem.setParent(child, parent);
|
||||
*
|
||||
* // 查询层级
|
||||
* const parent = hierarchySystem.getParent(entity);
|
||||
* const children = hierarchySystem.getChildren(entity);
|
||||
* const depth = hierarchySystem.getDepth(entity);
|
||||
* ```
|
||||
*/
|
||||
export class HierarchySystem extends EntitySystem {
|
||||
private static readonly MAX_DEPTH = 32;
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(HierarchyComponent));
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统优先级,确保在其他系统之前更新层级缓存
|
||||
*/
|
||||
public override get updateOrder(): number {
|
||||
return -1000;
|
||||
}
|
||||
|
||||
protected override process(): void {
|
||||
// 更新所有脏缓存
|
||||
for (const entity of this.entities) {
|
||||
const hierarchy = entity.getComponent(HierarchyComponent);
|
||||
if (hierarchy?.bCacheDirty) {
|
||||
this.updateHierarchyCache(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实体的父级
|
||||
*
|
||||
* @param child - 子实体
|
||||
* @param parent - 父实体,null 表示移动到根级
|
||||
*/
|
||||
public setParent(child: Entity, parent: Entity | null): void {
|
||||
let childHierarchy = child.getComponent(HierarchyComponent);
|
||||
|
||||
// 如果子实体没有 HierarchyComponent,自动添加
|
||||
if (!childHierarchy) {
|
||||
childHierarchy = new HierarchyComponent();
|
||||
child.addComponent(childHierarchy);
|
||||
}
|
||||
|
||||
// 检查是否需要变更
|
||||
const currentParentId = childHierarchy.parentId;
|
||||
const newParentId = parent?.id ?? null;
|
||||
if (currentParentId === newParentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 防止循环引用
|
||||
if (parent && this.isAncestorOf(child, parent)) {
|
||||
throw new Error('Cannot set parent: would create circular reference');
|
||||
}
|
||||
|
||||
// 从旧父级移除
|
||||
if (currentParentId !== null) {
|
||||
const oldParent = this.scene?.findEntityById(currentParentId);
|
||||
if (oldParent) {
|
||||
const oldParentHierarchy = oldParent.getComponent(HierarchyComponent);
|
||||
if (oldParentHierarchy) {
|
||||
const idx = oldParentHierarchy.childIds.indexOf(child.id);
|
||||
if (idx !== -1) {
|
||||
oldParentHierarchy.childIds.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到新父级
|
||||
if (parent) {
|
||||
let parentHierarchy = parent.getComponent(HierarchyComponent);
|
||||
if (!parentHierarchy) {
|
||||
parentHierarchy = new HierarchyComponent();
|
||||
parent.addComponent(parentHierarchy);
|
||||
}
|
||||
childHierarchy.parentId = parent.id;
|
||||
parentHierarchy.childIds.push(child.id);
|
||||
} else {
|
||||
childHierarchy.parentId = null;
|
||||
}
|
||||
|
||||
// 标记缓存脏
|
||||
this.markCacheDirty(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定位置插入子实体
|
||||
*
|
||||
* @param parent - 父实体
|
||||
* @param child - 子实体
|
||||
* @param index - 插入位置索引,-1 表示追加到末尾
|
||||
*/
|
||||
public insertChildAt(parent: Entity, child: Entity, index: number): void {
|
||||
let childHierarchy = child.getComponent(HierarchyComponent);
|
||||
let parentHierarchy = parent.getComponent(HierarchyComponent);
|
||||
|
||||
// 自动添加 HierarchyComponent
|
||||
if (!childHierarchy) {
|
||||
childHierarchy = new HierarchyComponent();
|
||||
child.addComponent(childHierarchy);
|
||||
}
|
||||
if (!parentHierarchy) {
|
||||
parentHierarchy = new HierarchyComponent();
|
||||
parent.addComponent(parentHierarchy);
|
||||
}
|
||||
|
||||
// 防止循环引用
|
||||
if (this.isAncestorOf(child, parent)) {
|
||||
throw new Error('Cannot set parent: would create circular reference');
|
||||
}
|
||||
|
||||
// 从旧父级移除
|
||||
if (childHierarchy.parentId !== null && childHierarchy.parentId !== parent.id) {
|
||||
const oldParent = this.scene?.findEntityById(childHierarchy.parentId);
|
||||
if (oldParent) {
|
||||
const oldParentHierarchy = oldParent.getComponent(HierarchyComponent);
|
||||
if (oldParentHierarchy) {
|
||||
const idx = oldParentHierarchy.childIds.indexOf(child.id);
|
||||
if (idx !== -1) {
|
||||
oldParentHierarchy.childIds.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置新父级
|
||||
childHierarchy.parentId = parent.id;
|
||||
|
||||
// 从当前父级的子列表中移除(如果已存在)
|
||||
const existingIdx = parentHierarchy.childIds.indexOf(child.id);
|
||||
if (existingIdx !== -1) {
|
||||
parentHierarchy.childIds.splice(existingIdx, 1);
|
||||
}
|
||||
|
||||
// 插入到指定位置
|
||||
if (index < 0 || index >= parentHierarchy.childIds.length) {
|
||||
parentHierarchy.childIds.push(child.id);
|
||||
} else {
|
||||
parentHierarchy.childIds.splice(index, 0, child.id);
|
||||
}
|
||||
|
||||
// 标记缓存脏
|
||||
this.markCacheDirty(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除子实体(将其移动到根级)
|
||||
*/
|
||||
public removeChild(parent: Entity, child: Entity): boolean {
|
||||
const parentHierarchy = parent.getComponent(HierarchyComponent);
|
||||
const childHierarchy = child.getComponent(HierarchyComponent);
|
||||
|
||||
if (!parentHierarchy || !childHierarchy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (childHierarchy.parentId !== parent.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const idx = parentHierarchy.childIds.indexOf(child.id);
|
||||
if (idx !== -1) {
|
||||
parentHierarchy.childIds.splice(idx, 1);
|
||||
}
|
||||
|
||||
childHierarchy.parentId = null;
|
||||
this.markCacheDirty(child);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除所有子实体
|
||||
*/
|
||||
public removeAllChildren(parent: Entity): void {
|
||||
const parentHierarchy = parent.getComponent(HierarchyComponent);
|
||||
if (!parentHierarchy) return;
|
||||
|
||||
const childIds = [...parentHierarchy.childIds];
|
||||
for (const childId of childIds) {
|
||||
const child = this.scene?.findEntityById(childId);
|
||||
if (child) {
|
||||
this.removeChild(parent, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体的父级
|
||||
*/
|
||||
public getParent(entity: Entity): Entity | null {
|
||||
const hierarchy = entity.getComponent(HierarchyComponent);
|
||||
if (!hierarchy || hierarchy.parentId === null) {
|
||||
return null;
|
||||
}
|
||||
return this.scene?.findEntityById(hierarchy.parentId) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体的子级列表
|
||||
*/
|
||||
public getChildren(entity: Entity): Entity[] {
|
||||
const hierarchy = entity.getComponent(HierarchyComponent);
|
||||
if (!hierarchy) return [];
|
||||
|
||||
const children: Entity[] = [];
|
||||
for (const childId of hierarchy.childIds) {
|
||||
const child = this.scene?.findEntityById(childId);
|
||||
if (child) {
|
||||
children.push(child);
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子级数量
|
||||
*/
|
||||
public getChildCount(entity: Entity): number {
|
||||
const hierarchy = entity.getComponent(HierarchyComponent);
|
||||
return hierarchy?.childIds.length ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实体是否有子级
|
||||
*/
|
||||
public hasChildren(entity: Entity): boolean {
|
||||
return this.getChildCount(entity) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 ancestor 是否是 entity 的祖先
|
||||
*/
|
||||
public isAncestorOf(ancestor: Entity, entity: Entity): boolean {
|
||||
let current = this.getParent(entity);
|
||||
let depth = 0;
|
||||
|
||||
while (current && depth < HierarchySystem.MAX_DEPTH) {
|
||||
if (current.id === ancestor.id) {
|
||||
return true;
|
||||
}
|
||||
current = this.getParent(current);
|
||||
depth++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 descendant 是否是 entity 的后代
|
||||
*/
|
||||
public isDescendantOf(descendant: Entity, entity: Entity): boolean {
|
||||
return this.isAncestorOf(entity, descendant);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取根实体
|
||||
*/
|
||||
public getRoot(entity: Entity): Entity {
|
||||
let current = entity;
|
||||
let parent = this.getParent(current);
|
||||
let depth = 0;
|
||||
|
||||
while (parent && depth < HierarchySystem.MAX_DEPTH) {
|
||||
current = parent;
|
||||
parent = this.getParent(current);
|
||||
depth++;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体在层级中的深度
|
||||
*/
|
||||
public getDepth(entity: Entity): number {
|
||||
const hierarchy = entity.getComponent(HierarchyComponent);
|
||||
if (!hierarchy) return 0;
|
||||
|
||||
// 如果缓存有效,直接返回
|
||||
if (!hierarchy.bCacheDirty) {
|
||||
return hierarchy.depth;
|
||||
}
|
||||
|
||||
// 重新计算
|
||||
let depth = 0;
|
||||
let current = this.getParent(entity);
|
||||
while (current && depth < HierarchySystem.MAX_DEPTH) {
|
||||
depth++;
|
||||
current = this.getParent(current);
|
||||
}
|
||||
|
||||
hierarchy.depth = depth;
|
||||
return depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实体在层级中是否激活
|
||||
*/
|
||||
public isActiveInHierarchy(entity: Entity): boolean {
|
||||
if (!entity.active) return false;
|
||||
|
||||
const hierarchy = entity.getComponent(HierarchyComponent);
|
||||
if (!hierarchy) return entity.active;
|
||||
|
||||
// 如果缓存有效,直接返回
|
||||
if (!hierarchy.bCacheDirty) {
|
||||
return hierarchy.bActiveInHierarchy;
|
||||
}
|
||||
|
||||
// 重新计算
|
||||
const parent = this.getParent(entity);
|
||||
if (!parent) {
|
||||
hierarchy.bActiveInHierarchy = entity.active;
|
||||
} else {
|
||||
hierarchy.bActiveInHierarchy = entity.active && this.isActiveInHierarchy(parent);
|
||||
}
|
||||
|
||||
return hierarchy.bActiveInHierarchy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有根实体(没有父级的实体)
|
||||
*/
|
||||
public getRootEntities(): Entity[] {
|
||||
const roots: Entity[] = [];
|
||||
for (const entity of this.entities) {
|
||||
const hierarchy = entity.getComponent(HierarchyComponent);
|
||||
if (hierarchy && hierarchy.parentId === null) {
|
||||
roots.push(entity);
|
||||
}
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称查找子实体
|
||||
*
|
||||
* @param entity - 父实体
|
||||
* @param name - 子实体名称
|
||||
* @param bRecursive - 是否递归查找
|
||||
*/
|
||||
public findChild(entity: Entity, name: string, bRecursive: boolean = false): Entity | null {
|
||||
const children = this.getChildren(entity);
|
||||
|
||||
for (const child of children) {
|
||||
if (child.name === name) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
if (bRecursive) {
|
||||
for (const child of children) {
|
||||
const found = this.findChild(child, name, true);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标签查找子实体
|
||||
*
|
||||
* @param entity - 父实体
|
||||
* @param tag - 标签值
|
||||
* @param bRecursive - 是否递归查找
|
||||
*/
|
||||
public findChildrenByTag(entity: Entity, tag: number, bRecursive: boolean = false): Entity[] {
|
||||
const result: Entity[] = [];
|
||||
const children = this.getChildren(entity);
|
||||
|
||||
for (const child of children) {
|
||||
if ((child.tag & tag) !== 0) {
|
||||
result.push(child);
|
||||
}
|
||||
|
||||
if (bRecursive) {
|
||||
result.push(...this.findChildrenByTag(child, tag, true));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历所有子级
|
||||
*
|
||||
* @param entity - 父实体
|
||||
* @param callback - 回调函数
|
||||
* @param bRecursive - 是否递归遍历
|
||||
*/
|
||||
public forEachChild(
|
||||
entity: Entity,
|
||||
callback: (child: Entity) => void,
|
||||
bRecursive: boolean = false
|
||||
): void {
|
||||
const children = this.getChildren(entity);
|
||||
|
||||
for (const child of children) {
|
||||
callback(child);
|
||||
|
||||
if (bRecursive) {
|
||||
this.forEachChild(child, callback, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扁平化层级树(用于虚拟化渲染)
|
||||
*
|
||||
* @param expandedIds - 展开的实体 ID 集合
|
||||
* @returns 扁平化的节点列表
|
||||
*/
|
||||
public flattenHierarchy(expandedIds: Set<number>): Array<{
|
||||
entity: Entity;
|
||||
depth: number;
|
||||
bHasChildren: boolean;
|
||||
bIsExpanded: boolean;
|
||||
}> {
|
||||
const result: Array<{
|
||||
entity: Entity;
|
||||
depth: number;
|
||||
bHasChildren: boolean;
|
||||
bIsExpanded: boolean;
|
||||
}> = [];
|
||||
|
||||
const traverse = (entity: Entity, depth: number): void => {
|
||||
const bHasChildren = this.hasChildren(entity);
|
||||
const bIsExpanded = expandedIds.has(entity.id);
|
||||
|
||||
result.push({
|
||||
entity,
|
||||
depth,
|
||||
bHasChildren,
|
||||
bIsExpanded
|
||||
});
|
||||
|
||||
if (bHasChildren && bIsExpanded) {
|
||||
for (const child of this.getChildren(entity)) {
|
||||
traverse(child, depth + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const root of this.getRootEntities()) {
|
||||
traverse(root, 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记缓存为脏
|
||||
*/
|
||||
private markCacheDirty(entity: Entity): void {
|
||||
const hierarchy = entity.getComponent(HierarchyComponent);
|
||||
if (!hierarchy) return;
|
||||
|
||||
hierarchy.bCacheDirty = true;
|
||||
|
||||
// 递归标记所有子级
|
||||
for (const childId of hierarchy.childIds) {
|
||||
const child = this.scene?.findEntityById(childId);
|
||||
if (child) {
|
||||
this.markCacheDirty(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新层级缓存
|
||||
*/
|
||||
private updateHierarchyCache(entity: Entity): void {
|
||||
const hierarchy = entity.getComponent(HierarchyComponent);
|
||||
if (!hierarchy) return;
|
||||
|
||||
// 计算深度
|
||||
hierarchy.depth = this.getDepth(entity);
|
||||
|
||||
// 计算激活状态
|
||||
hierarchy.bActiveInHierarchy = this.isActiveInHierarchy(entity);
|
||||
|
||||
// 标记缓存有效
|
||||
hierarchy.bCacheDirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当实体被移除时清理层级关系
|
||||
*/
|
||||
protected onEntityRemoved(entity: Entity): void {
|
||||
const hierarchy = entity.getComponent(HierarchyComponent);
|
||||
if (!hierarchy) return;
|
||||
|
||||
// 从父级移除
|
||||
if (hierarchy.parentId !== null) {
|
||||
const parent = this.scene?.findEntityById(hierarchy.parentId);
|
||||
if (parent) {
|
||||
const parentHierarchy = parent.getComponent(HierarchyComponent);
|
||||
if (parentHierarchy) {
|
||||
const idx = parentHierarchy.childIds.indexOf(entity.id);
|
||||
if (idx !== -1) {
|
||||
parentHierarchy.childIds.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理子级:可选择销毁或移动到根级
|
||||
// 默认将子级移动到根级
|
||||
for (const childId of hierarchy.childIds) {
|
||||
const child = this.scene?.findEntityById(childId);
|
||||
if (child) {
|
||||
const childHierarchy = child.getComponent(HierarchyComponent);
|
||||
if (childHierarchy) {
|
||||
childHierarchy.parentId = null;
|
||||
this.markCacheDirty(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
// 清理资源
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ export { ProcessingSystem } from './ProcessingSystem';
|
||||
export { PassiveSystem } from './PassiveSystem';
|
||||
export { IntervalSystem } from './IntervalSystem';
|
||||
export { WorkerEntitySystem } from './WorkerEntitySystem';
|
||||
export { HierarchySystem } from './HierarchySystem';
|
||||
|
||||
// Worker系统相关类型导出
|
||||
export type {
|
||||
|
||||
Reference in New Issue
Block a user