组件引用完整性,升级到es2021使用weakref

This commit is contained in:
YHH
2025-10-10 23:38:48 +08:00
parent 536871d09b
commit 7850fc610c
12 changed files with 1024 additions and 5 deletions

View File

@@ -45,6 +45,13 @@ export abstract class Component implements IComponent {
*/
public readonly id: number;
/**
* 所属实体ID
*
* 存储实体ID而非引用避免循环引用符合ECS数据导向设计。
*/
public entityId: number | null = null;
/**
* 创建组件实例
*

View File

@@ -0,0 +1,296 @@
import { Component } from '../Component';
import type { Entity } from '../Entity';
import type { IScene } from '../IScene';
import { createLogger } from '../../Utils/Logger';
const logger = createLogger('ReferenceTracker');
/**
* Entity引用记录
*/
export interface EntityRefRecord {
component: WeakRef<Component>;
propertyKey: string;
}
/**
* 全局EntityID到Scene的映射
*
* 使用全局Map记录每个Entity ID对应的Scene用于装饰器通过Component.entityId查找Scene。
*/
const globalEntitySceneMap = new Map<number, WeakRef<IScene>>();
/**
* 通过Entity ID获取Scene
*
* @param entityId Entity ID
* @returns Scene实例如果不存在则返回null
*/
export function getSceneByEntityId(entityId: number): IScene | null {
const sceneRef = globalEntitySceneMap.get(entityId);
return sceneRef?.deref() || null;
}
/**
* Entity引用追踪器
*
* 追踪Component中对Entity的引用当Entity被销毁时自动清理所有引用。
*
* @example
* ```typescript
* const tracker = new ReferenceTracker();
* tracker.registerReference(targetEntity, component, 'parent');
* targetEntity.destroy(); // 自动将 component.parent 设为 null
* ```
*/
export class ReferenceTracker {
/**
* Entity ID -> 引用该Entity的所有组件记录
*/
private _references: Map<number, Set<EntityRefRecord>> = new Map();
/**
* 当前Scene的引用
*/
private _scene: WeakRef<IScene> | null = null;
/**
* 注册Entity引用
*
* @param entity 被引用的Entity
* @param component 持有引用的Component
* @param propertyKey Component中存储引用的属性名
*/
public registerReference(entity: Entity, component: Component, propertyKey: string): void {
const entityId = entity.id;
let records = this._references.get(entityId);
if (!records) {
records = new Set();
this._references.set(entityId, records);
}
const existingRecord = this._findRecord(records, component, propertyKey);
if (existingRecord) {
return;
}
records.add({
component: new WeakRef(component),
propertyKey
});
}
/**
* 注销Entity引用
*
* @param entity 被引用的Entity
* @param component 持有引用的Component
* @param propertyKey Component中存储引用的属性名
*/
public unregisterReference(entity: Entity, component: Component, propertyKey: string): void {
const entityId = entity.id;
const records = this._references.get(entityId);
if (!records) {
return;
}
const record = this._findRecord(records, component, propertyKey);
if (record) {
records.delete(record);
if (records.size === 0) {
this._references.delete(entityId);
}
}
}
/**
* 清理所有指向指定Entity的引用
*
* 将所有引用该Entity的Component属性设为null。
*
* @param entityId 被销毁的Entity ID
*/
public clearReferencesTo(entityId: number): void {
const records = this._references.get(entityId);
if (!records) {
return;
}
const validRecords: EntityRefRecord[] = [];
for (const record of records) {
const component = record.component.deref();
if (component) {
validRecords.push(record);
}
}
for (const record of validRecords) {
const component = record.component.deref();
if (component) {
(component as any)[record.propertyKey] = null;
}
}
this._references.delete(entityId);
}
/**
* 清理Component的所有引用注册
*
* 当Component被移除时调用清理该Component注册的所有引用。
*
* @param component 被移除的Component
*/
public clearComponentReferences(component: Component): void {
for (const [entityId, records] of this._references.entries()) {
const toDelete: EntityRefRecord[] = [];
for (const record of records) {
const comp = record.component.deref();
if (!comp || comp === component) {
toDelete.push(record);
}
}
for (const record of toDelete) {
records.delete(record);
}
if (records.size === 0) {
this._references.delete(entityId);
}
}
}
/**
* 获取指向指定Entity的所有引用记录
*
* @param entityId Entity ID
* @returns 引用记录数组(仅包含有效引用)
*/
public getReferencesTo(entityId: number): EntityRefRecord[] {
const records = this._references.get(entityId);
if (!records) {
return [];
}
const validRecords: EntityRefRecord[] = [];
for (const record of records) {
const component = record.component.deref();
if (component) {
validRecords.push(record);
}
}
return validRecords;
}
/**
* 清理所有失效的WeakRef引用
*
* 遍历所有记录移除已被GC回收的Component引用。
*/
public cleanup(): void {
const entitiesToDelete: number[] = [];
for (const [entityId, records] of this._references.entries()) {
const toDelete: EntityRefRecord[] = [];
for (const record of records) {
if (!record.component.deref()) {
toDelete.push(record);
}
}
for (const record of toDelete) {
records.delete(record);
}
if (records.size === 0) {
entitiesToDelete.push(entityId);
}
}
for (const entityId of entitiesToDelete) {
this._references.delete(entityId);
}
}
/**
* 设置Scene引用
*
* @param scene Scene实例
*/
public setScene(scene: IScene): void {
this._scene = new WeakRef(scene);
}
/**
* 注册Entity到Scene的映射
*
* @param entityId Entity ID
* @param scene Scene实例
*/
public registerEntityScene(entityId: number, scene: IScene): void {
globalEntitySceneMap.set(entityId, new WeakRef(scene));
}
/**
* 注销Entity到Scene的映射
*
* @param entityId Entity ID
*/
public unregisterEntityScene(entityId: number): void {
globalEntitySceneMap.delete(entityId);
}
/**
* 获取调试信息
*/
public getDebugInfo(): object {
const info: Record<string, any> = {};
for (const [entityId, records] of this._references.entries()) {
const validRecords = [];
for (const record of records) {
const component = record.component.deref();
if (component) {
validRecords.push({
componentId: component.id,
propertyKey: record.propertyKey
});
}
}
if (validRecords.length > 0) {
info[`entity_${entityId}`] = validRecords;
}
}
return info;
}
/**
* 查找指定的引用记录
*/
private _findRecord(
records: Set<EntityRefRecord>,
component: Component,
propertyKey: string
): EntityRefRecord | undefined {
for (const record of records) {
const comp = record.component.deref();
if (comp === component && record.propertyKey === propertyKey) {
return record;
}
}
return undefined;
}
}

View File

@@ -0,0 +1,147 @@
import type { Entity } from '../Entity';
import type { Component } from '../Component';
import { getSceneByEntityId } from '../Core/ReferenceTracker';
import { createLogger } from '../../Utils/Logger';
const logger = createLogger('EntityRefDecorator');
/**
* EntityRef元数据的Symbol键
*/
export const ENTITY_REF_METADATA = Symbol('EntityRefMetadata');
/**
* EntityRef值存储的Symbol键
*/
const ENTITY_REF_VALUES = Symbol('EntityRefValues');
/**
* EntityRef元数据
*/
export interface EntityRefMetadata {
properties: Set<string>;
}
/**
* 获取或创建组件的EntityRef值存储Map
*/
function getValueMap(component: Component): Map<string, Entity | null> {
let map = (component as any)[ENTITY_REF_VALUES];
if (!map) {
map = new Map<string, Entity | null>();
(component as any)[ENTITY_REF_VALUES] = map;
}
return map;
}
/**
* Entity引用装饰器
*
* 标记Component属性为Entity引用自动追踪引用关系。
* 当被引用的Entity销毁时该属性会自动设为null。
*
* @example
* ```typescript
* class ParentComponent extends Component {
* @EntityRef() parent: Entity | null = null;
* }
*
* const parent = scene.createEntity('Parent');
* const child = scene.createEntity('Child');
* const comp = child.addComponent(new ParentComponent());
*
* comp.parent = parent;
* parent.destroy(); // comp.parent 自动变为 null
* ```
*/
export function EntityRef(): PropertyDecorator {
return function (target: any, propertyKey: string | symbol) {
const constructor = target.constructor;
let metadata: EntityRefMetadata = constructor[ENTITY_REF_METADATA];
if (!metadata) {
metadata = {
properties: new Set()
};
constructor[ENTITY_REF_METADATA] = metadata;
}
const propKeyString = typeof propertyKey === 'symbol' ? propertyKey.toString() : propertyKey;
metadata.properties.add(propKeyString);
Object.defineProperty(target, propertyKey, {
get: function (this: Component) {
const valueMap = getValueMap(this);
return valueMap.get(propKeyString) || null;
},
set: function (this: Component, newValue: Entity | null) {
const valueMap = getValueMap(this);
const oldValue = valueMap.get(propKeyString) || null;
if (oldValue === newValue) {
return;
}
const scene = this.entityId !== null ? getSceneByEntityId(this.entityId) : null;
if (!scene || !scene.referenceTracker) {
valueMap.set(propKeyString, newValue);
return;
}
const tracker = scene.referenceTracker;
if (oldValue) {
tracker.unregisterReference(oldValue, this, propKeyString);
}
if (newValue) {
if (newValue.scene !== scene) {
logger.error(`Cannot reference Entity from different Scene. Entity: ${newValue.name}, Scene: ${newValue.scene?.name || 'null'}`);
return;
}
if (newValue.isDestroyed) {
logger.warn(`Cannot reference destroyed Entity: ${newValue.name}`);
valueMap.set(propKeyString, null);
return;
}
tracker.registerReference(newValue, this, propKeyString);
}
valueMap.set(propKeyString, newValue);
},
enumerable: true,
configurable: true
});
};
}
/**
* 获取Component的EntityRef元数据
*
* @param component Component实例或Component类
* @returns EntityRef元数据如果不存在则返回null
*/
export function getEntityRefMetadata(component: any): EntityRefMetadata | null {
if (!component) {
return null;
}
const constructor = typeof component === 'function'
? component
: component.constructor;
return constructor[ENTITY_REF_METADATA] || null;
}
/**
* 检查Component是否有EntityRef属性
*
* @param component Component实例或Component类
* @returns 如果有EntityRef属性返回true
*/
export function hasEntityRef(component: any): boolean {
return getEntityRefMetadata(component) !== null;
}

View File

@@ -10,4 +10,13 @@ export {
SYSTEM_TYPE_NAME
} from './TypeDecorators';
export type { SystemMetadata } from './TypeDecorators';
export type { SystemMetadata } from './TypeDecorators';
export {
EntityRef,
getEntityRefMetadata,
hasEntityRef,
ENTITY_REF_METADATA
} from './EntityRefDecorator';
export type { EntityRefMetadata } from './EntityRefDecorator';

View File

@@ -409,6 +409,10 @@ export class Entity {
this.scene.componentStorageManager.addComponent(this.id, component);
component.entityId = this.id;
if (this.scene.referenceTracker) {
this.scene.referenceTracker.registerEntityScene(this.id, this.scene);
}
component.onAddedToEntity();
if (Entity.eventBus) {
@@ -538,10 +542,16 @@ export class Entity {
this.scene.componentStorageManager.removeComponent(this.id, componentType);
}
if (this.scene?.referenceTracker) {
this.scene.referenceTracker.clearComponentReferences(component);
}
if (component.onRemovedFromEntity) {
component.onRemovedFromEntity();
}
component.entityId = null;
if (Entity.eventBus) {
Entity.eventBus.emitComponentRemoved({
timestamp: Date.now(),
@@ -867,6 +877,11 @@ export class Entity {
this._isDestroyed = true;
if (this.scene && this.scene.referenceTracker) {
this.scene.referenceTracker.clearReferencesTo(this.id);
this.scene.referenceTracker.unregisterEntityScene(this.id);
}
const childrenToDestroy = [...this._children];
for (const child of childrenToDestroy) {
child.destroy();

View File

@@ -5,6 +5,7 @@ import { EntitySystem } from './Systems/EntitySystem';
import { ComponentStorageManager } from './Core/ComponentStorage';
import { QuerySystem } from './Core/QuerySystem';
import { TypeSafeEventSystem } from './Core/EventSystem';
import type { ReferenceTracker } from './Core/ReferenceTracker';
/**
* 场景接口定义
@@ -61,6 +62,11 @@ export interface IScene {
*/
readonly eventSystem: TypeSafeEventSystem;
/**
* 引用追踪器
*/
readonly referenceTracker: ReferenceTracker;
/**
* 获取系统列表
*/

View File

@@ -6,6 +6,7 @@ import { ComponentStorageManager, ComponentRegistry } from './Core/ComponentStor
import { QuerySystem } from './Core/QuerySystem';
import { TypeSafeEventSystem } from './Core/EventSystem';
import { EventBus } from './Core/EventBus';
import { ReferenceTracker } from './Core/ReferenceTracker';
import { IScene, ISceneConfig } from './IScene';
import { getComponentInstanceTypeName, getSystemInstanceTypeName, getSystemMetadata } from "./Decorators";
import { TypedQueryBuilder } from './Core/Query/TypedQuery';
@@ -76,6 +77,13 @@ export class Scene implements IScene {
*/
public readonly eventSystem: TypeSafeEventSystem;
/**
* 引用追踪器
*
* 追踪Component中对Entity的引用当Entity销毁时自动清理引用。
*/
public readonly referenceTracker: ReferenceTracker;
/**
* 服务容器
*
@@ -171,6 +179,7 @@ export class Scene implements IScene {
this.componentStorageManager = new ComponentStorageManager();
this.querySystem = new QuerySystem();
this.eventSystem = new TypeSafeEventSystem();
this.referenceTracker = new ReferenceTracker();
this._services = new ServiceContainer();
this.logger = createLogger('Scene');

View File

@@ -13,4 +13,6 @@ export * from './Core/Events';
export * from './Core/Query';
export * from './Core/Storage';
export * from './Core/StorageDecorators';
export * from './Serialization';
export * from './Serialization';
export { ReferenceTracker, getSceneByEntityId } from './Core/ReferenceTracker';
export type { EntityRefRecord } from './Core/ReferenceTracker';