1
1
from __future__ import annotations
2
- from beartype .typing import *
2
+ from beartype .typing import TYPE_CHECKING
3
+ from beartype .typing import Any
4
+ from beartype .typing import TypedDict
3
5
4
6
from uuid import uuid1
5
7
from collections import OrderedDict
8
+ import json
6
9
7
10
if TYPE_CHECKING :
11
+ from pecs_framework .component import Component
8
12
from pecs_framework .engine import Engine
9
- from pecs_framework .query import ComponentQuery
10
13
from pecs_framework .prefab import EntityTemplate
14
+ from pecs_framework .query import ComponentQuery
11
15
12
- from pecs_framework .entities import Entity
16
+ from pathlib import Path
17
+ from pecs_framework .entity import Entity
13
18
from pecs_framework .query import Query
14
19
from rich .console import Console
15
- from rich import inspect
16
20
17
21
18
22
console = Console ()
19
23
24
+ class ComponentDict (TypedDict ):
25
+ comp_id : str
26
+ cbit : int
27
+ data : dict [str , Any ]
28
+
29
+
30
+ class EntityDict (TypedDict ):
31
+ alias : str | None
32
+ components : list [ComponentDict ]
33
+
20
34
21
35
class EntityRegistry :
22
36
23
37
def __init__ (self , domain : Domain ) -> None :
24
38
self .domain = domain
25
- self .aliases = OrderedDict ()
39
+ self .alias_to_eid = OrderedDict ()
40
+ self .eid_to_alias = OrderedDict ()
26
41
self ._map : dict [str , Entity ] = OrderedDict ()
27
42
28
43
def __getitem__ (self , key : str ) -> Entity :
@@ -31,12 +46,15 @@ def __getitem__(self, key: str) -> Entity:
31
46
def __setitem__ (self , key : str , entity : Entity ) -> None :
32
47
self ._map [key ] = entity
33
48
49
+ def __iter__ (self ):
50
+ return iter (self ._map .values ())
51
+
34
52
def keys (self ):
35
53
return self ._map .keys ()
36
54
37
55
def values (self ):
38
56
return self ._map .values ()
39
-
57
+
40
58
def get_entity_id (self , entity_or_alias : Entity | str ) -> str :
41
59
"""
42
60
Get an Entity's identifier either by passing the entity itself or its
@@ -52,12 +70,17 @@ def get_entity_id(self, entity_or_alias: Entity | str) -> str:
52
70
The Entity's unique identifier
53
71
"""
54
72
if isinstance (entity_or_alias , str ):
55
- id_ : str = self .aliases .get (entity_or_alias , '' )
73
+ id_ : str = self .alias_to_eid .get (entity_or_alias , '' )
56
74
else :
57
75
id_ : str = entity_or_alias .eid
58
76
return id_
59
-
60
- def create (self , alias : str | None = None ) -> Entity :
77
+
78
+ def create (
79
+ self ,
80
+ alias : str | None = None ,
81
+ * ,
82
+ entity_id : str | None = None ,
83
+ ) -> Entity :
61
84
"""
62
85
Create a new Entity in the engine's Entity registry.
63
86
@@ -70,13 +93,17 @@ def create(self, alias: str | None = None) -> Entity:
70
93
-------
71
94
The newly created Entity instance
72
95
"""
73
- entity = Entity (self .domain , self .domain .create_uid ())
96
+ entity = Entity (
97
+ self .domain ,
98
+ entity_id if entity_id else self .domain .create_uid (),
99
+ )
74
100
self ._map [entity .eid ] = entity
75
-
101
+
76
102
if alias :
77
- if alias in self .aliases . keys () :
103
+ if alias in self .alias_to_eid :
78
104
raise KeyError (f"Entity already exists with alias { alias } " )
79
- self .aliases [alias ] = entity .eid
105
+ self .alias_to_eid [alias ] = entity .eid
106
+ self .eid_to_alias [entity .eid ] = alias
80
107
81
108
return entity
82
109
@@ -108,7 +135,7 @@ def create_from_prefab(
108
135
# props from the process above.
109
136
for name , props in comp_props .items ():
110
137
self .domain .engine .components .attach (entity , name , props )
111
-
138
+
112
139
return entity
113
140
114
141
def get_by_alias (self , alias : str ) -> Entity :
@@ -124,7 +151,7 @@ def get_by_alias(self, alias: str) -> Entity:
124
151
-------
125
152
The Entity corresponding to the passed alias.
126
153
"""
127
- entity_id : str = self .aliases .get (alias , '' )
154
+ entity_id : str = self .alias_to_eid .get (alias , '' )
128
155
if entity_id :
129
156
return self .get_by_id (entity_id )
130
157
else :
@@ -134,25 +161,35 @@ def get_by_alias(self, alias: str) -> Entity:
134
161
def get_by_id (self , entity_id : str ) -> Entity :
135
162
return self ._map [entity_id ]
136
163
164
+ def get_alias_for_entity (self , entity_or_eid : Entity | str ) -> str | None :
165
+ if isinstance (entity_or_eid , str ):
166
+ return self .eid_to_alias .get (entity_or_eid , None )
167
+ return self .eid_to_alias .get (entity_or_eid .eid , None )
168
+
137
169
def remove_entity_by_id (self , entity_id : str ) -> None :
138
170
self ._map [entity_id ]._on_entity_destroyed ()
139
171
del self ._map [entity_id ]
140
172
141
173
def remove_entity_by_alias (self , alias : str ) -> None :
142
174
entity_id = self .get_entity_id (alias )
175
+ if alias in self .alias_to_eid :
176
+ del self .alias_to_eid [alias ]
143
177
self .remove_entity_by_id (entity_id )
144
178
145
179
146
180
class Domain :
147
-
181
+
148
182
@staticmethod
149
183
def create_uid () -> str :
150
184
return str (uuid1 ())
151
-
185
+
152
186
def __init__ (self , engine : Engine ) -> None :
153
187
self .engine = engine
188
+ self .reset ()
189
+
190
+ def reset (self ) -> None :
154
191
self .entities = EntityRegistry (self )
155
- self .queries : List [Query ] = []
192
+ self .queries : list [Query ] = []
156
193
157
194
def destroy_entity (self , entity : Entity | str ) -> None :
158
195
if isinstance (entity , str ):
@@ -161,18 +198,91 @@ def destroy_entity(self, entity: Entity | str) -> None:
161
198
else :
162
199
self .entities .remove_entity_by_alias (entity )
163
200
else :
201
+ # if entity.eid in self.entities.aliases.values():
202
+ for k , v in self .entities .alias_to_eid .items ():
203
+ if v == entity .eid :
204
+ alias = k
205
+ self .entities .remove_entity_by_alias (alias )
206
+ return
164
207
self .entities .remove_entity_by_id (entity .eid )
165
208
166
209
def create_query (
167
210
self ,
168
211
all_of : ComponentQuery | None = None ,
169
212
any_of : ComponentQuery | None = None ,
170
213
none_of : ComponentQuery | None = None ,
171
- ) -> Query :
214
+ ) -> Query :
172
215
query = Query (self , all_of , any_of , none_of )
173
216
self .queries .append (query )
174
217
return query
175
218
176
219
def candidate (self , entity : Entity ) -> None :
177
220
for query in self .queries :
178
221
query .candidate (entity )
222
+
223
+ def save (self , directory : Path , filename : str ) -> None :
224
+ output : dict [str , EntityDict ] = {}
225
+
226
+ for entity in self .entities :
227
+ output [entity .eid ] = {
228
+ "alias" : self .entities .get_alias_for_entity (entity .eid ),
229
+ "components" : [],
230
+ }
231
+
232
+ for component in entity .components .values ():
233
+ component_data = serialize_component (component )
234
+ output [entity .eid ]["components" ].append (component_data )
235
+
236
+ write_to_file (directory , filename , output )
237
+
238
+ def load (self , directory : Path , filename : str ) -> None :
239
+ if loaded_data := load_from_file (directory , filename ):
240
+ for eid , entity_data in loaded_data .items ():
241
+ alias = entity_data ["alias" ]
242
+ component_data = entity_data ["components" ]
243
+
244
+ entity = self .entities .create (alias = alias , entity_id = eid )
245
+
246
+ for component_datum in component_data :
247
+ self .engine .components .attach (
248
+ entity ,
249
+ component_datum ["comp_id" ],
250
+ {k : v for k , v in component_datum ["data" ].items ()},
251
+ )
252
+
253
+
254
+ def write_to_file (
255
+ directory : Path ,
256
+ filename : str ,
257
+ data_dict : dict [str , EntityDict ],
258
+ ) -> None :
259
+ if not filename .endswith (".json" ):
260
+ filename = filename + ".json"
261
+ with open (Path (directory , filename ), "+w" ) as file :
262
+ file .write (json .dumps (data_dict , indent = 4 ))
263
+
264
+
265
+ def load_from_file (directory : Path , filename : str ) -> dict [str , EntityDict ]:
266
+ if not (filename .endswith (".json" )):
267
+ filename = filename + ".json"
268
+ with open (Path (directory , filename ), "+r" ) as file :
269
+ return json .loads (file .read ())
270
+
271
+
272
+ def serialize_component (component : Component ) -> ComponentDict :
273
+ """
274
+ Serialization should provide all of the necessary information needed to
275
+ rebuild what is set up in `setup_ecs` anew, with the state that the
276
+ components were in when they were serialized.
277
+ """
278
+ comp_id = component .__class__ .comp_id
279
+ cbit = component .__class__ .cbit
280
+ instance_data = vars (component )
281
+
282
+ del instance_data ["_entity_id" ]
283
+
284
+ return {
285
+ "comp_id" : comp_id ,
286
+ "cbit" : cbit ,
287
+ "data" : instance_data ,
288
+ }
0 commit comments