重构 Component基类违反ECS纯粹性问题

This commit is contained in:
YHH
2025-09-30 22:26:44 +08:00
parent 51debede52
commit 952247def0
8 changed files with 81 additions and 383 deletions

View File

@@ -1,21 +1,30 @@
import type { IComponent } from '../Types'; import type { IComponent } from '../Types';
import type { Entity } from './Entity';
/** /**
* 游戏组件基类 * 游戏组件基类
* *
* ECS架构中的组件Component,用于实现具体的游戏功能 * ECS架构中的组件Component应该是纯数据容器
* 组件包含数据和行为,可以被添加到实体上以扩展实体的功能 * 所有游戏逻辑应该在 EntitySystem 中实现,而不是在组件内部
* *
* @example * @example
* 推荐做法:纯数据组件
* ```typescript * ```typescript
* class HealthComponent extends Component { * class HealthComponent extends Component {
* public health: number = 100; * public health: number = 100;
* * public maxHealth: number = 100;
* public takeDamage(damage: number): void { * }
* this.health -= damage; * ```
* if (this.health <= 0) { *
* this.entity.destroy(); * @example
* 推荐做法:在 System 中处理逻辑
* ```typescript
* class HealthSystem extends EntitySystem {
* process(entities: Entity[]): void {
* for (const entity of entities) {
* const health = entity.getComponent(HealthComponent);
* if (health && health.health <= 0) {
* entity.destroy();
* }
* } * }
* } * }
* } * }
@@ -24,141 +33,49 @@ import type { Entity } from './Entity';
export abstract class Component implements IComponent { export abstract class Component implements IComponent {
/** /**
* 组件ID生成器 * 组件ID生成器
* *
* 用于为每个组件分配唯一的ID。 * 用于为每个组件分配唯一的ID。
*/ */
public static _idGenerator: number = 0; public static _idGenerator: number = 0;
/** /**
* 组件唯一标识符 * 组件唯一标识符
* *
* 在整个游戏生命周期中唯一的数字ID。 * 在整个游戏生命周期中唯一的数字ID。
*/ */
public readonly id: number; public readonly id: number;
/**
* 组件所属的实体
*
* 指向拥有此组件的实体实例。
*/
public entity!: Entity;
/**
* 组件启用状态
*
* 控制组件是否参与更新循环。
*/
private _enabled: boolean = true;
/**
* 更新顺序
*
* 决定组件在更新循环中的执行顺序。
*
* @see EntitySystem
*/
private _updateOrder: number = 0;
/** /**
* 创建组件实例 * 创建组件实例
* *
* 自动分配唯一ID给组件。 * 自动分配唯一ID给组件。
*/ */
constructor() { constructor() {
this.id = Component._idGenerator++; this.id = Component._idGenerator++;
} }
/**
* 获取组件启用状态
*
* 组件的实际启用状态取决于自身状态和所属实体的状态。
*
* @deprecated 不符合ECS架构规范建议自己实现DisabledComponent标记组件替代
* @returns 如果组件和所属实体都启用则返回true
*/
public get enabled(): boolean {
return this.entity ? this.entity.enabled && this._enabled : this._enabled;
}
/**
* 设置组件启用状态
*
* 当状态改变时会触发相应的生命周期回调。
*
* @deprecated 不符合ECS架构规范建议自己实现DisabledComponent标记组件替代
* @param value - 新的启用状态
*/
public set enabled(value: boolean) {
if (this._enabled !== value) {
this._enabled = value;
if (this._enabled) {
this.onEnabled();
} else {
this.onDisabled();
}
}
}
/**
* 获取更新顺序
*
* @deprecated 不符合ECS架构规范更新顺序应该由EntitySystem管理
* @see EntitySystem
* @returns 组件的更新顺序值
*/
public get updateOrder(): number {
return this._updateOrder;
}
/**
* 设置更新顺序
*
* @deprecated 不符合ECS架构规范更新顺序应该由EntitySystem管理
* @see EntitySystem
* @param value - 新的更新顺序值
*/
public set updateOrder(value: number) {
this._updateOrder = value;
}
/** /**
* 组件添加到实体时的回调 * 组件添加到实体时的回调
* *
* 当组件被添加到实体时调用,可以在此方法中进行初始化操作。 * 当组件被添加到实体时调用,可以在此方法中进行初始化操作。
*
* @remarks
* 这是一个生命周期钩子,用于组件的初始化逻辑。
* 虽然保留此方法,但建议将复杂的初始化逻辑放在 System 中处理。
*/ */
public onAddedToEntity(): void { public onAddedToEntity(): void {
} }
/** /**
* 组件从实体移除时的回调 * 组件从实体移除时的回调
* *
* 当组件从实体中移除时调用,可以在此方法中进行清理操作。 * 当组件从实体中移除时调用,可以在此方法中进行清理操作。
*
* @remarks
* 这是一个生命周期钩子,用于组件的清理逻辑。
* 虽然保留此方法,但建议将复杂的清理逻辑放在 System 中处理。
*/ */
public onRemovedFromEntity(): void { public onRemovedFromEntity(): void {
} }
/**
* 组件启用时的回调
*
* 当组件被启用时调用。
*/
public onEnabled(): void {
}
/**
* 组件禁用时的回调
*
* 当组件被禁用时调用。
*/
public onDisabled(): void {
}
/**
* 更新组件
*
* @deprecated 不符合ECS架构规范建议使用EntitySystem来处理更新逻辑
*/
public update(): void {
}
} }

