@@ -63,6 +63,8 @@ export class World {
6363 private archetypesByComponent = new Map < EntityId < any > , Archetype [ ] > ( ) ;
6464 private entityReferences : EntityReferencesMap = new Map ( ) ;
6565 private dontFragmentRelations : Map < EntityId , Map < EntityId < any > , any > > = new Map ( ) ;
66+ private componentEntityComponents : Map < EntityId , Map < EntityId < any > , any > > = new Map ( ) ;
67+ private relationEntityIdsByTarget : Map < EntityId , Set < EntityId > > = new Map ( ) ;
6668
6769 // Query management
6870 private queries : Query [ ] = [ ] ;
@@ -86,6 +88,24 @@ export class World {
8688 this . entityIdManager . deserializeState ( snapshot . entityManager ) ;
8789 }
8890
91+ if ( Array . isArray ( snapshot . componentEntities ) ) {
92+ for ( const entry of snapshot . componentEntities ) {
93+ const entityId = decodeSerializedId ( entry . id ) ;
94+ if ( ! this . isComponentEntityId ( entityId ) ) continue ;
95+
96+ const componentsArray : SerializedComponent [ ] = entry . components || [ ] ;
97+ const componentMap = new Map < EntityId < any > , any > ( ) ;
98+
99+ for ( const componentEntry of componentsArray ) {
100+ const componentType = decodeSerializedId ( componentEntry . type ) ;
101+ componentMap . set ( componentType , componentEntry . value ) ;
102+ }
103+
104+ this . componentEntityComponents . set ( entityId , componentMap ) ;
105+ this . registerRelationEntityId ( entityId ) ;
106+ }
107+ }
108+
89109 if ( Array . isArray ( snapshot . entities ) ) {
90110 for ( const entry of snapshot . entities ) {
91111 const entityId = decodeSerializedId ( entry . id ) ;
@@ -140,6 +160,69 @@ export class World {
140160 return entityId as EntityId < T > ;
141161 }
142162
163+ private isComponentEntityId ( entityId : EntityId ) : boolean {
164+ const detailed = getDetailedIdType ( entityId ) ;
165+ return detailed . type !== "entity" && detailed . type !== "invalid" ;
166+ }
167+
168+ private registerRelationEntityId ( entityId : EntityId ) : void {
169+ const detailed = getDetailedIdType ( entityId ) ;
170+ if ( detailed . type !== "entity-relation" ) return ;
171+
172+ const targetId = detailed . targetId ;
173+ if ( targetId === undefined ) return ;
174+
175+ const existing = this . relationEntityIdsByTarget . get ( targetId ) ;
176+ if ( existing ) {
177+ existing . add ( entityId ) ;
178+ return ;
179+ }
180+
181+ this . relationEntityIdsByTarget . set ( targetId , new Set ( [ entityId ] ) ) ;
182+ }
183+
184+ private unregisterRelationEntityId ( entityId : EntityId ) : void {
185+ const detailed = getDetailedIdType ( entityId ) ;
186+ if ( detailed . type !== "entity-relation" ) return ;
187+
188+ const targetId = detailed . targetId ;
189+ if ( targetId === undefined ) return ;
190+
191+ const existing = this . relationEntityIdsByTarget . get ( targetId ) ;
192+ if ( ! existing ) return ;
193+
194+ existing . delete ( entityId ) ;
195+ if ( existing . size === 0 ) {
196+ this . relationEntityIdsByTarget . delete ( targetId ) ;
197+ }
198+ }
199+
200+ private getComponentEntityComponents ( entityId : EntityId , create : boolean ) : Map < EntityId < any > , any > | undefined {
201+ let data = this . componentEntityComponents . get ( entityId ) ;
202+ if ( ! data && create ) {
203+ data = new Map ( ) ;
204+ this . componentEntityComponents . set ( entityId , data ) ;
205+ this . registerRelationEntityId ( entityId ) ;
206+ }
207+ return data ;
208+ }
209+
210+ private clearComponentEntityComponents ( entityId : EntityId ) : void {
211+ if ( this . componentEntityComponents . delete ( entityId ) ) {
212+ this . unregisterRelationEntityId ( entityId ) ;
213+ }
214+ }
215+
216+ private cleanupComponentEntitiesReferencingEntity ( targetId : EntityId ) : void {
217+ const relationEntities = this . relationEntityIdsByTarget . get ( targetId ) ;
218+ if ( ! relationEntities ) return ;
219+
220+ for ( const relationEntityId of relationEntities ) {
221+ this . componentEntityComponents . delete ( relationEntityId ) ;
222+ }
223+ this . relationEntityIdsByTarget . delete ( targetId ) ;
224+ }
225+
143226 private destroyEntityImmediate ( entityId : EntityId ) : void {
144227 const queue : EntityId [ ] = [ entityId ] ;
145228 const visited = new Set < EntityId > ( ) ;
@@ -176,6 +259,7 @@ export class World {
176259
177260 this . cleanupArchetypesReferencingEntity ( cur ) ;
178261 this . entityIdManager . deallocate ( cur ) ;
262+ this . cleanupComponentEntitiesReferencingEntity ( cur ) ;
179263 }
180264 }
181265
@@ -191,6 +275,7 @@ export class World {
191275 * }
192276 */
193277 exists ( entityId : EntityId ) : boolean {
278+ if ( this . isComponentEntityId ( entityId ) ) return true ;
194279 return this . entityToArchetype . has ( entityId ) ;
195280 }
196281
@@ -290,6 +375,23 @@ export class World {
290375 * }
291376 */
292377 has < T > ( entityId : EntityId , componentType : EntityId < T > ) : boolean {
378+ if ( this . isComponentEntityId ( entityId ) ) {
379+ if ( isWildcardRelationId ( componentType ) ) {
380+ const componentId = getComponentIdFromRelationId ( componentType ) ;
381+ if ( componentId === undefined ) return false ;
382+
383+ const data = this . componentEntityComponents . get ( entityId ) ;
384+ if ( ! data ) return false ;
385+
386+ for ( const key of data . keys ( ) ) {
387+ if ( getComponentIdFromRelationId ( key ) === componentId ) return true ;
388+ }
389+ return false ;
390+ }
391+
392+ return this . componentEntityComponents . get ( entityId ) ?. has ( componentType ) ?? false ;
393+ }
394+
293395 const archetype = this . entityToArchetype . get ( entityId ) ;
294396 if ( ! archetype ) return false ;
295397
@@ -330,6 +432,35 @@ export class World {
330432 entityId : EntityId ,
331433 componentType : EntityId < T > | WildcardRelationId < T > = entityId as EntityId < T > ,
332434 ) : T | [ EntityId < unknown > , any ] [ ] {
435+ if ( this . isComponentEntityId ( entityId ) ) {
436+ if ( isWildcardRelationId ( componentType as EntityId < any > ) ) {
437+ const componentId = getComponentIdFromRelationId ( componentType as EntityId < any > ) ;
438+ const data = this . componentEntityComponents . get ( entityId ) ;
439+ const relations : [ EntityId < unknown > , any ] [ ] = [ ] ;
440+
441+ if ( componentId !== undefined && data ) {
442+ for ( const [ key , value ] of data . entries ( ) ) {
443+ if ( getComponentIdFromRelationId ( key ) === componentId ) {
444+ const detailed = getDetailedIdType ( key ) ;
445+ if ( detailed . type === "entity-relation" || detailed . type === "component-relation" ) {
446+ relations . push ( [ detailed . targetId ! , value ] ) ;
447+ }
448+ }
449+ }
450+ }
451+
452+ return relations ;
453+ }
454+
455+ const data = this . componentEntityComponents . get ( entityId ) ;
456+ if ( ! data || ! data . has ( componentType as EntityId < any > ) ) {
457+ throw new Error (
458+ `Entity ${ entityId } does not have component ${ componentType } . Use has() to check component existence before calling get().` ,
459+ ) ;
460+ }
461+ return data . get ( componentType as EntityId < any > ) ;
462+ }
463+
333464 const archetype = this . entityToArchetype . get ( entityId ) ;
334465 if ( ! archetype ) {
335466 throw new Error ( `Entity ${ entityId } does not exist` ) ;
@@ -374,6 +505,33 @@ export class World {
374505 getOptional < T > ( entityId : EntityId < T > ) : { value : T } | undefined ;
375506 getOptional < T > ( entityId : EntityId , componentType : EntityId < T > ) : { value : T } | undefined ;
376507 getOptional < T > ( entityId : EntityId , componentType : EntityId < T > = entityId as EntityId < T > ) : { value : T } | undefined {
508+ if ( this . isComponentEntityId ( entityId ) ) {
509+ if ( isWildcardRelationId ( componentType ) ) {
510+ const componentId = getComponentIdFromRelationId ( componentType ) ;
511+ if ( componentId === undefined ) return undefined ;
512+
513+ const data = this . componentEntityComponents . get ( entityId ) ;
514+ if ( ! data ) return undefined ;
515+
516+ const relations : [ EntityId < unknown > , any ] [ ] = [ ] ;
517+ for ( const [ key , value ] of data . entries ( ) ) {
518+ if ( getComponentIdFromRelationId ( key ) === componentId ) {
519+ const detailed = getDetailedIdType ( key ) ;
520+ if ( detailed . type === "entity-relation" || detailed . type === "component-relation" ) {
521+ relations . push ( [ detailed . targetId ! , value ] ) ;
522+ }
523+ }
524+ }
525+
526+ if ( relations . length === 0 ) return undefined ;
527+ return { value : relations as T } ;
528+ }
529+
530+ const data = this . componentEntityComponents . get ( entityId ) ;
531+ if ( ! data || ! data . has ( componentType ) ) return undefined ;
532+ return { value : data . get ( componentType ) } ;
533+ }
534+
377535 const archetype = this . entityToArchetype . get ( entityId ) ;
378536 if ( ! archetype ) {
379537 throw new Error ( `Entity ${ entityId } does not exist` ) ;
@@ -811,6 +969,11 @@ export class World {
811969 executeEntityCommands ( entityId : EntityId , commands : Command [ ] ) : ComponentChangeset {
812970 const changeset = new ComponentChangeset ( ) ;
813971
972+ if ( this . isComponentEntityId ( entityId ) ) {
973+ this . executeComponentEntityCommands ( entityId , commands ) ;
974+ return changeset ;
975+ }
976+
814977 if ( commands . some ( ( cmd ) => cmd . type === "destroy" ) ) {
815978 this . destroyEntityImmediate ( entityId ) ;
816979 return changeset ;
@@ -846,6 +1009,40 @@ export class World {
8461009 return changeset ;
8471010 }
8481011
1012+ private executeComponentEntityCommands ( entityId : EntityId , commands : Command [ ] ) : void {
1013+ if ( commands . some ( ( cmd ) => cmd . type === "destroy" ) ) {
1014+ this . clearComponentEntityComponents ( entityId ) ;
1015+ return ;
1016+ }
1017+
1018+ for ( const command of commands ) {
1019+ if ( command . type === "set" && command . componentType ) {
1020+ const data = this . getComponentEntityComponents ( entityId , true ) ! ;
1021+ data . set ( command . componentType , command . component ) ;
1022+ } else if ( command . type === "delete" && command . componentType ) {
1023+ const data = this . componentEntityComponents . get ( entityId ) ;
1024+ if ( ! data ) continue ;
1025+
1026+ if ( isWildcardRelationId ( command . componentType ) ) {
1027+ const componentId = getComponentIdFromRelationId ( command . componentType ) ;
1028+ if ( componentId !== undefined ) {
1029+ for ( const key of Array . from ( data . keys ( ) ) ) {
1030+ if ( getComponentIdFromRelationId ( key ) === componentId ) {
1031+ data . delete ( key ) ;
1032+ }
1033+ }
1034+ }
1035+ } else {
1036+ data . delete ( command . componentType ) ;
1037+ }
1038+
1039+ if ( data . size === 0 ) {
1040+ this . clearComponentEntityComponents ( entityId ) ;
1041+ }
1042+ }
1043+ }
1044+ }
1045+
8491046 private createHooksContext ( ) : HooksContext {
8501047 return {
8511048 hooks : this . legacyHooks ,
@@ -1033,10 +1230,22 @@ export class World {
10331230 }
10341231 }
10351232
1233+ const componentEntities : SerializedEntity [ ] = [ ] ;
1234+ for ( const [ entityId , components ] of this . componentEntityComponents . entries ( ) ) {
1235+ componentEntities . push ( {
1236+ id : encodeEntityId ( entityId ) ,
1237+ components : Array . from ( components . entries ( ) ) . map ( ( [ rawType , value ] ) => ( {
1238+ type : encodeEntityId ( rawType ) ,
1239+ value : value === MISSING_COMPONENT ? undefined : value ,
1240+ } ) ) ,
1241+ } ) ;
1242+ }
1243+
10361244 return {
10371245 version : 1 ,
10381246 entityManager : this . entityIdManager . serializeState ( ) ,
10391247 entities,
1248+ componentEntities,
10401249 } ;
10411250 }
10421251}
0 commit comments