microecs 0.2.3__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: microecs
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: MicroECS: Minimal Entity Component System (ECS) in python and numpy
5
5
  Home-page: https://gitlab.com/meehai/microecs
6
6
  License: MIT
@@ -20,54 +20,55 @@ Usage:
20
20
  - Sandbox: `python main.py`
21
21
  - Tests: `pytest test/`
22
22
 
23
- There are only 5 primitives (bottom up): `Component`, `Pool`, `QueryResult`, `World` and `System`.
23
+ There are only 4 primitives (bottom up): `Component`, `Pool`, `QueryResult`, `World`:
24
24
 
25
- - `Component` is a simple python dataclass holding only data. All entries must be numpy arrays with metadata fields: shape and dtype. We support 5 dtypes only: `int32`, `float32`, `bool`, `str` and `object`.
25
+ - `Component` is a simple python dataclass holding only data. All entries must be numpy arrays with metadata fields: shape and dtype. We support 5 dtypes only: `int32`, `float32`, `bool`, `str` and `object`. A component with no fields is a valid **tag** for querying (e.g. `class Frozen(Component): pass`).
26
26
  - `Pool` is a simple 'archetype' dynamic array, holding entities of the same type (same set of components). Usses `Components` metadata to construct contiguous arrays for all entities of the same type.
27
27
  - `QueryResult` is a list of pools that match some query on all the entities of the `World`. It acts as a contiguous numpy-like container that implements numpy's `__array_function__` and `__array_ufunc__`. For all intents and purposes it should feel like a `(N, ...)` view over all the entities. If you need a numpy array (not all ops are supported, for e.g. indexing on the first axis), use `QueryResult.numpy()`. It also exposes `entity_ids`: a flat `(N,)` array of the matched entities' ids, in the same pool-by-pool order as the fields, so you can `zip(qr.entity_ids, qr.position)` or feed an id back to `world.get_entity` / `world.remove_entity`.
28
- - `World` is a manager of `Pools` and has an overview of all the entities in the scene. It also manages the migration of entities from one pool to the other.
29
- - `System` is an abstract class that queries the `World` for a subset of `Pools` matching some components. It updates the entities in these pools given some logic (e.g. collisions, motion physics or simply calls the drawing functions). They are merely a convention, not tied to `World` per se.
28
+ - `World` is a manager of `Pools` and has an overview of all the entities in the scene. It also manages the migration of entities from one pool to the other. A `World` can also require extra metadata keys on every field via `World(extra_field_metadata=["serializable"])`, to enforce component-level behavior such as field serialization.
30
29
 
31
30
  Few relevant concepts:
32
31
 
33
32
  - `Pool` operates on array indices, while `World` operates on entity IDs (also integers). This allows seamless movement between pools while the high-level systems still working as intended.
34
33
  - All mutable operations on `World` are lazy. These are: `add_entity`, `remove_entity`, `add_component`, `remove_component`. They are added to a command buffer which is only executed when calling `world.update()`.
34
+ - `Systems` are a convention, they are not part of this library. They can be defined at application level and act as hooks or callbacks. The `World` object doesn't need to know more than entities and components.
35
35
 
36
36
  Super simplified main loop structure:
37
37
 