View File

@@ -104,12 +104,7 @@ export class Entity {
* 所属场景引用 * 所属场景引用
*/ */
public scene: IScene | null = null; public scene: IScene | null = null;
/**
* 更新间隔
*/
public updateInterval: number = 1;
/** /**
* 销毁状态标志 * 销毁状态标志
*/ */
@@ -334,8 +329,6 @@ export class Entity {
const typeId = ComponentRegistry.getBitIndex(componentType); const typeId = ComponentRegistry.getBitIndex(componentType);
component.entity = this;
this._componentsByTypeId[typeId] = component; this._componentsByTypeId[typeId] = component;
const denseIndex = this.components.length; const denseIndex = this.components.length;
@@ -528,8 +521,6 @@ export class Entity {
component: component component: component
}); });
} }
component.entity = null as any;
// 通知所有相关的QuerySystem组件已变动 // 通知所有相关的QuerySystem组件已变动
Entity.notifyQuerySystems(this); Entity.notifyQuerySystems(this);
@@ -568,8 +559,6 @@ export class Entity {
} }
component.onRemovedFromEntity(); component.onRemovedFromEntity();
component.entity = null as any;
} }
this.components.length = 0; this.components.length = 0;
@@ -833,26 +822,6 @@ export class Entity {
} }
} }
/**
* 更新实体
*
* 调用所有组件的更新方法,并更新子实体。
*/
public update(): void {
if (!this.activeInHierarchy || this._isDestroyed) {
return;
}
for (const component of this.components) {
if (component.enabled) {
component.update();
}
}
for (const child of this._children) {
child.update();
}
}
/** /**
* 销毁实体 * 销毁实体

View File

@@ -152,13 +152,4 @@ export interface ISceneConfig {
* 场景名称 * 场景名称
*/ */
name?: string; name?: string;
/**
* 调试配置
*/
debug?: boolean;
/**
* 是否启用实体直接更新
* @default false
*/
enableEntityDirectUpdate?: boolean;
} }

View File

