Skip to content

Commit 04ad736

Browse files
committed
refactor world internals and normalize component type handling
先分析问题,然后开始优化 但要保证公开api不变
1 parent 2903bee commit 04ad736

File tree

5 files changed

+124
-103
lines changed

5 files changed

+124
-103
lines changed

src/core/archetype.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
isRelationType,
88
} from "./archetype-helpers";
99
import type { EntityId, WildcardRelationId } from "./entity";
10+
import { normalizeComponentTypes } from "./component-type-utils";
1011
import {
1112
getComponentIdFromRelationId,
1213
getDetailedIdType,
@@ -71,7 +72,7 @@ export class Archetype {
7172
private componentDataSourcesCache: Map<string, (any[] | EntityId<any>[] | undefined)[]> = new Map();
7273

7374
constructor(componentTypes: EntityId<any>[], dontFragmentRelations: Map<EntityId, Map<EntityId<any>, any>>) {
74-
this.componentTypes = [...componentTypes].sort((a, b) => a - b);
75+
this.componentTypes = normalizeComponentTypes(componentTypes);
7576
this.componentTypeSet = new Set(this.componentTypes);
7677
this.dontFragmentRelations = dontFragmentRelations;
7778

@@ -92,7 +93,7 @@ export class Archetype {
9293
*/
9394
matches(componentTypes: EntityId<any>[]): boolean {
9495
if (this.componentTypes.length !== componentTypes.length) return false;
95-
const sortedTypes = [...componentTypes].sort((a, b) => a - b);
96+
const sortedTypes = normalizeComponentTypes(componentTypes);
9697
return this.componentTypes.every((type, index) => type === sortedTypes[index]);
9798
}
9899

src/core/component-type-utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { EntityId } from "./entity";
2+
3+
/**
4+
* Normalize component type collections into a stable ascending order.
5+
* This keeps cache keys and archetype signatures deterministic.
6+
*/
7+
export function normalizeComponentTypes(componentTypes: Iterable<EntityId<any>>): EntityId<any>[] {
8+
return [...componentTypes].sort((a, b) => a - b);
9+
}

src/core/world-commands.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ComponentChangeset } from "../commands/changeset";
22
import type { Command } from "../commands/command-buffer";
33
import type { Archetype } from "./archetype";
4+
import { normalizeComponentTypes } from "./component-type-utils";
45
import {
56
getComponentIdFromRelationId,
67
isDontFragmentComponent,
@@ -307,7 +308,7 @@ export function filterRegularComponentTypes(componentTypes: Iterable<EntityId<an
307308

308309
export function areComponentTypesEqual(types1: EntityId<any>[], types2: EntityId<any>[]): boolean {
309310
if (types1.length !== types2.length) return false;
310-
const sorted1 = [...types1].sort((a, b) => a - b);
311-
const sorted2 = [...types2].sort((a, b) => a - b);
311+
const sorted1 = normalizeComponentTypes(types1);
312+
const sorted2 = normalizeComponentTypes(types2);
312313
return sorted1.every((v, i) => v === sorted2[i]);
313314
}

src/core/world.ts

Lines changed: 107 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getOrCompute } from "../utils/utils";
66
import { Archetype, MISSING_COMPONENT } from "./archetype";
77
import { hasWildcardRelation } from "./archetype-helpers";
88
import { EntityBuilder } from "./builder";
9+
import { normalizeComponentTypes } from "./component-type-utils";
910
import type { ComponentId, EntityId, WildcardRelationId } from "./entity";
1011
import {
1112
ENTITY_ID_START,
@@ -284,6 +285,97 @@ export class World {
284285
return this.entityToArchetype.has(entityId);
285286
}
286287

288+
private assertEntityExists(entityId: EntityId, label: "Entity" | "Component entity"): void {
289+
if (!this.exists(entityId)) {
290+
throw new Error(`${label} ${entityId} does not exist`);
291+
}
292+
}
293+
294+
private assertComponentTypeValid(componentType: EntityId): void {
295+
const detailedType = getDetailedIdType(componentType);
296+
if (detailedType.type === "invalid") {
297+
throw new Error(`Invalid component type: ${componentType}`);
298+
}
299+
}
300+
301+
private assertSetComponentTypeValid(componentType: EntityId): void {
302+
const detailedType = getDetailedIdType(componentType);
303+
if (detailedType.type === "invalid") {
304+
throw new Error(`Invalid component type: ${componentType}`);
305+
}
306+
if (detailedType.type === "wildcard-relation") {
307+
throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
308+
}
309+
}
310+
311+
private resolveSetOperation(
312+
entityId: EntityId | ComponentId,
313+
componentTypeOrComponent?: EntityId | any,
314+
maybeComponent?: any,
315+
): { entityId: EntityId; componentType: EntityId; component: any } {
316+
// Handle singleton component overload: set(componentId, data)
317+
if (maybeComponent === undefined && componentTypeOrComponent !== undefined) {
318+
const detailedType = getDetailedIdType(entityId);
319+
if (detailedType.type === "component" || detailedType.type === "component-relation") {
320+
const componentId = entityId as ComponentId;
321+
this.assertEntityExists(componentId, "Component entity");
322+
this.assertSetComponentTypeValid(componentId);
323+
return { entityId: componentId, componentType: componentId, component: componentTypeOrComponent };
324+
}
325+
}
326+
327+
const targetEntityId = entityId as EntityId;
328+
const componentType = componentTypeOrComponent as EntityId;
329+
this.assertEntityExists(targetEntityId, "Entity");
330+
this.assertSetComponentTypeValid(componentType);
331+
332+
return { entityId: targetEntityId, componentType, component: maybeComponent };
333+
}
334+
335+
private resolveRemoveOperation<T>(
336+
entityId: EntityId | ComponentId,
337+
componentType?: EntityId<T>,
338+
): { entityId: EntityId; componentType: EntityId } {
339+
// Handle singleton component overload: remove(componentId)
340+
if (componentType === undefined) {
341+
const componentId = entityId as ComponentId<T>;
342+
this.assertEntityExists(componentId, "Component entity");
343+
return { entityId: componentId, componentType: componentId };
344+
}
345+
346+
const targetEntityId = entityId as EntityId;
347+
this.assertEntityExists(targetEntityId, "Entity");
348+
this.assertComponentTypeValid(componentType);
349+
350+
return { entityId: targetEntityId, componentType };
351+
}
352+
353+
private getComponentEntityWildcardRelations<T>(
354+
entityId: EntityId,
355+
wildcardComponentType: WildcardRelationId<T>,
356+
): [EntityId<unknown>, T][] {
357+
const componentId = getComponentIdFromRelationId(wildcardComponentType);
358+
const data = this.componentEntityComponents.get(entityId);
359+
if (componentId === undefined || !data) {
360+
return [];
361+
}
362+
363+
const relations: [EntityId<unknown>, T][] = [];
364+
for (const [key, value] of data.entries()) {
365+
if (getComponentIdFromRelationId(key) !== componentId) {
366+
continue;
367+
}
368+
369+
const detailed = getDetailedIdType(key);
370+
if (detailed.type === "entity-relation" || detailed.type === "component-relation") {
371+
// Safe: targetId is guaranteed to exist for entity-relation and component-relation types
372+
relations.push([detailed.targetId, value]);
373+
}
374+
}
375+
376+
return relations;
377+
}
378+
287379
/**
288380
* Adds or updates a component on an entity (or marks void component as present).
289381
* The change is buffered and takes effect after calling `world.sync()`.
@@ -311,47 +403,12 @@ export class World {
311403
set<T>(entityId: EntityId, componentType: EntityId<T>, component: NoInfer<T>): void;
312404
set<T>(componentId: ComponentId<T>, component: NoInfer<T>): void;
313405
set(entityId: EntityId | ComponentId, componentTypeOrComponent?: EntityId | any, maybeComponent?: any): void {
314-
// Handle singleton component overload: set(componentId, data)
315-
if (maybeComponent === undefined && componentTypeOrComponent !== undefined) {
316-
const detailedType = getDetailedIdType(entityId);
317-
// Check if this looks like a singleton call (2 arguments, second is not an EntityId)
318-
if (detailedType.type === "component" || detailedType.type === "component-relation") {
319-
// Singleton component: set(componentId, data)
320-
const componentId = entityId as ComponentId;
321-
const component = componentTypeOrComponent;
322-
if (!this.exists(componentId)) {
323-
throw new Error(`Component entity ${componentId} does not exist`);
324-
}
325-
const detailedComponentType = getDetailedIdType(componentId);
326-
if (detailedComponentType.type === "invalid") {
327-
throw new Error(`Invalid component type: ${componentId}`);
328-
}
329-
if (detailedComponentType.type === "wildcard-relation") {
330-
throw new Error(`Cannot directly add wildcard relation components: ${componentId}`);
331-
}
332-
this.commandBuffer.set(componentId, componentId, component);
333-
return;
334-
}
335-
}
336-
337-
// Standard overload: set(entityId, componentType, data?) or set(entityId, componentType)
338-
const entityIdArg = entityId as EntityId;
339-
const componentType = componentTypeOrComponent as EntityId;
340-
const component = maybeComponent;
341-
342-
if (!this.exists(entityIdArg)) {
343-
throw new Error(`Entity ${entityIdArg} does not exist`);
344-
}
345-
346-
const detailedType = getDetailedIdType(componentType);
347-
if (detailedType.type === "invalid") {
348-
throw new Error(`Invalid component type: ${componentType}`);
349-
}
350-
if (detailedType.type === "wildcard-relation") {
351-
throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
352-
}
353-
354-
this.commandBuffer.set(entityIdArg, componentType, component);
406+
const { entityId: targetEntityId, componentType, component } = this.resolveSetOperation(
407+
entityId,
408+
componentTypeOrComponent,
409+
maybeComponent,
410+
);
411+
this.commandBuffer.set(targetEntityId, componentType, component);
355412
}
356413

357414
/**
@@ -380,27 +437,11 @@ export class World {
380437
remove<T>(componentId: ComponentId<T>): void;
381438
remove<T>(entityId: EntityId, componentType: EntityId<T>): void;
382439
remove<T>(entityId: EntityId | ComponentId, componentType?: EntityId<T>): void {
383-
// Handle singleton component overload: remove(componentId)
384-
if (componentType === undefined) {
385-
const componentId = entityId as ComponentId<T>;
386-
if (!this.exists(componentId)) {
387-
throw new Error(`Component entity ${componentId} does not exist`);
388-
}
389-
this.commandBuffer.remove(componentId, componentId);
390-
return;
391-
}
392-
393-
const entityIdArg = entityId as EntityId;
394-
if (!this.exists(entityIdArg)) {
395-
throw new Error(`Entity ${entityIdArg} does not exist`);
396-
}
397-
398-
const detailedType = getDetailedIdType(componentType);
399-
if (detailedType.type === "invalid") {
400-
throw new Error(`Invalid component type: ${componentType}`);
401-
}
402-
403-
this.commandBuffer.remove(entityIdArg, componentType);
440+
const { entityId: targetEntityId, componentType: targetComponentType } = this.resolveRemoveOperation(
441+
entityId,
442+
componentType,
443+
);
444+
this.commandBuffer.remove(targetEntityId, targetComponentType);
404445
}
405446

406447
/**
@@ -506,23 +547,7 @@ export class World {
506547
): T | [EntityId<unknown>, any][] {
507548
if (this.isComponentEntityId(entityId)) {
508549
if (isWildcardRelationId(componentType as EntityId<any>)) {
509-
const componentId = getComponentIdFromRelationId(componentType as EntityId<any>);
510-
const data = this.componentEntityComponents.get(entityId);
511-
const relations: [EntityId<unknown>, any][] = [];
512-
513-
if (componentId !== undefined && data) {
514-
for (const [key, value] of data.entries()) {
515-
if (getComponentIdFromRelationId(key) === componentId) {
516-
const detailed = getDetailedIdType(key);
517-
if (detailed.type === "entity-relation" || detailed.type === "component-relation") {
518-
// Safe: targetId is guaranteed to exist for entity-relation and component-relation types
519-
relations.push([detailed.targetId, value]);
520-
}
521-
}
522-
}
523-
}
524-
525-
return relations;
550+
return this.getComponentEntityWildcardRelations(entityId, componentType as WildcardRelationId<T>);
526551
}
527552

528553
const data = this.componentEntityComponents.get(entityId);
@@ -580,23 +605,7 @@ export class World {
580605
getOptional<T>(entityId: EntityId, componentType: EntityId<T> = entityId as EntityId<T>): { value: T } | undefined {
581606
if (this.isComponentEntityId(entityId)) {
582607
if (isWildcardRelationId(componentType)) {
583-
const componentId = getComponentIdFromRelationId(componentType);
584-
if (componentId === undefined) return undefined;
585-
586-
const data = this.componentEntityComponents.get(entityId);
587-
if (!data) return undefined;
588-
589-
const relations: [EntityId<unknown>, any][] = [];
590-
for (const [key, value] of data.entries()) {
591-
if (getComponentIdFromRelationId(key) === componentId) {
592-
const detailed = getDetailedIdType(key);
593-
if (detailed.type === "entity-relation" || detailed.type === "component-relation") {
594-
// Safe: targetId is guaranteed to exist for entity-relation and component-relation types
595-
relations.push([detailed.targetId, value]);
596-
}
597-
}
598-
}
599-
608+
const relations = this.getComponentEntityWildcardRelations(entityId, componentType as WildcardRelationId<any>);
600609
if (relations.length === 0) return undefined;
601610
return { value: relations as T };
602611
}
@@ -843,7 +852,7 @@ export class World {
843852
* });
844853
*/
845854
createQuery(componentTypes: EntityId<any>[], filter: QueryFilter = {}): Query {
846-
const sortedTypes = [...componentTypes].sort((a, b) => a - b);
855+
const sortedTypes = normalizeComponentTypes(componentTypes);
847856
const filterKey = serializeQueryFilter(filter);
848857
const key = `${this.createArchetypeSignature(sortedTypes)}${filterKey ? `|${filterKey}` : ""}`;
849858

@@ -1200,7 +1209,7 @@ export class World {
12001209

12011210
private ensureArchetype(componentTypes: Iterable<EntityId<any>>): Archetype {
12021211
const regularTypes = filterRegularComponentTypes(componentTypes);
1203-
const sortedTypes = regularTypes.sort((a, b) => a - b);
1212+
const sortedTypes = normalizeComponentTypes(regularTypes);
12041213
const hashKey = this.createArchetypeSignature(sortedTypes);
12051214

12061215
return getOrCompute(this.archetypeBySignature, hashKey, () => this.createNewArchetype(sortedTypes));

src/query/query.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Archetype } from "../core/archetype";
2+
import { normalizeComponentTypes } from "../core/component-type-utils";
23
import type { EntityId, WildcardRelationId } from "../core/entity";
34
import { getDetailedIdType, isDontFragmentComponent } from "../core/entity";
45
import type { ComponentTuple, ComponentType } from "../core/types";
@@ -23,7 +24,7 @@ export class Query {
2324

2425
constructor(world: World, componentTypes: EntityId<any>[], filter: QueryFilter = {}) {
2526
this.world = world;
26-
this.componentTypes = [...componentTypes].sort((a, b) => a - b);
27+
this.componentTypes = normalizeComponentTypes(componentTypes);
2728
this.filter = filter;
2829
// Pre-compute wildcard types once
2930
this.wildcardTypes = this.componentTypes.filter(

0 commit comments

Comments
 (0)