Files
esengine/packages/core/src/ECS/Utils/ComponentSparseSet.ts
YHH ed8f6e283b feat: 纹理路径稳定 ID 与架构改进 (#305)
* feat(asset-system): 实现路径稳定 ID 生成器

使用 FNV-1a hash 算法为纹理生成稳定的运行时 ID:
- 新增 _pathIdCache 静态缓存,跨 Play/Stop 循环保持稳定
- 新增 getStableIdForPath() 方法,相同路径永远返回相同 ID
- 修改 loadTextureForComponent/loadTextureByGuid 使用稳定 ID
- clearTextureMappings() 不再清除 _pathIdCache

这解决了 Play/Stop 后纹理 ID 失效的根本问题。

* fix(runtime-core): 移除 Play/Stop 循环中的 clearTextureMappings 调用

使用路径稳定 ID 后,不再需要在快照保存/恢复时清除纹理缓存:
- saveSceneSnapshot() 移除 clearTextureMappings() 调用
- restoreSceneSnapshot() 移除 clearTextureMappings() 调用
- 组件保存的 textureId 在 Play/Stop 后仍然有效

* fix(editor-core): 修复场景切换时的资源泄漏

在 openScene() 加载新场景前先卸载旧场景资源:
- 调用 sceneResourceManager.unloadSceneResources() 释放旧资源
- 使用引用计数机制,仅卸载不再被引用的资源
- 路径稳定 ID 缓存不受影响,保持 ID 稳定性

* fix(runtime-core): 修复 PluginManager 组件注册类型错误

将 ComponentRegistry 类改为 GlobalComponentRegistry 实例:
- registerComponents() 期望 IComponentRegistry 接口实例
- GlobalComponentRegistry 是 ComponentRegistry 的全局实例

* refactor(core): 提取 IComponentRegistry 接口

将组件注册表抽象为接口,支持场景级组件注册:
- 新增 IComponentRegistry 接口定义
- Scene 持有独立的 componentRegistry 实例
- 支持从 GlobalComponentRegistry 克隆
- 各系统支持传入自定义注册表

* refactor(engine-core): 改进插件服务注册机制

- 更新 IComponentRegistry 类型引用
- 优化 PluginServiceRegistry 服务管理

* refactor(modules): 适配新的组件注册接口

更新各模块 RuntimeModule 使用 IComponentRegistry 接口:
- audio, behavior-tree, camera
- sprite, tilemap, world-streaming

* fix(physics-rapier2d): 修复物理插件组件注册

- PhysicsEditorPlugin 添加 runtimeModule 引用
- 适配 IComponentRegistry 接口
- 修复物理组件在场景加载时未注册的问题

* feat(editor-core): 添加 UserCodeService 就绪信号机制

- 新增 waitForReady()/signalReady() API
- 支持等待用户脚本编译完成
- 解决场景加载时组件未注册的时序问题

* fix(editor-app): 在编译完成后调用 signalReady()

确保用户脚本编译完成后发出就绪信号:
- 编译成功后调用 userCodeService.signalReady()
- 编译失败也要发出信号,避免阻塞场景加载

* feat(editor-core): 改进编辑器核心服务

- EntityStoreService 添加调试日志
- AssetRegistryService 优化资产注册
- PluginManager 改进插件管理
- IFileAPI 添加 getFileMtime 接口

* feat(engine): 改进 Rust 纹理管理器

- 支持任意 ID 的纹理加载(非递增)
- 添加纹理状态追踪 API
- 优化纹理缓存清理机制
- 更新 TypeScript 绑定

* feat(ui): 添加场景切换和文本闪烁组件

新增组件:
- SceneLoadTriggerComponent: 场景切换触发器
- TextBlinkComponent: 文本闪烁效果

新增系统:
- SceneLoadTriggerSystem: 处理场景切换逻辑
- TextBlinkSystem: 处理文本闪烁动画

其他改进:
- UIRuntimeModule 适配新组件注册接口
- UI 渲染系统优化

* feat(editor-app): 添加外部文件修改检测

- 新增 ExternalModificationDialog 组件
- TauriFileAPI 支持 getFileMtime
- 场景文件被外部修改时提示用户

* feat(editor-app): 添加渲染调试面板

- 新增 RenderDebugService 和调试面板 UI
- App/ContentBrowser 添加调试日志
- TitleBar/Viewport 优化
- DialogManager 改进

* refactor(editor-app): 编辑器服务和组件优化

- EngineService 改进引擎集成
- EditorEngineSync 同步优化
- AssetFileInspector 改进
- VectorFieldEditors 优化
- InstantiatePrefabCommand 改进

* feat(i18n): 更新国际化翻译

- 添加新功能相关翻译
- 更新中文、英文、西班牙文

* feat(tauri): 添加文件修改时间查询命令

- 新增 get_file_mtime 命令
- 支持检测文件外部修改

* refactor(particle): 粒子系统改进

- 适配新的组件注册接口
- ParticleSystem 优化
- 添加单元测试

* refactor(platform): 平台适配层优化

- BrowserRuntime 改进
- 新增 RuntimeSceneManager 服务
- 导出优化

* refactor(asset-system-editor): 资产元数据改进

- AssetMetaFile 优化
- 导出调整

* fix(asset-system): 移除未使用的 TextureLoader 导入

* fix(tests): 更新测试以使用 GlobalComponentRegistry 实例

修复多个测试文件以适配 ComponentRegistry 从静态类变为实例类的变更:
- ComponentStorage.test.ts: 使用 GlobalComponentRegistry.reset()
- EntitySerializer.test.ts: 使用 GlobalComponentRegistry 实例
- IncrementalSerialization.test.ts: 使用 GlobalComponentRegistry 实例
- SceneSerializer.test.ts: 使用 GlobalComponentRegistry 实例
- ComponentRegistry.extended.test.ts: 使用 GlobalComponentRegistry,同时注册到 scene.componentRegistry
- SystemTypes.test.ts: 在 Scene 创建前注册组件
- QuerySystem.test.ts: mockScene 添加 componentRegistry
2025-12-16 12:46:14 +08:00

415 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Entity } from '../Entity';
import { ComponentType, GlobalComponentRegistry } from '../Core/ComponentStorage';
import { BitMask64Utils, BitMask64Data } from './BigIntCompatibility';
import { SparseSet } from './SparseSet';
import { Pool } from '../../Utils/Pool/Pool';
import { IPoolable } from '../../Utils/Pool/IPoolable';
/**
* 可池化的实体集合
*
* 实现IPoolable接口支持对象池复用以减少内存分配开销。
*/
class PoolableEntitySet extends Set<Entity> implements IPoolable {
constructor(..._args: unknown[]) {
super();
}
reset(): void {
this.clear();
}
}
/**
* 组件稀疏集合实现
*
* 结合通用稀疏集合和组件位掩码
*
* 存储结构:
* - 稀疏集合存储实体
* - 位掩码数组存储组件信息
* - 组件类型映射表
*/
export class ComponentSparseSet {
/**
* 实体稀疏集合
*
* 存储所有拥有组件的实体提供O(1)的实体操作。
*/
private _entities: SparseSet<Entity>;
/**
* 组件位掩码数组
*
* 与实体稀疏集合的密集数组对应,存储每个实体的组件位掩码。
* 数组索引与稀疏集合的密集数组索引一一对应。
*/
private _componentMasks: BitMask64Data[] = [];
/**
* 组件类型到实体集合的映射
*
* 维护每个组件类型对应的实体集合,用于快速的单组件查询。
*/
private _componentToEntities = new Map<ComponentType, PoolableEntitySet>();
/**
* 实体集合对象池
*
* 使用core库的Pool系统来管理PoolableEntitySet对象的复用。
*/
private static _entitySetPool = Pool.getPool(PoolableEntitySet, 50, 512);
constructor() {
this._entities = new SparseSet<Entity>();
}
/**
* 添加实体到组件索引
*
* 分析实体的组件组成,生成位掩码,并更新所有相关索引。
*
* @param entity 要添加的实体
*/
public addEntity(entity: Entity): void {
// 如果实体已存在,先移除旧数据
if (this._entities.has(entity)) {
this.removeEntity(entity);
}
const componentMask = BitMask64Utils.clone(BitMask64Utils.ZERO);
const entityComponents = new Set<ComponentType>();
// 分析实体组件并构建位掩码
for (const component of entity.components) {
const componentType = component.constructor as ComponentType;
entityComponents.add(componentType);
// 获取组件位掩码并合并
const bitMask = GlobalComponentRegistry.getBitMask(componentType);
BitMask64Utils.orInPlace(componentMask, bitMask);
}
// 添加实体到稀疏集合
this._entities.add(entity);
const entityIndex = this._entities.getIndex(entity)!;
// 确保位掩码数组有足够空间
while (this._componentMasks.length <= entityIndex) {
this._componentMasks.push(BitMask64Utils.clone(BitMask64Utils.ZERO));
}
this._componentMasks[entityIndex] = componentMask;
// 更新组件类型到实体的映射
this.updateComponentMappings(entity, entityComponents, true);
}
/**
* 从组件索引中移除实体
*
* 清理实体相关的所有索引数据,保持数据结构的紧凑性。
*
* @param entity 要移除的实体
*/
public removeEntity(entity: Entity): void {
const entityIndex = this._entities.getIndex(entity);
if (entityIndex === undefined) {
return; // 实体不存在
}
// 获取实体的组件类型集合
const entityComponents = this.getEntityComponentTypes(entity);
// 更新组件类型到实体的映射
this.updateComponentMappings(entity, entityComponents, false);
// 从稀疏集合中移除实体
this._entities.remove(entity);
// 维护位掩码数组的紧凑性
const lastIndex = this._componentMasks.length - 1;
if (entityIndex !== lastIndex) {
// 将最后一个位掩码移动到当前位置
this._componentMasks[entityIndex] = this._componentMasks[lastIndex]!;
}
this._componentMasks.pop();
}
/**
* 查询包含指定组件的所有实体
*
* @param componentType 组件类型
* @returns 包含该组件的实体集合
*/
public queryByComponent(componentType: ComponentType): Set<Entity> {
const entities = this._componentToEntities.get(componentType);
return entities ? new Set(entities) : new Set<Entity>();
}
/**
* 多组件查询AND操作
*
* 查找同时包含所有指定组件的实体。
*
* @param componentTypes 组件类型数组
* @returns 满足条件的实体集合
*/
public queryMultipleAnd(componentTypes: ComponentType[]): Set<Entity> {
if (componentTypes.length === 0) {
return new Set<Entity>();
}
if (componentTypes.length === 1) {
return this.queryByComponent(componentTypes[0]!);
}
// 构建目标位掩码
const targetMask = BitMask64Utils.clone(BitMask64Utils.ZERO);
for (const componentType of componentTypes) {
if (!GlobalComponentRegistry.isRegistered(componentType)) {
return new Set<Entity>(); // 未注册的组件类型,结果为空
}
const bitMask = GlobalComponentRegistry.getBitMask(componentType);
BitMask64Utils.orInPlace(targetMask, bitMask);
}
const result = ComponentSparseSet._entitySetPool.obtain();
// 遍历所有实体,检查位掩码匹配
this._entities.forEach((entity, index) => {
const entityMask = this._componentMasks[index]!;
if (BitMask64Utils.hasAll(entityMask, targetMask)) {
result.add(entity);
}
});
return result;
}
/**
* 多组件查询OR操作
*
* 查找包含任意一个指定组件的实体。
*
* @param componentTypes 组件类型数组
* @returns 满足条件的实体集合
*/
public queryMultipleOr(componentTypes: ComponentType[]): Set<Entity> {
if (componentTypes.length === 0) {
return new Set<Entity>();
}
if (componentTypes.length === 1) {
return this.queryByComponent(componentTypes[0]!);
}
// 构建目标位掩码
const targetMask = BitMask64Utils.clone(BitMask64Utils.ZERO);
for (const componentType of componentTypes) {
if (GlobalComponentRegistry.isRegistered(componentType)) {
const bitMask = GlobalComponentRegistry.getBitMask(componentType);
BitMask64Utils.orInPlace(targetMask, bitMask);
}
}
if (BitMask64Utils.equals(targetMask, BitMask64Utils.ZERO)) {
return new Set<Entity>(); // 没有有效的组件类型
}
const result = ComponentSparseSet._entitySetPool.obtain();
// 遍历所有实体,检查位掩码匹配
this._entities.forEach((entity, index) => {
const entityMask = this._componentMasks[index]!;
if (BitMask64Utils.hasAny(entityMask, targetMask)) {
result.add(entity);
}
});
return result;
}
/**
* 检查实体是否包含指定组件
*
* @param entity 实体
* @param componentType 组件类型
* @returns 是否包含该组件
*/
public hasComponent(entity: Entity, componentType: ComponentType): boolean {
const entityIndex = this._entities.getIndex(entity);
if (entityIndex === undefined) {
return false;
}
if (!GlobalComponentRegistry.isRegistered(componentType)) {
return false;
}
const entityMask = this._componentMasks[entityIndex]!;
const componentMask = GlobalComponentRegistry.getBitMask(componentType);
return BitMask64Utils.hasAny(entityMask, componentMask);
}
/**
* 获取实体的组件位掩码
*
* @param entity 实体
* @returns 组件位掩码如果实体不存在则返回undefined
*/
public getEntityMask(entity: Entity): BitMask64Data | undefined {
const entityIndex = this._entities.getIndex(entity);
if (entityIndex === undefined) {
return undefined;
}
return this._componentMasks[entityIndex];
}
/**
* 获取所有实体
*
* @returns 所有实体的数组
*/
public getAllEntities(): Entity[] {
return this._entities.toArray();
}
/**
* 获取实体数量
*/
public get size(): number {
return this._entities.size;
}
/**
* 检查是否为空
*/
public get isEmpty(): boolean {
return this._entities.isEmpty;
}
/**
* 遍历所有实体
*
* @param callback 遍历回调函数
*/
public forEach(callback: (entity: Entity, mask: BitMask64Data, index: number) => void): void {
this._entities.forEach((entity, index) => {
callback(entity, this._componentMasks[index]!, index);
});
}
/**
* 清空所有数据
*/
public clear(): void {
this._entities.clear();
this._componentMasks.length = 0;
// 清理时将所有持有的实体集合返回到池中
for (const entitySet of this._componentToEntities.values()) {
ComponentSparseSet._entitySetPool.release(entitySet);
}
this._componentToEntities.clear();
}
/**
* 获取内存使用统计
*/
public getMemoryStats(): {
entitiesMemory: number;
masksMemory: number;
mappingsMemory: number;
totalMemory: number;
} {
const entitiesStats = this._entities.getMemoryStats();
const masksMemory = this._componentMasks.length * 16; // 估计每个BigInt 16字节
let mappingsMemory = this._componentToEntities.size * 16; // Map条目开销
for (const entitySet of this._componentToEntities.values()) {
mappingsMemory += entitySet.size * 8; // 每个实体引用8字节
}
return {
entitiesMemory: entitiesStats.totalMemory,
masksMemory,
mappingsMemory,
totalMemory: entitiesStats.totalMemory + masksMemory + mappingsMemory
};
}
/**
* 验证数据结构完整性
*/
public validate(): boolean {
// 检查稀疏集合的有效性
if (!this._entities.validate()) {
return false;
}
// 检查位掩码数组长度一致性
if (this._componentMasks.length !== this._entities.size) {
return false;
}
// 检查组件映射的一致性
const allMappedEntities = new Set<Entity>();
for (const entitySet of this._componentToEntities.values()) {
for (const entity of entitySet) {
allMappedEntities.add(entity);
}
}
// 验证映射中的实体都在稀疏集合中
for (const entity of allMappedEntities) {
if (!this._entities.has(entity)) {
return false;
}
}
return true;
}
/**
* 获取实体的组件类型集合
*/
private getEntityComponentTypes(entity: Entity): Set<ComponentType> {
const componentTypes = new Set<ComponentType>();
for (const component of entity.components) {
componentTypes.add(component.constructor as ComponentType);
}
return componentTypes;
}
/**
* 更新组件类型到实体的映射
*/
private updateComponentMappings(
entity: Entity,
componentTypes: Set<ComponentType>,
add: boolean
): void {
for (const componentType of componentTypes) {
let entities = this._componentToEntities.get(componentType);
if (add) {
if (!entities) {
entities = ComponentSparseSet._entitySetPool.obtain();
this._componentToEntities.set(componentType, entities);
}
entities.add(entity);
} else {
if (entities) {
entities.delete(entity);
if (entities.size === 0) {
this._componentToEntities.delete(componentType);
ComponentSparseSet._entitySetPool.release(entities);
}
}
}
}
}
}