tinyecs 0.3.4__tar.gz → 0.3.6__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.
Files changed (25) hide show
  1. tinyecs-0.3.6/PKG-INFO +90 -0
  2. {tinyecs-0.3.4 → tinyecs-0.3.6}/pyproject.toml +2 -2
  3. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs/__init__.py +72 -35
  4. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs/tutorial.py +5 -0
  5. tinyecs-0.3.6/src/tinyecs.egg-info/PKG-INFO +90 -0
  6. {tinyecs-0.3.4 → tinyecs-0.3.6}/tests/test_tinyecs.py +13 -0
  7. tinyecs-0.3.4/PKG-INFO +0 -17
  8. tinyecs-0.3.4/src/tinyecs.egg-info/PKG-INFO +0 -17
  9. {tinyecs-0.3.4 → tinyecs-0.3.6}/LICENSE +0 -0
  10. {tinyecs-0.3.4 → tinyecs-0.3.6}/README.rst +0 -0
  11. {tinyecs-0.3.4 → tinyecs-0.3.6}/setup.cfg +0 -0
  12. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs/components.py +0 -0
  13. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs/compsys.py +0 -0
  14. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs/demo.py +0 -0
  15. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs/demos/__init__.py +0 -0
  16. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs/demos/background.py +0 -0
  17. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs/demos/bouncing_sprites.py +0 -0
  18. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs/demos/example.py +0 -0
  19. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs/demos/homing-missiles.py +0 -0
  20. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs/demos/marquee.py +0 -0
  21. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs.egg-info/SOURCES.txt +0 -0
  22. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs.egg-info/dependency_links.txt +0 -0
  23. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs.egg-info/entry_points.txt +0 -0
  24. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs.egg-info/requires.txt +0 -0
  25. {tinyecs-0.3.4 → tinyecs-0.3.6}/src/tinyecs.egg-info/top_level.txt +0 -0
tinyecs-0.3.6/PKG-INFO ADDED
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.4
2
+ Name: tinyecs
3
+ Version: 0.3.6
4
+ Summary: The teeniest, tiniest ECS system
5
+ Author-email: Michael Lamertz <michael.lamertz@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: homepage, https://github.com/dickerdackel/tinyecs
8
+ Project-URL: bugtracker, https://github.com/DickerDackel/tinyecs/issues
9
+ Project-URL: docs, https://tinyecs.readthedocs.io/
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Games/Entertainment
13
+ Classifier: Topic :: Software Development :: Libraries :: pygame
14
+ Description-Content-Type: text/x-rst
15
+ License-File: LICENSE
16
+ Requires-Dist: pgcooldown
17
+ Dynamic: license-file
18
+
19
+ Introduction
20
+ ############
21
+
22
+ ECS stands for Entity Component System, and it is a programming paradigm that
23
+ differs from the well known OO.
24
+
25
+ During my research I stumbled over `this article`_ and after reading part 2 and
26
+ 3, I decided to implement an ECS myself, well aware that `esper` is a solid
27
+ and long existing implementation, but I wanted to see how to implement it
28
+ myself.
29
+
30
+ .. _this article: https://web.archive.org/web/20250408195932/https://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/
31
+
32
+
33
+ I'm not trying to sell you an ECS by explaining the problems of multiple
34
+ inheritance in game programming. There are articles out there that do this in
35
+ much detail. If you're here, you're already interested in the concept, so
36
+ the tutorial might be a good starting point. It will create a small demo with
37
+ bouncing sprites using pygame-ce. But tinyecs is platform agnostic, you can
38
+ use it with whatever library you like.
39
+
40
+
41
+ Installation
42
+ ============
43
+
44
+ tinyecs is available on pip and can be installed by::
45
+
46
+ pip install tinyecs
47
+
48
+ The project is maintained and hosted on `github`_, where you also can find the
49
+ wheels for a local install, or install it directly from the cloned repo.
50
+
51
+ .. _github: https://github.com/dickerdackel/tinyecs
52
+
53
+ .. code-block::
54
+
55
+ git clone https://github.com/dickerdackel/tinyecs
56
+ cd tinyecs
57
+ python3 -m venv --prompt tinyecs .venv
58
+ .venv/bin/activate
59
+ # or .venv/Scripts/activate.bat on windows
60
+ pip install .
61
+
62
+ Documentation
63
+ =============
64
+
65
+ The project home and main documentation is on `Read the Docs`_.
66
+
67
+ .. _Read the Docs: https://tinyecs.readthedocs.io/
68
+
69
+ Support / Contributing
70
+ ======================
71
+
72
+ Issues can be opened on `github issues`_
73
+
74
+ .. _github issues: https://github.com/dickerdackel/tinyecs/issues
75
+
76
+ Please respect, that I don't want any contributions done with the assistance
77
+ of AI. This is a hobby project with focus on the craft of programming. I
78
+ have experimentally used AI to audit parts of the code and documentation, and
79
+ while it found a lot of typos and 2 actual issues, without exception, the
80
+ provided solutions were often besides the point or plain wrong.
81
+
82
+ I have no possibility to enforce that request, the simple fact that I ask for
83
+ it should be sufficient.
84
+
85
+ License
86
+ =======
87
+
88
+ This software is provided under the MIT license.
89
+
90
+ See LICENSE file for details.
@@ -1,8 +1,8 @@
1
1
  [project]
