You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

201 lines
6.8 KiB

import type { EntityId } from 'db://assets/mx/module/ecs/EntityId';
import { ECS_INSTANCE_TYPE, type EcsComponent, type ComponentCtor } from 'db://assets/mx/module/ecs/Component';
import { View1, View2 } from 'db://assets/mx/module/ecs/View';
import { createMembershipSet, membershipAdd, membershipForEach, membershipHas, type MembershipSet } from '../../tools/MembershipSet';
const COMPONENT_CTOR_PREFIX = '__mx_ecs_ctor__';
const _duplicateCtorWarned = createMembershipSet<string>();
export function componentTypeKey(ctor: ComponentCtor<EcsComponent>): string {
return ctor.ecsTypeId ?? ctor.name ?? 'UnknownComponent';
}
function canonicalComponentCtor<T extends EcsComponent>(ctor: ComponentCtor<T>): ComponentCtor<T> {
const typeKey = componentTypeKey(ctor);
const g = globalThis as unknown as Record<string, ComponentCtor<EcsComponent> | undefined>;
const key = `${COMPONENT_CTOR_PREFIX}${typeKey}`;
const existing = g[key];
if (!existing) {
g[key] = ctor;
return ctor;
}
if (existing !== ctor && !membershipHas(_duplicateCtorWarned, typeKey)) {
membershipAdd(_duplicateCtorWarned, typeKey);
console.log('[World] duplicate component ctor merged', typeKey);
}
return existing as ComponentCtor<T>;
}
/** 单类型组件存储桶 */
export class ComponentStorage<T extends EcsComponent> {
private readonly _map = new Map<EntityId, T>();
set(id: EntityId, comp: T): void {
this._map.set(id, comp);
}
get(id: EntityId): T | undefined {
return this._map.get(id);
}
has(id: EntityId): boolean {
return this._map.has(id);
}
delete(id: EntityId): void {
this._map.delete(id);
}
/** 遍历桶内所有组件(含已销毁实体的残留,调用方需自行 isAlive 过滤) */
forEach(fn: (id: EntityId, comp: T) => void): void {
this._map.forEach((comp, id) => fn(id, comp));
}
get size(): number {
return this._map.size;
}
clear(): void {
this._map.clear();
}
}
/** 实体世界:组件分桶存储 + 实体生命周期 + 查询 */
export class World {
private _nextId: EntityId = 1;
private readonly _alive = createMembershipSet<EntityId>();
private readonly _storagesByTypeId = new Map<string, ComponentStorage<any>>();
/** 下一实体 ID(快照恢复用) */
get nextEntityId(): EntityId {
return this._nextId;
}
/** 设置下一实体 ID(快照恢复前调用,须 ≥ 已有最大实体 ID + 1) */
setNextEntityId(next: EntityId): void {
this._nextId = Math.max(1, next);
}
/** 创建新实体 */
spawn(): EntityId {
const id = this._nextId++;
membershipAdd(this._alive, id);
return id;
}
/** 以指定 ID 复活实体(战斗快照冷恢复) */
spawnWithId(id: EntityId): EntityId {
if (id <= 0) return this.spawn();
membershipAdd(this._alive, id);
if (id >= this._nextId) this._nextId = id + 1;
return id;
}
/** 销毁实体(组件数据保留以便本帧查询,下帧清理) */
destroy(id: EntityId): void {
this._alive.delete(id);
}
/** 实体是否存活 */
isAlive(id: EntityId): boolean {
return id !== 0 && this._alive.has(id);
}
/** 清理已销毁实体的残留组件(可定期调用) */
gc(): void {
const alive = this._alive;
this._storagesByTypeId.forEach((storage) => {
storage.forEach((id) => {
if (!alive.has(id)) storage.delete(id);
});
});
}
private _storageForTypeId<T extends EcsComponent>(typeId: string): ComponentStorage<T> {
let s = this._storagesByTypeId.get(typeId);
if (!s) {
s = new ComponentStorage<T>();
this._storagesByTypeId.set(typeId, s);
}
return s as ComponentStorage<T>;
}
private _storage<T extends EcsComponent>(ctor: ComponentCtor<T>): ComponentStorage<T> {
const canonical = canonicalComponentCtor(ctor);
return this._storageForTypeId(componentTypeKey(canonical));
}
private _tagInstanceType(comp: EcsComponent, ctor: ComponentCtor<EcsComponent>): void {
(comp as Record<symbol, string>)[ECS_INSTANCE_TYPE] = componentTypeKey(ctor);
}
private _matchesInstanceType(comp: EcsComponent, expectedTypeId: string): boolean {
const actual = (comp as Record<symbol, string | undefined>)[ECS_INSTANCE_TYPE];
if (actual == null) return true;
if (actual === expectedTypeId) return true;
console.error(`[World] component type mismatch expected=${expectedTypeId} actual=${actual}`);
return false;
}
/** 添加组件(覆盖已有) */
addComponent<T extends EcsComponent>(id: EntityId, comp: T): T {
const ctor = canonicalComponentCtor(comp.constructor as ComponentCtor<T>);
this._tagInstanceType(comp, comp.constructor as ComponentCtor<T>);
this._storage(ctor).set(id, comp);
return comp;
}
/** 读取组件,不存在返回 undefined */
getComponent<T extends EcsComponent>(id: EntityId, ctor: ComponentCtor<T>): T | undefined {
const typeId = componentTypeKey(canonicalComponentCtor(ctor));
const comp = this._storage(ctor).get(id);
if (!comp) return undefined;
if (!this._matchesInstanceType(comp, typeId)) return undefined;
return comp as T;
}
/** 读取组件,不存在抛错 */
requireComponent<T extends EcsComponent>(id: EntityId, ctor: ComponentCtor<T>): T {
const comp = this.getComponent(id, ctor);
if (!comp) throw new Error(`[World] entity ${id} missing component ${componentTypeKey(ctor)}`);
return comp;
}
/** 是否拥有某组件 */
hasComponent<T extends EcsComponent>(id: EntityId, ctor: ComponentCtor<T>): boolean {
return this.getComponent(id, ctor) != null;
}
/** 移除组件 */
removeComponent<T extends EcsComponent>(id: EntityId, ctor: ComponentCtor<T>): void {
this._storage(ctor).delete(id);
}
/** 单组件查询视图 */
view<C extends EcsComponent>(ctor: ComponentCtor<C>): View1<C> {
return new View1(this._storage(ctor), (id) => this.isAlive(id));
}
/** 双组件查询视图(按 ctorA 桶遍历 + 过滤 ctorB) */
view2<A extends EcsComponent, B extends EcsComponent>(
ctorA: ComponentCtor<A>,
ctorB: ComponentCtor<B>,
): View2<A, B> {
return new View2(
this._storage(ctorA),
ctorB,
(id, ctor) => this.getComponent(id, ctor),
(id) => this.isAlive(id),
);
}
/** 遍历所有存活实体 */
forEachAlive(fn: (id: EntityId) => void): void {
membershipForEach(this._alive, fn);
}
/** 存活实体数量 */
get aliveCount(): number {
return this._alive.size;
}
}