Skip to content

Commit f63d2c8

Browse files
author
Jonathan Crum
committed
1.3.0 Release
1 parent 305bf78 commit f63d2c8

16 files changed

+553
-200
lines changed

pecs_framework/component.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22
from beartype.typing import TypeVar
3+
from beartype.typing import TypedDict
4+
from beartype.typing import Any
35

46
from pecs_framework._types import Bases, Namespace
57
from pecs_framework.events import EntityEvent

pecs_framework/domain.py

+86-2
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,36 @@
11
from __future__ import annotations
22
from beartype.typing import TYPE_CHECKING
33
from beartype.typing import Any
4+
from beartype.typing import TypedDict
45

56
from uuid import uuid1
67
from collections import OrderedDict
8+
import json
79

810
if TYPE_CHECKING:
11+
from pecs_framework.component import Component
912
from pecs_framework.engine import Engine
10-
from pecs_framework.query import ComponentQuery
1113
from pecs_framework.prefab import EntityTemplate
14+
from pecs_framework.query import ComponentQuery
1215

13-
from pecs_framework.entities import Entity
16+
from pathlib import Path
17+
from pecs_framework.entity import Entity
1418
from pecs_framework.query import Query
1519
from rich.console import Console
1620

1721

1822
console = Console()
1923

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+
2034

2135
class EntityRegistry:
2236

@@ -171,6 +185,9 @@ def create_uid() -> str:
171185

172186
def __init__(self, engine: Engine) -> None:
173187
self.engine = engine
188+
self.reset()
189+
190+
def reset(self) -> None:
174191
self.entities = EntityRegistry(self)
175192
self.queries: list[Query] = []
176193

@@ -202,3 +219,70 @@ def create_query(
202219
def candidate(self, entity: Entity) -> None:
203220
for query in self.queries:
204221
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+
}

pecs_framework/engine.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111

1212
from pecs_framework.component import ComponentMeta, Component
1313
from pecs_framework.domain import Domain, EntityRegistry
14-
from pecs_framework.entities import add_component
15-
from pecs_framework.entities import add_component_type
16-
from pecs_framework.entities import Entity
17-
from pecs_framework.entities import has_component
18-
from pecs_framework.entities import remove_component
14+
from pecs_framework.entity import add_component
15+
from pecs_framework.entity import add_component_type
16+
from pecs_framework.entity import Entity
17+
from pecs_framework.entity import has_component
18+
from pecs_framework.entity import remove_component
1919
from pecs_framework.prefab import PrefabBuilder
2020

2121

pecs_framework/entities/__init__.py

-25
This file was deleted.

pecs_framework/entities/entity.py

-105
This file was deleted.

pecs_framework/entities/utils.py pecs_framework/entity.py

+94-1
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
from __future__ import annotations
2+
from beartype import beartype
23
from beartype.typing import TYPE_CHECKING
34
from beartype.typing import Any
45
from beartype.typing import cast
6+
from collections import OrderedDict
57

68
if TYPE_CHECKING:
79
from pecs_framework._types import CompId
810
from pecs_framework.domain import Domain
911
from .entity import Entity
1012

1113
import json
12-
14+
from pecs_framework.events import EventData, EntityEvent
1315
from pecs_framework.utils import has_bit
1416
from pecs_framework.utils import subtract_bit
1517
from pecs_framework.utils import add_bit
@@ -18,6 +20,97 @@
1820
from pecs_framework.component import CT
1921

2022

23+
class Entity:
24+
25+
@beartype
26+
def __init__(self, domain, entity_id: str = '') -> None:
27+
self.domain = domain
28+
self.eid = entity_id
29+
self.cbits: int = 0
30+
self.components: OrderedDict[CompId, Component] = OrderedDict()
31+
self.qeligible: bool = True
32+
33+
def __getitem__(self, component: type[CT] | str) -> CT:
34+
if isinstance(component, str):
35+
_component = self.domain.engine.components.get_type(component)
36+
else:
37+
_component = component
38+
return cast(CT, get_component(self, _component))
39+
40+
def fire_event(
41+
self,
42+
event: str,
43+
data: dict[str, Any] | EventData | None = None
44+
) -> EntityEvent:
45+
"""
46+
Fire an event to all subscribed components attached to this entity.
47+
48+
The event name should be a simple, snake-cased string. Subscribed
49+
components must implement a handler method that takes in an
50+
`EntityEvent` and returns that event.
51+
52+
## Example
53+
54+
```py
55+
@dataclass
56+
class Health(Component):
57+
'''Representation of an Entity's health.'''
58+
maximum: int = 100
59+
current: int = field(init=False)
60+
61+
def __post_init__(self) -> None:
62+
self.current = self.maximum
63+
64+
def on_damage_taken(self, evt: EntityEvent) -> EntityEvent:
65+
damage = evt.data.amount
66+
self.current -= damage
67+
evt.handle()
68+
return evt
69+
```
70+
"""
71+
if data and isinstance(data, EventData):
72+
data = data.record
73+
elif data and isinstance(data, dict):
74+
data = EventData(**data).record
75+
else:
76+
data = {}
77+
evt = EntityEvent(event, data)
78+
79+
for component in self.components.values():
80+
component.handle_event(evt)
81+
if evt.prevented:
82+
return evt
83+
return evt
84+
85+
def on_component_added(self):
86+
pass
87+
88+
def on_component_removed(self):
89+
pass
90+
91+
def on_entity_destroyed(self):
92+
pass
93+
94+
def _on_component_added(self):
95+
candidacy(self.domain, self)
96+
self.on_component_added()
97+
98+
def _on_component_removed(self):
99+
candidacy(self.domain, self)
100+
self.on_component_removed()
101+
102+
def _on_entity_destroyed(self):
103+
to_delete = []
104+
for component in self.components.values():
105+
to_delete.append(component)
106+
107+
for component in to_delete:
108+
self.components[component.comp_id]._entity_id = ''
109+
del self.components[component.comp_id]
110+
111+
self.on_entity_destroyed()
112+
113+
21114
def add_component_type(
22115
entity: Entity,
23116
component: ComponentMeta,

0 commit comments

Comments
 (0)