2
2
  name = "tinyecs"
3
3
  description = "The teeniest, tiniest ECS system"
4
- version = "0.3.4"
5
- readme = "README.md"
4
+ version = "0.3.6"
5
+ readme = "README.rst"
6
6
 
7
7
  authors = [
8
8
  { name="Michael Lamertz", email="michael.lamertz@gmail.com" }
@@ -282,16 +282,16 @@ def remove_component(eid: EntityID, *cids: ComponentID) -> None:
282
282
  obj.shutdown_()
283
283
 
284
284
 
285
- def add_system(fkt: SystemFunction, *cids: ComponentID) -> None:
285
+ def add_system(fn: SystemFunction, *cids: ComponentID) -> None:
286
286
  r"""Add a system for the specifiied cids.
287
287
 
288
- :param fkt: The system function
288
+ :param fn: The system function
289
289
  :param cids: The component ids that are required for this system
290
290
  :return: None
291
291
 
292
292
  The prototype for the function is::
293
293
 
294
- fkt(delta_time, eid, *comps)
294
+ fn(delta_time, eid, *comps)
295
295
 
296
296
  where delta_time is e.g. the miliseconds from a pygame tick. eid is the id
297
297
  of the entity that matches, and \*comps are all requested components for
@@ -304,13 +304,13 @@ def add_system(fkt: SystemFunction, *cids: ComponentID) -> None:
304
304
  """
305
305
 
306
306
  create_archetype(*cids)
307
- sidx[fkt] = cids
307
+ sidx[fn] = cids
308
308
 
309
309
 
310
- def remove_system(fkt: SystemFunction) -> None:
310
+ def remove_system(fn: SystemFunction) -> None:
311
311
  """Remove the given function from the registry.
312
312
 
313
- :param fkt: The system function
313
+ :param fn: The system function
314
314
  :return: None
315
315
 
316
316
  Remove the match for this function from the registry
@@ -319,11 +319,11 @@ def remove_system(fkt: SystemFunction) -> None:
319
319
  """
320
320
 
321
321
  for domain in didx:
322
- remove_system_from_domain(domain, fkt)
322
+ remove_system_from_domain(domain, fn)
323
323
 
324
324
  # Ignore unregistered systems, since we're removing anyways
325
325
  try:
326
- del sidx[fkt]
326
+ del sidx[fn]
327
327
  except KeyError:
328
328
  pass
329
329
 
@@ -519,43 +519,80 @@ def cid_of_comp(eid: EntityID, comp: Component) -> ComponentID:
519
519
  raise UnknownComponentError(f'Component {comp} not found in entity {eid}')
520
520
 
521
521
 
522
+ def _create_call_list(cids: tuple[ComponentID],
523
+ has_properties: _OptionalProperties) -> list[tuple[EntityID, Component]]:
524
+ at = tuple(cids)
525
+ if at not in archetype:
526
+ create_archetype(*cids)
527
+
528
+ adict = archetype[at]
529
+ # need to get call_list upfront, since kill_system could modify the dict
530
+ # Also: not running in the if clause is vastly faster than checking an
531
+ # empty set.
532
+ if has_properties:
533
+ property_filter = set(has_properties)
534
+ call_list = [(eid, *parms) for eid, parms in adict.items()
535
+ if property_filter <= plist[eid]]
536
+ else:
537
+ call_list = [(eid, *parms) for eid, parms in adict.items()]
538
+ return call_list
539
+
540
+
522
541
  def run_system(dt: float,
523
- fkt: SystemFunction,
542
+ fn: SystemFunction,
524
543
  *cids: ComponentID,
525
544
  has_properties: _OptionalProperties = None,
526
545
  **kwargs: dict[str, Any]) -> _RunSystemResult:
527
546
  """Run the system for the matching cids.
528
547
 
529
548
  :param dt: delta time since the last frame (miliseconds)
530
- :param fkt: the actual system function
549
+ :param fn: the actual system function
531
550
  :param cids: the components to run on
532
551
  :param has_properties: set of required properties
533
552
  :return: A dictionary with entity IDs as key and the function result as value
534
553
 
535
- The fkt gets the list of all entities that contain the listed
536
- components. The list can further be narrowed down my filtering for given
537
- properties. Then it runs the function for every entity and the requested
538
- components, passing dt as heartbeat.
554
+ For every entity that has all the listed components, ``fn`` is run. The
555
+ function prototype of ``fn`` is::
556
+
557
+ def callback(dt: float, eid: EntityID, *comps: Component) -> Any
558
+
559
+ The list can further be narrowed down my filtering for given properties.
539
560
 
540
561
  This function is a direct call. Alternatively, you can use add_system
541
562
  combined with run_all_systems or run_domain below.
542
563
  """
543
564
 
544
- at = tuple(cids)
545
- if at not in archetype:
546
- create_archetype(*cids)
565
+ call_list = _create_call_list(cids, has_properties)
566
+ return {eid: fn(dt, eid, *parms, **kwargs) for eid, *parms in call_list}
547
567
 
548
- adict = archetype[at]
549
- # need to get call_list upfront, since kill_system could modify the dict
550
- # Also: not running in the if clause is vastly faster than checking an
551
- # empty set.
552
- if has_properties:
553
- property_filter = set(has_properties)
554
- call_list = [(eid, *parms) for eid, parms in adict.items()
555
- if property_filter <= plist[eid]]
556
- else:
557
- call_list = [(eid, *parms) for eid, parms in adict.items()]
558
- return {eid: fkt(dt, eid, *parms, **kwargs) for eid, *parms in call_list}
568
+
569
+ def run_bulk_system(dt: float,
570
+ fn: SystemFunction,
571
+ *cids: ComponentID,
572
+ has_properties: _OptionalProperties = None,
573
+ **kwargs: dict[str, Any]) -> Any:
574
+ """Run the bulk system for all entities with matching cids.
575
+
576
+ :param dt: delta time since the last frame (miliseconds)
577
+ :param fn: the actual system function
578
+ :param cids: the components to run on
579
+ :param has_properties: set of required properties
580
+ :return: A dictionary with entity IDs as key and the function result as value
581
+
582
+ The fn is called with a list of entity ID and components tuples of all
583
+ entities that contain the listed components. The list can further be
584
+ narrowed down my filtering for given properties.
585
+
586
+ ``fn`` is called given a list of ``(EntityID, (components,...))``. In
587
+ contrast to ``run_system``, the user is responsible to loop over the list.
588
+
589
+ The prototype of ``fn`` is::
590
+
591
+ def callback(dt: call_list: list[tuple[EntityID, list[Component]]]) -> Any
592
+ """
593
+
594
+ call_list = _create_call_list(cids, has_properties)
595
+ return fn(dt, call_list, **kwargs)
559
596
 
560
597
 
561
598
  def run_all_systems(dt: float) -> dict[SystemFunction, _RunSystemResult]:
@@ -568,8 +605,8 @@ def run_all_systems(dt: float) -> dict[SystemFunction, _RunSystemResult]:
568
605
  appropriate components.
569
606
  """
570
607
 
571
- return {fkt: run_system(dt, fkt, *comps)
572
- for fkt, comps in sidx.items()}
608
+ return {fn: run_system(dt, fn, *comps)
609
+ for fn, comps in sidx.items()}
573
610
 
574
611
 
575
612
  def run_domain(dt: float, domain: DomainID) -> dict[SystemFunction, _RunSystemResult]:
@@ -584,14 +621,14 @@ def run_domain(dt: float, domain: DomainID) -> dict[SystemFunction, _RunSystemRe
584
621
  if domain not in didx:
585
622
  return {}
586
623
 
587
- return {fkt: run_system(dt, fkt, *sidx[fkt])
588
- for fkt in didx[domain]}
624
+ return {fn: run_system(dt, fn, *sidx[fn])
625
+ for fn in didx[domain]}
589
626
 
590
627
 
591
628
  def create_archetype(*cids: ComponentID) -> None:
592
629
  """Create an archetype from the provided cids.
593
630
 
594
- :param cids The list of cids that define the archetype.
631
+ :param cids: The list of cids that define the archetype.
595
632
  :return: None
596
633
 
597
634
  An archetype is a fixed combination of components. Each time a component
@@ -672,8 +709,8 @@ def remove_from_archetype(eid: EntityID, cid: ComponentID | None = None) -> None
672
709
  def comps_of_archetype(*cids: ComponentID, has_properties: _OptionalProperties = None) -> list[_EntityComponentsBundle]:
673
710
  """Return the given archetype.
674
711
 
675
- :param cids The cids that define the archetype.
676
- :param has_properties Optional set of required properties
712
+ :param cids: The cids that define the archetype.
713
+ :param has_properties: Optional set of required properties
677
714
  :return: A list of tuples of (eid, components)
678
715
  :raises UnknownArchetypeError: If the given archetype doesn't exist.
679
716
 
@@ -204,6 +204,11 @@ ecs.run_system(dt, deadzone_system, 'position', deadzone=WORLD)
204
204
 
205
205
  So the system is basically the `update(dt)` function in an OO driven game.
206
206
 
207
+ If you prefer to loop over the list of entities yourself, instead of your
208
+ system being called for each entity individually, theren is `run_bulk_system`,
209
+ which gets the same arguments as ``run_system``, but will pass the full list
210
+ to the system.
211
+
207
212
  At this point you might get the feeling, that you will have a very large list
208
213
  of systems in a big block in your game loop, and that's exactly right. You
209
214
  either hate that, which is fine, so the option is either to go back to an OO
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.4
2
+ Name: tinyecs
3
+ Version: 0.3.6
4
+ Summary: The teeniest, tiniest ECS system
5
+ Author-email: Michael Lamertz <michael.lamertz@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: homepage, https://github.com/dickerdackel/tinyecs
8
+ Project-URL: bugtracker, https://github.com/DickerDackel/tinyecs/issues
9
+ Project-URL: docs, https://tinyecs.readthedocs.io/
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Games/Entertainment
13
+ Classifier: Topic :: Software Development :: Libraries :: pygame
14
+ Description-Content-Type: text/x-rst
15
+ License-File: LICENSE
16
+ Requires-Dist: pgcooldown
17
+ Dynamic: license-file
18
+
19
+ Introduction
20
+ ############
21
+
22
+ ECS stands for Entity Component System, and it is a programming paradigm that
23
+ differs from the well known OO.
24
+
25
+ During my research I stumbled over `this article`_ and after reading part 2 and
26
+ 3, I decided to implement an ECS myself, well aware that `esper` is a solid
27
+ and long existing implementation, but I wanted to see how to implement it
28
+ myself.
29
+
30
+ .. _this article: https://web.archive.org/web/20250408195932/https://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/
31
+
32
+
33
+ I'm not trying to sell you an ECS by explaining the problems of multiple
34
+ inheritance in game programming. There are articles out there that do this in
35
+ much detail. If you're here, you're already interested in the concept, so
36
+ the tutorial might be a good starting point. It will create a small demo with
37
+ bouncing sprites using pygame-ce. But tinyecs is platform agnostic, you can
38
+ use it with whatever library you like.
39
+
40
+
41
+ Installation
42
+ ============
43
+
44
+ tinyecs is available on pip and can be installed by::
45
+
46
+ pip install tinyecs
47
+
48
+ The project is maintained and hosted on `github`_, where you also can find the
49
+ wheels for a local install, or install it directly from the cloned repo.
50
+
51
+ .. _github: https://github.com/dickerdackel/tinyecs
52
+
53
+ .. code-block::
54
+
55
+ git clone https://github.com/dickerdackel/tinyecs
56
+ cd tinyecs
57
+ python3 -m venv --prompt tinyecs .venv
58
+ .venv/bin/activate
59
+ # or .venv/Scripts/activate.bat on windows
60
+ pip install .
61
+
62
+ Documentation
63
+ =============
64
+
65
+ The project home and main documentation is on `Read the Docs`_.
66
+
67
+ .. _Read the Docs: https://tinyecs.readthedocs.io/
68
+
69
+ Support / Contributing
70
+ ======================
71
+
72
+ Issues can be opened on `github issues`_
73
+
74
+ .. _github issues: https://github.com/dickerdackel/tinyecs/issues
75
+
76
+ Please respect, that I don't want any contributions done with the assistance
77
+ of AI. This is a hobby project with focus on the craft of programming. I
78
+ have experimentally used AI to audit parts of the code and documentation, and
79
+ while it found a lot of typos and 2 actual issues, without exception, the
80
+ provided solutions were often besides the point or plain wrong.
81
+
82
+ I have no possibility to enforce that request, the simple fact that I ask for
83
+ it should be sufficient.
84
+
85
+ License
86
+ =======
87
+
88
+ This software is provided under the MIT license.
89
+
90
+ See LICENSE file for details.
@@ -175,6 +175,19 @@ def test_run_system():
175
175
  assert worked
176
176
 
177
177
 
178
+ def test_run_bulk_system():
179
+ ecs.reset()
180
+ for i in range(10):
181
+ eid = ecs.create_entity()
182
+ ecs.add_component(eid, 'number', i)
183
+
184
+ def bulk_runner(dt, call_list):
185
+ assert len(call_list) == 10
186
+ return sum([i for eid, i in call_list])
187
+
188
+ assert ecs.run_bulk_system(0, bulk_runner, 'number') == 45
189
+
190
+
178
191
  def test_run_all_systems():
179
192
  e1, e2 = setup()
180
193
  ecs.add_system(move_system, 'pos', 'velocity')
tinyecs-0.3.4/PKG-INFO DELETED
@@ -1,17 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: tinyecs
3
- Version: 0.3.4
4
- Summary: The teeniest, tiniest ECS system
5
- Author-email: Michael Lamertz <michael.lamertz@gmail.com>
6
- License-Expression: MIT
7
- Project-URL: homepage, https://github.com/dickerdackel/tinyecs
8
- Project-URL: bugtracker, https://github.com/DickerDackel/tinyecs/issues
9
- Project-URL: docs, https://tinyecs.readthedocs.io/
10
- Classifier: Development Status :: 3 - Alpha
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Topic :: Games/Entertainment
13
- Classifier: Topic :: Software Development :: Libraries :: pygame
14
- Description-Content-Type: text/markdown
15
- License-File: LICENSE
16
- Requires-Dist: pgcooldown
17
- Dynamic: license-file
@@ -1,17 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: tinyecs
3
- Version: 0.3.4
4
- Summary: The teeniest, tiniest ECS system
5
- Author-email: Michael Lamertz <michael.lamertz@gmail.com>
6
- License-Expression: MIT
7
- Project-URL: homepage, https://github.com/dickerdackel/tinyecs
8
- Project-URL: bugtracker, https://github.com/DickerDackel/tinyecs/issues
9
- Project-URL: docs, https://tinyecs.readthedocs.io/
10
- Classifier: Development Status :: 3 - Alpha
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Topic :: Games/Entertainment
13
- Classifier: Topic :: Software Development :: Libraries :: pygame
14
- Description-Content-Type: text/markdown
15
- License-File: LICENSE
16
- Requires-Dist: pgcooldown
17
- Dynamic: license-file
File without changes
File without changes
File without changes
File without changes
File without changes