@@ -24,6 +24,107 @@ import { serializeQueryFilter, type QueryFilter } from "./query-filter";
2424import type { ComponentTuple , LifecycleHook } from "./types" ;
2525import { getOrCreateWithSideEffect } from "./utils" ;
2626
27+ // -----------------------------------------------------------------------------
28+ // Serialization helpers for IDs
29+ // -----------------------------------------------------------------------------
30+
31+ export type SerializedEntityId = number | string | { component : string ; target : number | string | "*" } ;
32+
33+ /**
34+ * Encode an internal EntityId into a SerializedEntityId for snapshots
35+ */
36+ function encodeEntityId ( id : EntityId < any > ) : SerializedEntityId {
37+ const detailed = getDetailedIdType ( id ) ;
38+ switch ( detailed . type ) {
39+ case "component" : {
40+ const name = getComponentNameById ( id as ComponentId ) ;
41+ if ( ! name ) {
42+ // Warn if component doesn't have a name; keep numeric fallback
43+ console . warn ( `Component ID ${ id } has no registered name, serializing as number` ) ;
44+ }
45+ return name || ( id as number ) ;
46+ }
47+ case "entity-relation" : {
48+ const componentName = getComponentNameById ( detailed . componentId ) ;
49+ if ( ! componentName ) {
50+ console . warn ( `Component ID ${ detailed . componentId } in relation has no registered name` ) ;
51+ }
52+ return { component : componentName || ( detailed . componentId as number ) . toString ( ) , target : detailed . targetId ! } ;
53+ }
54+ case "component-relation" : {
55+ const componentName = getComponentNameById ( detailed . componentId ) ;
56+ const targetName = getComponentNameById ( detailed . targetId ! as ComponentId ) ;
57+ if ( ! componentName ) {
58+ console . warn ( `Component ID ${ detailed . componentId } in relation has no registered name` ) ;
59+ }
60+ if ( ! targetName ) {
61+ console . warn ( `Target component ID ${ detailed . targetId } in relation has no registered name` ) ;
62+ }
63+ return {
64+ component : componentName || ( detailed . componentId as number ) . toString ( ) ,
65+ target : targetName || ( detailed . targetId as number ) ,
66+ } ;
67+ }
68+ case "wildcard-relation" : {
69+ const componentName = getComponentNameById ( detailed . componentId ) ;
70+ if ( ! componentName ) {
71+ console . warn ( `Component ID ${ detailed . componentId } in relation has no registered name` ) ;
72+ }
73+ return { component : componentName || ( detailed . componentId as number ) . toString ( ) , target : "*" } ;
74+ }
75+ default :
76+ return id as number ;
77+ }
78+ }
79+
80+ /**
81+ * Decode a SerializedEntityId back into an internal EntityId
82+ */
83+ function decodeSerializedId ( sid : SerializedEntityId ) : EntityId < any > {
84+ if ( typeof sid === "number" ) {
85+ return sid as EntityId < any > ;
86+ }
87+ if ( typeof sid === "string" ) {
88+ const id = getComponentIdByName ( sid ) ;
89+ if ( id === undefined ) {
90+ const num = parseInt ( sid , 10 ) ;
91+ if ( ! isNaN ( num ) ) return num as EntityId < any > ;
92+ throw new Error ( `Unknown component name in snapshot: ${ sid } ` ) ;
93+ }
94+ return id ;
95+ }
96+ if ( typeof sid === "object" && sid !== null && typeof sid . component === "string" ) {
97+ let compId = getComponentIdByName ( sid . component ) ;
98+ if ( compId === undefined ) {
99+ const num = parseInt ( sid . component , 10 ) ;
100+ if ( ! isNaN ( num ) ) compId = num as ComponentId ;
101+ }
102+ if ( compId === undefined ) {
103+ throw new Error ( `Unknown component name in snapshot: ${ sid . component } ` ) ;
104+ }
105+
106+ if ( sid . target === "*" ) {
107+ return relation ( compId , "*" ) ;
108+ }
109+
110+ let targetId : EntityId < any > ;
111+ if ( typeof sid . target === "string" ) {
112+ const tid = getComponentIdByName ( sid . target ) ;
113+ if ( tid === undefined ) {
114+ const num = parseInt ( sid . target , 10 ) ;
115+ if ( ! isNaN ( num ) ) targetId = num as EntityId < any > ;
116+ else throw new Error ( `Unknown target component name in snapshot: ${ sid . target } ` ) ;
117+ } else {
118+ targetId = tid ;
119+ }
120+ } else {
121+ targetId = sid . target as EntityId < any > ;
122+ }
123+ return relation ( compId , targetId as any ) ;
124+ }
125+ throw new Error ( `Invalid ID in snapshot: ${ JSON . stringify ( sid ) } ` ) ;
126+ }
127+
27128/**
28129 * World class for ECS architecture
29130 * Manages entities and components
@@ -81,47 +182,14 @@ export class World {
81182 // Restore entities and their components
82183 if ( Array . isArray ( snapshot . entities ) ) {
83184 for ( const entry of snapshot . entities ) {
84- const entityId = entry . id as EntityId ;
185+ const entityId = decodeSerializedId ( entry . id ) ;
85186 const componentsArray : SerializedComponent [ ] = entry . components || [ ] ;
86187
87188 const componentMap = new Map < EntityId < any > , any > ( ) ;
88189 const componentTypes : EntityId < any > [ ] = [ ] ;
89190
90191 for ( const componentEntry of componentsArray ) {
91- const componentTypeRaw = componentEntry . type ;
92- let componentType : EntityId < any > ;
93-
94- if ( typeof componentTypeRaw === "number" ) {
95- componentType = componentTypeRaw as EntityId < any > ;
96- } else if ( typeof componentTypeRaw === "string" ) {
97- // Component name lookup
98- const compId = getComponentIdByName ( componentTypeRaw ) ;
99- if ( compId === undefined ) {
100- throw new Error ( `Unknown component name in snapshot: ${ componentTypeRaw } ` ) ;
101- }
102- componentType = compId ;
103- } else if (
104- typeof componentTypeRaw === "object" &&
105- componentTypeRaw !== null &&
106- typeof componentTypeRaw . component === "string"
107- ) {
108- // Component name lookup
109- const compId = getComponentIdByName ( componentTypeRaw . component ) ;
110- if ( compId === undefined ) {
111- throw new Error ( `Unknown component name in snapshot: ${ componentTypeRaw . component } ` ) ;
112- }
113- if ( typeof componentTypeRaw . target === "string" ) {
114- const targetCompId = getComponentIdByName ( componentTypeRaw . target ) ;
115- if ( targetCompId === undefined ) {
116- throw new Error ( `Unknown target component name in snapshot: ${ componentTypeRaw . target } ` ) ;
117- }
118- componentType = relation ( compId , targetCompId ) ;
119- } else {
120- componentType = relation ( compId , componentTypeRaw . target as EntityId ) ;
121- }
122- } else {
123- throw new Error ( `Invalid component type in snapshot: ${ JSON . stringify ( componentTypeRaw ) } ` ) ;
124- }
192+ const componentType = decodeSerializedId ( componentEntry . type ) ;
125193 componentMap . set ( componentType , componentEntry . value ) ;
126194 componentTypes . push ( componentType ) ;
127195 }
@@ -1264,33 +1332,11 @@ export class World {
12641332 const dumpedEntities = archetype . dump ( ) ;
12651333 for ( const { entity, components } of dumpedEntities ) {
12661334 entities . push ( {
1267- id : entity ,
1268- components : Array . from ( components . entries ( ) ) . map ( ( [ rawType , value ] ) => {
1269- const detailedType = getDetailedIdType ( rawType ) ;
1270- let type : SerializedComponent [ "type" ] = rawType ;
1271- let componentName ;
1272- switch ( detailedType . type ) {
1273- case "component" :
1274- type = getComponentNameById ( rawType as ComponentId ) || rawType ;
1275- break ;
1276- case "entity-relation" :
1277- componentName = getComponentNameById ( detailedType . componentId ) ;
1278- if ( componentName ) {
1279- type = { component : componentName , target : detailedType . targetId ! } ;
1280- }
1281- break ;
1282- case "component-relation" :
1283- componentName = getComponentNameById ( detailedType . componentId ) ;
1284- if ( componentName ) {
1285- type = {
1286- component : componentName ,
1287- target : getComponentNameById ( detailedType . targetId ! ) || detailedType . targetId ! ,
1288- } ;
1289- }
1290- break ;
1291- }
1292- return { type, value : value === MISSING_COMPONENT ? undefined : value } ;
1293- } ) ,
1335+ id : encodeEntityId ( entity ) ,
1336+ components : Array . from ( components . entries ( ) ) . map ( ( [ rawType , value ] ) => ( {
1337+ type : encodeEntityId ( rawType ) ,
1338+ value : value === MISSING_COMPONENT ? undefined : value ,
1339+ } ) ) ,
12941340 } ) ;
12951341 }
12961342 }
@@ -1310,12 +1356,12 @@ export type SerializedWorld = {
13101356} ;
13111357
13121358export type SerializedEntity = {
1313- id : number ;
1359+ id : SerializedEntityId ;
13141360 components : SerializedComponent [ ] ;
13151361} ;
13161362
13171363export type SerializedComponent = {
1318- type : number | string | { component : string ; target : number | string } ;
1364+ type : SerializedEntityId ;
13191365 value : any ;
13201366} ;
13211367
0 commit comments