38
38
  ```python
39
+ from typing import Callable
39
40
  import numpy as np
40
41
  import raylib as rl
41
- from microecs import World, Component, TickSystem
42
+ from microecs import World, Component
42
43
 
43
44
  # components
44
45
  class HasPosition(Component):
45
- position: np.ndarray = field(metadata={"shape": (2, ), "dtype": "float32"})
46
+ position: np.ndarray = field(metadata={"shape": (2, ), "dtype": "float32"}) # 'shape' + 'dtype' are always required
46
47
  class HasVelocity(Component):
47
48
  velocity: np.ndarray = field(metadata={"shape": (2, ), "dtype": "float32"})
48
49
  class HasColor(Component):
49
50
  color: np.ndarray = field(metadata={"shape": (4, ), "dtype": "int32"})
50
51
 
51
- # systems
52
- class RenderSystem(TickSystem):
53
- def on_tick(self, world: World): # must override
54
- query_result = world.query_and((HasPosition, HasColor)) # contiguous-like view of all entities matching
52
+ # systems: Note they are a convention!
53
+ class RenderSystem:
54
+ def __call__(self, world: World):
55
+ query_result = world.query(HasPosition, HasColor, exclude=[]) # contiguous-like view of all entities matching
55
56
  for position, color in zip(query_result.position, query_result.color): # draw each entity
56
57
  DrawEntity(position, color)
57
58
 
58
- class MotionSystem(TickSystem)
59
- def on_tick(self, world: World): # must override
60
- qr = world.query_and((HasPosition, HasVelocity))
59
+ class MotionSystem:
60
+ def __call__(self, world: World):
61
+ qr = world.query(HasPosition, HasVelocity) # 'exclude' is optional
61
62
  qr.position[:] = qr.position + qr.velocity * DT # writes back to all the underlying pools using numpy's rules
62
63
  # Alternative for per-pool update. Less ergonomic, but maybe faster in extreme cases as it avoids the _Field obj
63
64
  for pool in qr.pool_list:
64
65
  pool.position[:] = pool.position + pool.velocity * DT
65
66
 
66
67
  def main():
67
- render_system = RenderSystem()
68
- update_systems: list[TickSystem] = [MotionSystem()]
68
+ render_system: list[Callable] = RenderSystem()
69
+ update_systems: list[Callable] = [MotionSystem()]
69
70
 
70
- world = World(components=[HasPosition, HasColor, HasVelocity])
71
+ world = World(components=[HasPosition, HasColor, HasVelocity], extra_field_metadata=None)
71
72
  for _ in range(n_objects):
72
73
  # NOTE: world.{add/remove}_{entity/component} are lazy. They take effect after the first world.update() call.
73
74
  world.add_entity(components=(HasPosition, HasVelocity, HasColor), # tuple of components (types)
@@ -78,11 +79,11 @@ def main():
78
79
  while not rl.WindowShouldClose():
79
80
  world.update() # must be called at each tick so the lazy methods are processed and entities are updated
80
81
  # update stuff...
81
- _ = [system.on_tick(world=world) for system in update_systems]
82
+ _ = [system(world=world) for system in update_systems]
82
83
  # draw stuff, e.g. using raylib
83
84
  rl.BeginDrawing()
84
85
  rl.ClearBackground(rl.RAYWHITE)
85
86
  rl.DrawFPS(rl.GetScreenWidth() - 100, 0)
86
- render_system.on_tick(world=world)
87
+ render_system(world=world)
87
88
  rl.EndDrawing()
88
89
  ```
@@ -8,54 +8,55 @@ Usage:
8
8
  - Sandbox: `python main.py`
9
9
  - Tests: `pytest test/`
10
10
 
11
- There are only 5 primitives (bottom up): `Component`, `Pool`, `QueryResult`, `World` and `System`.
11
+ There are only 4 primitives (bottom up): `Component`, `Pool`, `QueryResult`, `World`:
12
12
 
13
- - `Component` is a simple python dataclass holding only data. All entries must be numpy arrays with metadata fields: shape and dtype. We support 5 dtypes only: `int32`, `float32`, `bool`, `str` and `object`.
13
+ - `Component` is a simple python dataclass holding only data. All entries must be numpy arrays with metadata fields: shape and dtype. We support 5 dtypes only: `int32`, `float32`, `bool`, `str` and `object`. A component with no fields is a valid **tag** for querying (e.g. `class Frozen(Component): pass`).
14
14
  - `Pool` is a simple 'archetype' dynamic array, holding entities of the same type (same set of components). Usses `Components` metadata to construct contiguous arrays for all entities of the same type.
15
15
  - `QueryResult` is a list of pools that match some query on all the entities of the `World`. It acts as a contiguous numpy-like container that implements numpy's `__array_function__` and `__array_ufunc__`. For all intents and purposes it should feel like a `(N, ...)` view over all the entities. If you need a numpy array (not all ops are supported, for e.g. indexing on the first axis), use `QueryResult.numpy()`. It also exposes `entity_ids`: a flat `(N,)` array of the matched entities' ids, in the same pool-by-pool order as the fields, so you can `zip(qr.entity_ids, qr.position)` or feed an id back to `world.get_entity` / `world.remove_entity`.
16
- - `World` is a manager of `Pools` and has an overview of all the entities in the scene. It also manages the migration of entities from one pool to the other.
17
- - `System` is an abstract class that queries the `World` for a subset of `Pools` matching some components. It updates the entities in these pools given some logic (e.g. collisions, motion physics or simply calls the drawing functions). They are merely a convention, not tied to `World` per se.
16
+ - `World` is a manager of `Pools` and has an overview of all the entities in the scene. It also manages the migration of entities from one pool to the other. A `World` can also require extra metadata keys on every field via `World(extra_field_metadata=["serializable"])`, to enforce component-level behavior such as field serialization.
18
17
 
