@@ -6,6 +6,7 @@ import { getOrCompute } from "../utils/utils";
66import { Archetype , MISSING_COMPONENT } from "./archetype" ;
77import { hasWildcardRelation } from "./archetype-helpers" ;
88import { EntityBuilder } from "./builder" ;
9+ import { normalizeComponentTypes } from "./component-type-utils" ;
910import type { ComponentId , EntityId , WildcardRelationId } from "./entity" ;
1011import {
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 ) ) ;
0 commit comments