@@ -95,11 +95,6 @@ export class Scene implements IScene {
this.name = config.name; this.name = config.name;
} }
// 配置实体直接更新选项
if (config?.enableEntityDirectUpdate !== undefined) {
this.entities.setEnableEntityDirectUpdate(config.enableEntityDirectUpdate);
}
if (!Entity.eventBus) { if (!Entity.eventBus) {
Entity.eventBus = new EventBus(false); Entity.eventBus = new EventBus(false);
} }
@@ -180,16 +175,13 @@ export class Scene implements IScene {
* 更新场景 * 更新场景
*/ */
public update() { public update() {
// 更新实体列表 // 更新实体列表(处理延迟操作)
this.entities.updateLists(); this.entities.updateLists();
// 更新实体处理器 // 更新实体处理器
if (this.entityProcessors != null) if (this.entityProcessors != null)
this.entityProcessors.update(); this.entityProcessors.update();
// 更新实体
this.entities.update();
// 更新实体处理器后处理 // 更新实体处理器后处理
if (this.entityProcessors != null) if (this.entityProcessors != null)
this.entityProcessors.lateUpdate(); this.entityProcessors.lateUpdate();

View File

@@ -16,10 +16,6 @@ export class EntityList {
// 延迟操作队列 // 延迟操作队列
private _entitiesToAdd: Entity[] = []; private _entitiesToAdd: Entity[] = [];
private _entitiesToRemove: Entity[] = []; private _entitiesToRemove: Entity[] = [];
private _isUpdating = false;
// 是否启用实体直接更新
private _enableEntityDirectUpdate = false;
public get count(): number { public get count(): number {
return this.buffer.length; return this.buffer.length;
@@ -30,23 +26,11 @@ export class EntityList {
} }
/** /**
* 设置是否启用实体直接更新 * 添加实体
*/
public setEnableEntityDirectUpdate(enabled: boolean): void {
this._enableEntityDirectUpdate = enabled;
}
/**
* 添加实体(立即添加或延迟添加)
* @param entity 要添加的实体 * @param entity 要添加的实体
*/ */
public add(entity: Entity): void { public add(entity: Entity): void {
if (this._isUpdating) { this.addImmediate(entity);
// 如果正在更新中,延迟添加
this._entitiesToAdd.push(entity);
} else {
this.addImmediate(entity);
}
} }
/** /**
@@ -67,16 +51,11 @@ export class EntityList {
} }
/** /**
* 移除实体(立即移除或延迟移除) * 移除实体
* @param entity 要移除的实体 * @param entity 要移除的实体
*/ */
public remove(entity: Entity): void { public remove(entity: Entity): void {
if (this._isUpdating) { this.removeImmediate(entity);
// 如果正在更新中,延迟移除
this._entitiesToRemove.push(entity);
} else {
this.removeImmediate(entity);
}
} }
/** /**
@@ -147,25 +126,11 @@ export class EntityList {
} }
/** /**
* 更新实体列表和实体 * 更新实体列表
*
* 处理延迟操作(添加/删除实体)
*/ */
public update(): void { public update(): void {
this._isUpdating = true;
try {
// 只有启用实体直接更新时才遍历更新实体
if (this._enableEntityDirectUpdate) {
for (let i = 0; i < this.buffer.length; i++) {
const entity = this.buffer[i];
if (entity.enabled && !entity.isDestroyed) {
entity.update();
}
}
}
} finally {
this._isUpdating = false;
}
// 处理延迟操作 // 处理延迟操作
this.updateLists(); this.updateLists();
} }

View File

@@ -4,32 +4,20 @@
/** /**
* 组件接口 * 组件接口
* *
* 定义组件的基本契约,所有组件都应该实现此接口 * 定义组件的基本契约
* 在 ECS 架构中,组件应该是纯数据容器,不包含业务逻辑。
*/ */
export interface IComponent { export interface IComponent {
/** 组件唯一标识符 */ /** 组件唯一标识符 */
readonly id: number; readonly id: number;
/** 组件所属的实体ID */ /** 组件所属的实体ID */
entityId?: string | number; entityId?: string | number;
/** 组件启用状态 */
enabled: boolean;
/** 更新顺序 */
updateOrder: number;
/** 组件添加到实体时的回调 */ /** 组件添加到实体时的回调 */
onAddedToEntity(): void; onAddedToEntity(): void;
/** 组件从实体移除时的回调 */ /** 组件从实体移除时的回调 */
onRemovedFromEntity(): void; onRemovedFromEntity(): void;
/** 组件启用时的回调 */
onEnabled(): void;
/** 组件禁用时的回调 */
onDisabled(): void;
/**
* 更新组件
* @deprecated 不符合ECS架构规范建议使用EntitySystem来处理更新逻辑
*/
update(): void;
} }
/** /**

View File

@@ -6,9 +6,6 @@ class TestComponent extends Component {
public value: number = 100; public value: number = 100;
public onAddedCalled = false; public onAddedCalled = false;
public onRemovedCalled = false; public onRemovedCalled = false;
public onEnabledCalled = false;
public onDisabledCalled = false;
public updateCalled = false;
public override onAddedToEntity(): void { public override onAddedToEntity(): void {
this.onAddedCalled = true; this.onAddedCalled = true;
@@ -17,18 +14,6 @@ class TestComponent extends Component {
public override onRemovedFromEntity(): void { public override onRemovedFromEntity(): void {
this.onRemovedCalled = true; this.onRemovedCalled = true;
} }
public override onEnabled(): void {
this.onEnabledCalled = true;
}
public override onDisabled(): void {
this.onDisabledCalled = true;
}
public override update(): void {
this.updateCalled = true;
}
} }
class AnotherTestComponent extends Component { class AnotherTestComponent extends Component {
@@ -73,87 +58,10 @@ describe('Component - 组件基类测试', () => {
}); });
}); });
describe('启用状态管理', () => {
test('组件默认应该是启用的', () => {
expect(component.enabled).toBe(true);
});
test('设置组件禁用状态应该工作', () => {
component.enabled = false;
expect(component.enabled).toBe(false);
expect(component.onDisabledCalled).toBe(true);
});
test('重新启用组件应该工作', () => {
component.enabled = false;
component.onDisabledCalled = false;
component.onEnabledCalled = false;
component.enabled = true;
expect(component.enabled).toBe(true);
expect(component.onEnabledCalled).toBe(true);
});
test('设置相同的状态不应该触发回调', () => {
component.enabled = true; // 已经是true
expect(component.onEnabledCalled).toBe(false);
component.enabled = false;
component.onDisabledCalled = false;
component.enabled = false; // 已经是false
expect(component.onDisabledCalled).toBe(false);
});
test('组件启用状态应该受实体状态影响', () => {
entity.addComponent(component);
expect(component.enabled).toBe(true);
// 禁用实体应该让组件表现为禁用
entity.enabled = false;
expect(component.enabled).toBe(false);
// 重新启用实体
entity.enabled = true;
expect(component.enabled).toBe(true);
});
test('组件自身禁用时即使实体启用也应该是禁用的', () => {
entity.addComponent(component);
component.enabled = false;
entity.enabled = true;
expect(component.enabled).toBe(false);
});
test('没有实体时组件状态应该只取决于自身', () => {
// 组件还没有添加到实体
expect(component.enabled).toBe(true);
component.enabled = false;
expect(component.enabled).toBe(false);
});
});
describe('更新顺序', () => {
test('组件默认更新顺序应该是0', () => {
expect(component.updateOrder).toBe(0);
});
test('应该能够设置更新顺序', () => {
component.updateOrder = 10;
expect(component.updateOrder).toBe(10);
component.updateOrder = -5;
expect(component.updateOrder).toBe(-5);
});
});
describe('生命周期回调', () => { describe('生命周期回调', () => {
test('添加到实体时应该调用onAddedToEntity', () => { test('添加到实体时应该调用onAddedToEntity', () => {
expect(component.onAddedCalled).toBe(false); expect(component.onAddedCalled).toBe(false);
entity.addComponent(component); entity.addComponent(component);
expect(component.onAddedCalled).toBe(true); expect(component.onAddedCalled).toBe(true);
}); });
@@ -161,106 +69,73 @@ describe('Component - 组件基类测试', () => {
test('从实体移除时应该调用onRemovedFromEntity', () => { test('从实体移除时应该调用onRemovedFromEntity', () => {
entity.addComponent(component); entity.addComponent(component);
expect(component.onRemovedCalled).toBe(false); expect(component.onRemovedCalled).toBe(false);
entity.removeComponent(component); entity.removeComponent(component);
expect(component.onRemovedCalled).toBe(true); expect(component.onRemovedCalled).toBe(true);
}); });
test('启用时应该调用onEnabled', () => {
component.enabled = false;
component.onEnabledCalled = false;
component.enabled = true;
expect(component.onEnabledCalled).toBe(true);
});
test('禁用时应该调用onDisabled', () => {
expect(component.onDisabledCalled).toBe(false);
component.enabled = false;
expect(component.onDisabledCalled).toBe(true);
});
});
describe('更新方法', () => {
test('update方法应该可以被调用', () => {
expect(component.updateCalled).toBe(false);
component.update();
expect(component.updateCalled).toBe(true);
});
test('基类的默认生命周期方法应该安全调用', () => { test('基类的默认生命周期方法应该安全调用', () => {
const baseComponent = new (class extends Component {})(); const baseComponent = new (class extends Component {})();
// 这些方法不应该抛出异常 // 这些方法不应该抛出异常
expect(() => { expect(() => {
baseComponent.onAddedToEntity(); baseComponent.onAddedToEntity();
baseComponent.onRemovedFromEntity(); baseComponent.onRemovedFromEntity();
baseComponent.onEnabled();
baseComponent.onDisabled();
baseComponent.update();
}).not.toThrow(); }).not.toThrow();
}); });
}); });
describe('实体关联', () => { describe('实体-组件关系', () => {
test('组件应该能够访问其所属的实体', () => { test('组件可以被添加到实体', () => {
entity.addComponent(component); expect(() => {
expect(component.entity).toBe(entity); entity.addComponent(component);
}).not.toThrow();
expect(entity.hasComponent(TestComponent)).toBe(true);
expect(entity.getComponent(TestComponent)).toBe(component);
}); });
test('组件移除后entity引用行为', () => { test('组件可以从实体移除', () => {
entity.addComponent(component); entity.addComponent(component);
expect(component.entity).toBe(entity);
entity.removeComponent(component);
// 移除后entity引用可能被清空这是正常行为
// 具体行为取决于实现,这里只测试不会抛出异常
expect(() => { expect(() => {
const _ = component.entity; entity.removeComponent(component);
}).not.toThrow(); }).not.toThrow();
expect(entity.hasComponent(TestComponent)).toBe(false);
}); });
}); });
describe('边界情况', () => { describe('边界情况', () => {
test('多次启用禁用应该工作正常', () => {
for (let i = 0; i < 10; i++) {
component.enabled = false;
expect(component.enabled).toBe(false);
component.enabled = true;
expect(component.enabled).toBe(true);
}
});
test('极端更新顺序值应该被接受', () => {
component.updateOrder = 999999;
expect(component.updateOrder).toBe(999999);
component.updateOrder = -999999;
expect(component.updateOrder).toBe(-999999);
});
test('大量组件创建应该有不同的ID', () => { test('大量组件创建应该有不同的ID', () => {
const components: Component[] = []; const components: Component[] = [];
const count = 1000; const count = 1000;
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
components.push(new TestComponent()); components.push(new TestComponent());
} }
// 检查所有ID都不同 // 检查所有ID都不同
const ids = new Set(components.map(c => c.id)); const ids = new Set(components.map(c => c.id));
expect(ids.size).toBe(count); expect(ids.size).toBe(count);
}); });
test('组件应该是纯数据容器', () => {
// 验证组件只有数据字段
const comp = new TestComponent();
expect(comp.value).toBe(100);
// 可以修改数据
comp.value = 200;
expect(comp.value).toBe(200);
});
}); });
describe('继承和多态', () => { describe('继承和多态', () => {
test('不同类型的组件应该都继承自Component', () => { test('不同类型的组件应该都继承自Component', () => {
const test1 = new TestComponent(); const test1 = new TestComponent();
const test2 = new AnotherTestComponent(); const test2 = new AnotherTestComponent();
expect(test1).toBeInstanceOf(Component); expect(test1).toBeInstanceOf(Component);
expect(test2).toBeInstanceOf(Component); expect(test2).toBeInstanceOf(Component);
expect(test1).toBeInstanceOf(TestComponent); expect(test1).toBeInstanceOf(TestComponent);
@@ -269,9 +144,10 @@ describe('Component - 组件基类测试', () => {
test('组件应该能够重写基类方法', () => { test('组件应该能够重写基类方法', () => {
const test = new TestComponent(); const test = new TestComponent();
test.update();
// 重写生命周期方法应该工作
expect(test.updateCalled).toBe(true); entity.addComponent(test);
expect(test.onAddedCalled).toBe(true);
}); });
}); });
}); });

View File

@@ -68,7 +68,7 @@ describe('Entity - 组件缓存优化测试', () => {
expect(addedComponent).toBe(position); expect(addedComponent).toBe(position);
expect(entity.components.length).toBe(1); expect(entity.components.length).toBe(1);
expect(entity.components[0]).toBe(position); expect(entity.components[0]).toBe(position);
expect(position.entity).toBe(entity); expect(entity.hasComponent(TestPositionComponent)).toBe(true);
}); });
test('应该能够获取组件', () => { test('应该能够获取组件', () => {
@@ -96,7 +96,7 @@ describe('Entity - 组件缓存优化测试', () => {
entity.removeComponent(position); entity.removeComponent(position);
expect(entity.components.length).toBe(0); expect(entity.components.length).toBe(0);
expect(entity.hasComponent(TestPositionComponent)).toBe(false); expect(entity.hasComponent(TestPositionComponent)).toBe(false);
expect(position.entity).toBeNull(); expect(entity.getComponent(TestPositionComponent)).toBeNull();
}); });
}); });