19
18
  Few relevant concepts:
20
19
 
21
20
  - `Pool` operates on array indices, while `World` operates on entity IDs (also integers). This allows seamless movement between pools while the high-level systems still working as intended.
22
21
  - All mutable operations on `World` are lazy. These are: `add_entity`, `remove_entity`, `add_component`, `remove_component`. They are added to a command buffer which is only executed when calling `world.update()`.
22
+ - `Systems` are a convention, they are not part of this library. They can be defined at application level and act as hooks or callbacks. The `World` object doesn't need to know more than entities and components.
23
23
 
24
24
  Super simplified main loop structure:
25
25
 
26
26
  ```python
27
+ from typing import Callable
27
28
  import numpy as np
28
29
  import raylib as rl
29
- from microecs import World, Component, TickSystem
30
+ from microecs import World, Component
30
31
 
31
32
  # components
32
33
  class HasPosition(Component):
33
- position: np.ndarray = field(metadata={"shape": (2, ), "dtype": "float32"})
34
+ position: np.ndarray = field(metadata={"shape": (2, ), "dtype": "float32"}) # 'shape' + 'dtype' are always required
34
35
  class HasVelocity(Component):
35
36
  velocity: np.ndarray = field(metadata={"shape": (2, ), "dtype": "float32"})
36
37
  class HasColor(Component):
37
38
  color: np.ndarray = field(metadata={"shape": (4, ), "dtype": "int32"})
38
39
 
39
- # systems
40
- class RenderSystem(TickSystem):
41
- def on_tick(self, world: World): # must override
42
- query_result = world.query_and((HasPosition, HasColor)) # contiguous-like view of all entities matching
40
+ # systems: Note they are a convention!
41
+ class RenderSystem:
42
+ def __call__(self, world: World):
43
+ query_result = world.query(HasPosition, HasColor, exclude=[]) # contiguous-like view of all entities matching
43
44
  for position, color in zip(query_result.position, query_result.color): # draw each entity
44
45
  DrawEntity(position, color)
45
46
 
46
- class MotionSystem(TickSystem)
47
- def on_tick(self, world: World): # must override
48
- qr = world.query_and((HasPosition, HasVelocity))
47
+ class MotionSystem:
48
+ def __call__(self, world: World):
49
+ qr = world.query(HasPosition, HasVelocity) # 'exclude' is optional
49
50
  qr.position[:] = qr.position + qr.velocity * DT # writes back to all the underlying pools using numpy's rules
50
51
  # Alternative for per-pool update. Less ergonomic, but maybe faster in extreme cases as it avoids the _Field obj
51
52
  for pool in qr.pool_list:
52
53
  pool.position[:] = pool.position + pool.velocity * DT
53
54
 
54
55
  def main():
55
- render_system = RenderSystem()
56
- update_systems: list[TickSystem] = [MotionSystem()]
56
+ render_system: list[Callable] = RenderSystem()
57
+ update_systems: list[Callable] = [MotionSystem()]
57
58
 
58
- world = World(components=[HasPosition, HasColor, HasVelocity])
59
+ world = World(components=[HasPosition, HasColor, HasVelocity], extra_field_metadata=None)
59
60
  for _ in range(n_objects):
60
61
  # NOTE: world.{add/remove}_{entity/component} are lazy. They take effect after the first world.update() call.
61
62
  world.add_entity(components=(HasPosition, HasVelocity, HasColor), # tuple of components (types)
@@ -66,11 +67,11 @@ def main():
66
67
  while not rl.WindowShouldClose():
67
68
  world.update() # must be called at each tick so the lazy methods are processed and entities are updated
68
69
  # update stuff...
69
- _ = [system.on_tick(world=world) for system in update_systems]
70
+ _ = [system(world=world) for system in update_systems]
70
71
  # draw stuff, e.g. using raylib
71
72
  rl.BeginDrawing()
72
73
  rl.ClearBackground(rl.RAYWHITE)
73
74
  rl.DrawFPS(rl.GetScreenWidth() - 100, 0)
74
- render_system.on_tick(world=world)
75
+ render_system(world=world)
75
76
  rl.EndDrawing()
76
77
  ```
@@ -4,6 +4,5 @@ from .component import Component
4
4
  from .pool import Pool
5
5
  from .query_result import QueryResult
6
6
  from .world import World
7
- from .system import TickSystem
8
7
 
9
- __all__ = ["Component", "Pool", "QueryResult", "World", "TickSystem"]
8
+ __all__ = ["Component", "Pool", "QueryResult", "World"]
@@ -9,8 +9,14 @@ from .component import ComponentType
9
9
  from .query_result import QueryResult
10
10
 
11
11
  class World:
12
- """Generic container for pools of components. Newly added components are assigned a unique id and go in a pool"""
13
- def __init__(self, components: list[ComponentType]):
12
+ """
13
+ Generic container for pools of components. Newly added components are assigned a unique id and go in a pool
14
+ Parameters
15
+ - components The list of components that the world accepts
16
+ - extra_field_metadata The list of required extra metadata for each field besides shape and dtype.
17
+ """
18
+ def __init__(self, components: list[ComponentType], extra_field_metadata: list[str] | None = None):
19
+ self.extra_field_metadata = extra_field_metadata or []
14
20
  self._check_components(components)
15
21
  self.pools: dict[PoolKey, Pool] = {}
16
22
  self.pool_to_components: dict[Pool, list[ComponentType]] = {}
@@ -30,7 +36,7 @@ class World:
30
36
  self._live_ids: set[EntityId] = set() # 'eager' mode ids so e.g. calling remove_entity twice before update fails
31
37
  # command buffer management. {add/remove}_{entity/component} are lazy. Taken into account after update().
32
38
  self._command_buffer: list[Callable] = []
33
- self._cache: dict[PoolKey, QueryResult] = {}
39
+ self._cache: dict[tuple[PoolKey, PoolKey], QueryResult] = {} # include+exclude key
34
40
  logger.debug(f"Created scene with components: {self.component_names}")
35
41
 
36
42
  # public api
@@ -79,20 +85,34 @@ class World:
79
85
  assert component in self.component_types, f"Component '{component}' not in {self.component_types}"
80
86
  self._command_buffer.append(partial(self._do_remove_component, entity_id=entity_id, component=component))
81
87
 
82
- def query_and(self, component_types: list[ComponentType]) -> QueryResult:
83
- """returns A QueryResult object with the entities that have all the requested components (entity ids too)."""
88
+ def query(self, *include: ComponentType, exclude: list[ComponentType] | None = None) -> QueryResult:
89
+ """
90
+ Queries the world for entities that match the include set of components.
91
+ Syntax: `world.query(A, B, exclude=[C, D])` is, in logic form, a chain of 'ands': `A & B & ~C & ~D`.
92
+ Return: A `QueryResult` object with the entities that have all the requested components. Has `EntityIds` too.
93
+ """
94
+
84
95
  # Note: we can cache the queries. The only time it can get invalidated (via public API) is at update().
85
- if (key := self._make_key(component_types)) in self._cache:
96
+ include_key = self._make_key(include)
97
+ exclude_key = self._make_key(exclude or [])
98
+ if (key := (include_key, exclude_key)) in self._cache:
86
99
  return self._cache[key]
87
100
 
101
+ # archetype_key = (1 0 0 1 1) &
102
+ # key = (1 0 0 0 1)
103
+ # -> (1 0 0 0 1) OK
104
+ # but
105
+ # archetype_key = (1 0 0 1 1) &
106
+ # key = (1 0 1 0 0)
107
+ # -> (1 0 0 0 0) NOT OK
88
108
  res = []
89
109
  for archetype_key, archetype_pool in self.pools.items():
90
- if (archetype_key & key) == key: # key is subset of archetype_key
110
+ if (archetype_key & include_key) == include_key and (archetype_key & exclude_key) == 0:
91
111
  res.append(archetype_pool)
92
112
 
93
- field_names = sum([self.component_to_field_names[c] for c in component_types], [])
94
- field_shapes = dict(zip(field_names, sum([self.component_to_shapes[c] for c in component_types], [])))
95
- field_dtypes = dict(zip(field_names, sum([self.component_to_dtypes[c] for c in component_types], [])))
113
+ field_names = sum([self.component_to_field_names[c] for c in include], [])
114
+ field_shapes = dict(zip(field_names, sum([self.component_to_shapes[c] for c in include], [])))
115
+ field_dtypes = dict(zip(field_names, sum([self.component_to_dtypes[c] for c in include], [])))
96
116
  entity_ids = np.array(sum((self._pool_ids[p] for p in res), []), dtype="int64")
97
117
 
98
118
  self._cache[key] = QueryResult(res, field_shapes=field_shapes, field_dtypes=field_dtypes, entity_ids=entity_ids)
@@ -178,7 +198,7 @@ class World:
178
198
  hints = get_type_hints(component) # make it work with from __future__ import annotations
179
199
  for field_name, _field in component.__dataclass_fields__.items():
180
200
  assert hints[field_name] is np.ndarray, f"Field '{field_name}' of '{component=}' not an array: {_field}"
181
- assert _field.metadata.keys() == {"shape", "dtype"}, _field.metadata
201
+ assert _field.metadata.keys() == {"shape", "dtype", *self.extra_field_metadata}, _field.metadata
182
202
  assert isinstance(_field.metadata["shape"], tuple), _field.metadata["shape"]
183
203
  assert isinstance(fmd := _field.metadata["dtype"], str) and fmd in _dtypes, f"{fmd} not in {_dtypes}"
184
204
  assert field_name not in _query_result_reserved_names, f"Field '{field_name}' in {_qres}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: microecs
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: MicroECS: Minimal Entity Component System (ECS) in python and numpy
5
5
  Home-page: https://gitlab.com/meehai/microecs
6
6
  License: MIT
@@ -20,54 +20,55 @@ Usage:
20
20
  - Sandbox: `python main.py`
21
21
  - Tests: `pytest test/`
22
22
 
23
- There are only 5 primitives (bottom up): `Component`, `Pool`, `QueryResult`, `World` and `System`.
23
+ There are only 4 primitives (bottom up): `Component`, `Pool`, `QueryResult`, `World`:
24
24
 
25
- - `Component` is a simple python dataclass holding only data. All entries must be numpy arrays with metadata fields: shape and dtype. We support 5 dtypes only: `int32`, `float32`, `bool`, `str` and `object`.
25
+ - `Component` is a simple python dataclass holding only data. All entries must be numpy arrays with metadata fields: shape and dtype. We support 5 dtypes only: `int32`, `float32`, `bool`, `str` and `object`. A component with no fields is a valid **tag** for querying (e.g. `class Frozen(Component): pass`).
26
26
  - `Pool` is a simple 'archetype' dynamic array, holding entities of the same type (same set of components). Usses `Components` metadata to construct contiguous arrays for all entities of the same type.
27
27
  - `QueryResult` is a list of pools that match some query on all the entities of the `World`. It acts as a contiguous numpy-like container that implements numpy's `__array_function__` and `__array_ufunc__`. For all intents and purposes it should feel like a `(N, ...)` view over all the entities. If you need a numpy array (not all ops are supported, for e.g. indexing on the first axis), use `QueryResult.numpy()`. It also exposes `entity_ids`: a flat `(N,)` array of the matched entities' ids, in the same pool-by-pool order as the fields, so you can `zip(qr.entity_ids, qr.position)` or feed an id back to `world.get_entity` / `world.remove_entity`.
28
- - `World` is a manager of `Pools` and has an overview of all the entities in the scene. It also manages the migration of entities from one pool to the other.
29
- - `System` is an abstract class that queries the `World` for a subset of `Pools` matching some components. It updates the entities in these pools given some logic (e.g. collisions, motion physics or simply calls the drawing functions). They are merely a convention, not tied to `World` per se.
28
+ - `World` is a manager of `Pools` and has an overview of all the entities in the scene. It also manages the migration of entities from one pool to the other. A `World` can also require extra metadata keys on every field via `World(extra_field_metadata=["serializable"])`, to enforce component-level behavior such as field serialization.
30
29
 
31
30
  Few relevant concepts:
32
31
 
33
32
  - `Pool` operates on array indices, while `World` operates on entity IDs (also integers). This allows seamless movement between pools while the high-level systems still working as intended.
34
33
  - All mutable operations on `World` are lazy. These are: `add_entity`, `remove_entity`, `add_component`, `remove_component`. They are added to a command buffer which is only executed when calling `world.update()`.
34
+ - `Systems` are a convention, they are not part of this library. They can be defined at application level and act as hooks or callbacks. The `World` object doesn't need to know more than entities and components.
35
35
 
36
36
  Super simplified main loop structure:
37
37
 
38
38
  ```python
39
+ from typing import Callable
39
40
  import numpy as np
40
41
  import raylib as rl
41
- from microecs import World, Component, TickSystem
42
+ from microecs import World, Component
42
43
 
43
44
  # components
44
45
  class HasPosition(Component):
45
- position: np.ndarray = field(metadata={"shape": (2, ), "dtype": "float32"})
46
+ position: np.ndarray = field(metadata={"shape": (2, ), "dtype": "float32"}) # 'shape' + 'dtype' are always required
46
47
  class HasVelocity(Component):
47
48
  velocity: np.ndarray = field(metadata={"shape": (2, ), "dtype": "float32"})
48
49
  class HasColor(Component):
49
50
  color: np.ndarray = field(metadata={"shape": (4, ), "dtype": "int32"})
50
51
 
51
- # systems
52
- class RenderSystem(TickSystem):
53
- def on_tick(self, world: World): # must override
54
- query_result = world.query_and((HasPosition, HasColor)) # contiguous-like view of all entities matching
52
+ # systems: Note they are a convention!
53
+ class RenderSystem:
54
+ def __call__(self, world: World):
55
+ query_result = world.query(HasPosition, HasColor, exclude=[]) # contiguous-like view of all entities matching
55
56
  for position, color in zip(query_result.position, query_result.color): # draw each entity
56
57
  DrawEntity(position, color)
57
58
 
58
- class MotionSystem(TickSystem)
59
- def on_tick(self, world: World): # must override
60
- qr = world.query_and((HasPosition, HasVelocity))
59
+ class MotionSystem:
60
+ def __call__(self, world: World):
61
+ qr = world.query(HasPosition, HasVelocity) # 'exclude' is optional
61
62
  qr.position[:] = qr.position + qr.velocity * DT # writes back to all the underlying pools using numpy's rules
62
63
  # Alternative for per-pool update. Less ergonomic, but maybe faster in extreme cases as it avoids the _Field obj
63
64
  for pool in qr.pool_list:
64
65
  pool.position[:] = pool.position + pool.velocity * DT
65
66
 
66
67
  def main():
67
- render_system = RenderSystem()
68
- update_systems: list[TickSystem] = [MotionSystem()]
68
+ render_system: list[Callable] = RenderSystem()
69
+ update_systems: list[Callable] = [MotionSystem()]
69
70
 
70
- world = World(components=[HasPosition, HasColor, HasVelocity])
71
+ world = World(components=[HasPosition, HasColor, HasVelocity], extra_field_metadata=None)
71
72
  for _ in range(n_objects):
72
73
  # NOTE: world.{add/remove}_{entity/component} are lazy. They take effect after the first world.update() call.
73
74
  world.add_entity(components=(HasPosition, HasVelocity, HasColor), # tuple of components (types)
@@ -78,11 +79,11 @@ def main():
78
79
  while not rl.WindowShouldClose():
79
80
  world.update() # must be called at each tick so the lazy methods are processed and entities are updated
80
81
  # update stuff...
81
- _ = [system.on_tick(world=world) for system in update_systems]
82
+ _ = [system(world=world) for system in update_systems]
82
83
  # draw stuff, e.g. using raylib
83
84
  rl.BeginDrawing()
84
85
  rl.ClearBackground(rl.RAYWHITE)
85
86
  rl.DrawFPS(rl.GetScreenWidth() - 100, 0)
86
- render_system.on_tick(world=world)
87
+ render_system(world=world)
87
88
  rl.EndDrawing()
88
89
  ```
@@ -5,7 +5,6 @@ microecs/__init__.py
5
5
  microecs/component.py
6
6
  microecs/pool.py
7
7
  microecs/query_result.py
8
- microecs/system.py
9
8
  microecs/utils.py
10
9
  microecs/world.py
11
10
  microecs.egg-info/PKG-INFO
@@ -3,7 +3,7 @@ from pathlib import Path
3
3
  from setuptools import setup, find_packages
4
4
 
5
5
  NAME = "microecs"
6
- VERSION = "0.2.3"
6
+ VERSION = "0.3.0"
7
7
  DESCRIPTION = "MicroECS: Minimal Entity Component System (ECS) in python and numpy"
8
8
  URL = "https://gitlab.com/meehai/microecs"
9
9
 
@@ -1,10 +0,0 @@
1
- """systems.py - Interfaces for various types of systems in ECS"""
2
- from abc import ABC, abstractmethod
3
-
4
- from .world import World
5
-
6
- class TickSystem(ABC):
7
- """Generic tick-level system. Called inside the hot main engine loop on every tick"""
8
- @abstractmethod
9
- def on_tick(self, world: World):
10
- """callback called on each tick"""
File without changes
File without changes
File without changes
File without changes
File without changes