tinyecs 0.3.5__tar.gz → 0.4.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.
Files changed (23) hide show
  1. {tinyecs-0.3.5/src/tinyecs.egg-info → tinyecs-0.4.0}/PKG-INFO +1 -1
  2. {tinyecs-0.3.5 → tinyecs-0.4.0}/pyproject.toml +1 -1
  3. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/__init__.py +76 -45
  4. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/compsys.py +12 -12
  5. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demos/background.py +4 -4
  6. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demos/bouncing_sprites.py +2 -2
  7. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demos/example.py +6 -6
  8. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demos/homing-missiles.py +14 -14
  9. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demos/marquee.py +2 -2
  10. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/tutorial.py +42 -36
  11. {tinyecs-0.3.5 → tinyecs-0.4.0/src/tinyecs.egg-info}/PKG-INFO +1 -1
  12. {tinyecs-0.3.5 → tinyecs-0.4.0}/tests/test_tinyecs.py +30 -17
  13. {tinyecs-0.3.5 → tinyecs-0.4.0}/LICENSE +0 -0
  14. {tinyecs-0.3.5 → tinyecs-0.4.0}/README.rst +0 -0
  15. {tinyecs-0.3.5 → tinyecs-0.4.0}/setup.cfg +0 -0
  16. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/components.py +0 -0
  17. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demo.py +0 -0
  18. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demos/__init__.py +0 -0
  19. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs.egg-info/SOURCES.txt +0 -0
  20. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs.egg-info/dependency_links.txt +0 -0
  21. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs.egg-info/entry_points.txt +0 -0
  22. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs.egg-info/requires.txt +0 -0
  23. {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinyecs
3
- Version: 0.3.5
3
+ Version: 0.4.0
4
4
  Summary: The teeniest, tiniest ECS system
5
5
  Author-email: Michael Lamertz <michael.lamertz@gmail.com>
6
6
  License-Expression: MIT
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "tinyecs"
3
3
  description = "The teeniest, tiniest ECS system"
4
- version = "0.3.5"
4
+ version = "0.4.0"
5
5
  readme = "README.rst"
6
6
 
7
7
  authors = [
@@ -24,7 +24,7 @@ type DomainID = Hashable
24
24
  type Property = Hashable
25
25
  type Component = object
26
26
 
27
- type _EntityComponentsBundle = tuple[EntityID, list[Component]]
27
+ type EntityComponentsBundle = tuple[EntityID, list[Component]]
28
28
  type _OptionalProperties = Iterable[Property] | None
29
29
  type _RunSystemResult = dict[EntityID, Any]
30
30
 
@@ -32,7 +32,7 @@ type _RunSystemResult = dict[EntityID, Any]
32
32
  # The typehinting system can't work with functions using *args, **kwargs.
33
33
  # This solution comes from https://docs.python.org/3/library/typing.html#annotating-callable-objects
34
34
  class SystemFunction(Protocol):
35
- def __call__(self, dt: float, eid: EntityID, *cids: ComponentID, **kwargs: dict[str, object]) -> Any: ...
35
+ def __call__(self, eid: EntityID, *cids: ComponentID, **kwargs: dict[str, object]) -> Any: ...
36
36
 
37
37
 
38
38
  eidx = {} # entity index
@@ -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
 
@@ -480,7 +480,7 @@ def comp_of_eid(eid: EntityID, cid: ComponentID) -> Component:
480
480
  return comps_of_eid(eid, cid)[0]
481
481
 
482
482
 
483
- def eid_of_comp(comp: Component) -> set(EntityID):
483
+ def eid_of_comp(comp: Component) -> set[EntityID]:
484
484
  """Find the entity id for object comp.
485
485
 
486
486
  :param comp: The component to find the entity of
@@ -519,63 +519,94 @@ 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 run_system(dt: float,
523
- fkt: SystemFunction,
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
+
541
+ def run_system(fn: SystemFunction,
524
542
  *cids: ComponentID,
525
543
  has_properties: _OptionalProperties = None,
526
544
  **kwargs: dict[str, Any]) -> _RunSystemResult:
527
545
  """Run the system for the matching cids.
528
546
 
529
- :param dt: delta time since the last frame (miliseconds)
530
- :param fkt: the actual system function
547
+ :param fn: the actual system function
531
548
  :param cids: the components to run on
532
549
  :param has_properties: set of required properties
533
550
  :return: A dictionary with entity IDs as key and the function result as value
534
551
 
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.
552
+ For every entity that has all the listed components, ``fn`` is run. The
553
+ function prototype of ``fn`` is::
554
+
555
+ def callback(eid: EntityID, *comps: Component) -> Any
556
+
557
+ The list can further be narrowed down my filtering for given properties.
539
558
 
540
559
  This function is a direct call. Alternatively, you can use add_system
541
560
  combined with run_all_systems or run_domain below.
542
561
  """
543
562
 
544
- at = tuple(cids)
545
- if at not in archetype:
546
- create_archetype(*cids)
563
+ call_list = _create_call_list(cids, has_properties)
564
+ return {eid: fn(eid, *parms, **kwargs) for eid, *parms in call_list}
547
565
 
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}
559
566
 
567
+ def run_bulk_system(fn: SystemFunction,
568
+ *cids: ComponentID,
569
+ has_properties: _OptionalProperties = None,
570
+ **kwargs: dict[str, Any]) -> Any:
571
+ """Run the bulk system for all entities with matching cids.
560
572
 
561
- def run_all_systems(dt: float) -> dict[SystemFunction, _RunSystemResult]:
573
+ :param fn: the actual system function
574
+ :param cids: the components to run on
575
+ :param has_properties: set of required properties
576
+ :return: A dictionary with entity IDs as key and the function result as value
577
+
578
+ The fn is called with a list of entity ID and components tuples of all
579
+ entities that contain the listed components. The list can further be
580
+ narrowed down my filtering for given properties.
581
+
582
+ ``fn`` is called given a list of ``(EntityID, (components,...))``. In
583
+ contrast to ``run_system``, the user is responsible to loop over the list.
584
+
585
+ The prototype of ``fn`` is::
586
+
587
+ def callback(call_list: list[tuple[EntityID, list[Component]]]) -> Any
588
+ """
589
+
590
+ call_list = _create_call_list(cids, has_properties)
591
+ return fn(call_list, **kwargs) if call_list else None
592
+
593
+
594
+ def run_all_systems(**kwargs) -> dict[SystemFunction, _RunSystemResult]:
562
595
  """Run all registered systems.
563
596
 
564
- :param dt: Delta time
565
597
  :return: A dict of function as key, and the result of run_system as value
566
598
 
567
599
  This calls above run_system for all registered systems with their
568
600
  appropriate components.
569
601
  """
570
602
 
571
- return {fkt: run_system(dt, fkt, *comps)
572
- for fkt, comps in sidx.items()}
603
+ return {fn: run_system(fn, *comps, **kwargs)
604
+ for fn, comps in sidx.items()}
573
605
 
574
606
 
575
- def run_domain(dt: float, domain: DomainID) -> dict[SystemFunction, _RunSystemResult]:
607
+ def run_domain(domain: DomainID, **kwargs) -> dict[SystemFunction, _RunSystemResult]:
576
608
  """Run all systems within domain.
577
609
 
578
- :param dt: Delta time
579
610
  :return: A dict of function as key, and the result of run_system as value
580
611
 
581
612
  This is the same as run_all_systems, but limited to a specific domain.
@@ -584,14 +615,14 @@ def run_domain(dt: float, domain: DomainID) -> dict[SystemFunction, _RunSystemRe
584
615
  if domain not in didx:
585
616
  return {}
586
617
 
587
- return {fkt: run_system(dt, fkt, *sidx[fkt])
588
- for fkt in didx[domain]}
618
+ return {fn: run_system(fn, *sidx[fn], **kwargs)
619
+ for fn in didx[domain]}
589
620
 
590
621
 
591
622
  def create_archetype(*cids: ComponentID) -> None:
592
623
  """Create an archetype from the provided cids.
593
624
 
594
- :param cids The list of cids that define the archetype.
625
+ :param cids: The list of cids that define the archetype.
595
626
  :return: None
596
627
 
597
628
  An archetype is a fixed combination of components. Each time a component
@@ -669,11 +700,11 @@ def remove_from_archetype(eid: EntityID, cid: ComponentID | None = None) -> None
669
700
  del adict[eid]
670
701
 
671
702
 
672
- def comps_of_archetype(*cids: ComponentID, has_properties: _OptionalProperties = None) -> list[_EntityComponentsBundle]:
703
+ def comps_of_archetype(*cids: ComponentID, has_properties: _OptionalProperties = None) -> list[EntityComponentsBundle]:
673
704
  """Return the given archetype.
674
705
 
675
- :param cids The cids that define the archetype.
676
- :param has_properties Optional set of required properties
706
+ :param cids: The cids that define the archetype.
707
+ :param has_properties: Optional set of required properties
677
708
  :return: A list of tuples of (eid, components)
678
709
  :raises UnknownArchetypeError: If the given archetype doesn't exist.
679
710
 
@@ -249,12 +249,12 @@ class RSAImage:
249
249
  self.update()
250
250
 
251
251
 
252
- def dead_system(dt, eid, dead):
252
+ def dead_system(eid, dead, **kwargs):
253
253
  """Reap entities marked with a `dead` component."""
254
254
  ecs.remove_entity(eid)
255
255
 
256
256
 
257
- def deadzone_system(dt, eid, world, position, *, container):
257
+ def deadzone_system(eid, world, position, *, container, **kwargs):
258
258
  """Kill entities moving outside defined boundaries
259
259
 
260
260
  To avoid sprites flying off to infinity, a dead zone can be defined, that
@@ -284,7 +284,7 @@ def deadzone_system(dt, eid, world, position, *, container):
284
284
  ecs.remove_entity(eid)
285
285
 
286
286
 
287
- def extension_system(dt, eid, extension):
287
+ def extension_system(eid, extension, **kwargs):
288
288
  """Add additional components.
289
289
 
290
290
  Parameters:
@@ -318,29 +318,29 @@ def extension_system(dt, eid, extension):
318
318
  ecs.remove_component(eid, 'extension')
319
319
 
320
320
 
321
- def force_system(dt, eid, force, momentum):
321
+ def force_system(eid, force, momentum, *, dt, **kwargs):
322
322
  """Apply a force to a momentum"""
323
323
  momentum += force * dt
324
324
 
325
325
 
326
- def lifetime_system(dt, eid, lifetime):
326
+ def lifetime_system(eid, lifetime, **kwargs):
327
327
  """Kill an entity after a specified time"""
328
328
  if lifetime.cold():
329
329
  ecs.remove_entity(eid)
330
330
 
331
331
 
332
- def momentum_system(dt, eid, momentum, position):
332
+ def momentum_system(eid, momentum, position, *, dt):
333
333
  """Apply momentum to a position (a.k.a. "move")."""
334
334
  position += momentum * dt
335
335
 
336
336
 
337
- def mouse_system(dt, eid, mouse, position):
337
+ def mouse_system(eid, mouse, position):
338
338
  """Place the position of an entity to the mouse cursor."""
339
339
  mp = pygame.mouse.get_pos()
340
340
  position.xy = Vector2(mp)
341
341
 
342
342
 
343
- def scale_system(dt, eid, scale, momentum):
343
+ def scale_system(eid, scale, momentum, *, dt):
344
344
  """Apply friction to a momentum.
345
345
 
346
346
  In contrast to a force, which adds a directional vector to the momentum,
@@ -353,7 +353,7 @@ def scale_system(dt, eid, scale, momentum):
353
353
  friction_system = scale_system
354
354
 
355
355
 
356
- def sprite_system(dt, eid, sprite, position):
356
+ def sprite_system(eid, sprite, position, **kwargs):
357
357
  """Set the rect.center of sprite to position."""
358
358
 
359
359
  if sprite.rect:
@@ -362,7 +362,7 @@ def sprite_system(dt, eid, sprite, position):
362
362
  sprite.rect = sprite.image.get_rect(center=position)
363
363
 
364
364
 
365
- def wsad_system(dt, eid, wsad, position):
365
+ def wsad_system(eid, wsad, position, *, dt):
366
366
  """An example system to control a playear with the `wsad` keys.
367
367
 
368
368
  Given the following entity:
@@ -374,7 +374,7 @@ def wsad_system(dt, eid, wsad, position):
374
374
 
375
375
  running this system will modify the position component of the entity.
376
376
 
377
- ecs.run_system(dt, 'wsad', 'position', 'player')
377
+ ecs.run_system('wsad', 'position', 'player', dt=dt)
378
378
 
379
379
  Parameters
380
380
  ----------
@@ -396,4 +396,4 @@ def wsad_system(dt, eid, wsad, position):
396
396
  if keys[pygame.K_d]: v.x += 1
397
397
 
398
398
  # Normalize so diagonals are not faster then axis motion
399
- if v: position += v.normalize() * wsad * dt
399
+ if v: position += v.normalize() * wsad
@@ -6,11 +6,11 @@ from types import SimpleNamespace
6
6
  from pygame import Vector2
7
7
 
8
8
 
9
- def momentum_system(dt, eid, momentum, position):
9
+ def momentum_system(eid, momentum, position, *, dt):
10
10
  position += momentum * dt
11
11
 
12
12
 
13
- def border_system(dt, eid, border, position, momentum):
13
+ def border_system(eid, border, position, momentum, **kwargs):
14
14
  if position.x < 0:
15
15
  position.x = -position.x
16
16
  momentum.x = -momentum.x
@@ -25,7 +25,7 @@ def border_system(dt, eid, border, position, momentum):
25
25
  momentum.y = -momentum.y
26
26
 
27
27
 
28
- def background_system(dt, eid, background, position, surface):
28
+ def background_system(eid, background, position, surface, *, dt):
29
29
  def draw_one(angle, offset):
30
30
  v0 = Vector2(position)
31
31
  v1 = Vector2(2000, 0).rotate(angle)
@@ -81,7 +81,7 @@ def main():
81
81
 
82
82
  screen.fill('black')
83
83
 
84
- ecs.run_all_systems(dt)
84
+ ecs.run_all_systems(dt=dt)
85
85
 
86
86
  pygame.display.flip()
87
87
  clock.tick(FPS)
@@ -14,7 +14,7 @@ class Sprite(pygame.sprite.Sprite):
14
14
  self.rect = self.image.get_rect()
15
15
 
16
16
 
17
- def bounding_box_system(dt, eid, world, position, momentum):
17
+ def bounding_box_system(eid, world, position, momentum, **kwargs):
18
18
  if position.x < 0:
19
19
  position.x = -position.x
20
20
  momentum.x = -momentum.x
@@ -95,7 +95,7 @@ def main():
95
95
  screen.fill('black')
96
96
 
97
97
  group.update(dt)
98
- ecs.run_all_systems(dt)
98
+ ecs.run_all_systems(dt=dt)
99
99
 
100
100
  screen.blit(click, click_rect)
101
101
  group.draw(screen)
@@ -25,7 +25,7 @@ class Velocity:
25
25
  dy: float
26
26
 
27
27
 
28
- def motion_system(dt, eid, position, velocity):
28
+ def motion_system(eid, position, velocity, *, dt):
29
29
  # Note that assigning a new Vector to position would not make it back
30
30
  # into e.position. But since we don't change the position instance
31
31
  # itself, we're safe.
@@ -33,14 +33,14 @@ def motion_system(dt, eid, position, velocity):
33
33
  position.y += velocity.dy * dt
34
34
 
35
35
 
36
- def lifetime_system(dt, eid, lifetime):
36
+ def lifetime_system(eid, lifetime, *, dt):
37
37
  lifetime.time_left -= 1 * dt
38
38
  if lifetime.time_left <= 0:
39
39
  print(f'removing entity {eid}')
40
40
  ecs.remove_entity(eid)
41
41
 
42
42
 
43
- def render_system(dt, eid, printable):
43
+ def render_system(eid, printable):
44
44
  # the printable component contains no data at all and can be ignored,
45
45
  # but it will still be passed as a parameter.
46
46
  name, pos, velocity, lifetime = ecs.comps_of_eid(eid,
@@ -72,7 +72,7 @@ def main():
72
72
 
73
73
  ecs.run_system(1, render_system, Comps.PRINTABLE)
74
74
  for i in range(15):
75
- ecs.run_system(1, motion_system, Comps.POSITION, Comps.VELOCITY)
75
+ ecs.run_system(1, motion_system, Comps.POSITION, Comps.VELOCITY, dt=1)
76
76
  ecs.run_system(1, lifetime_system, Comps.LIFETIME)
77
77
  ecs.run_system(1, render_system, Comps.PRINTABLE)
78
78
 
@@ -105,8 +105,8 @@ def main():
105
105
 
106
106
  for i in range(15):
107
107
  # ecs.run_all_systems(1)
108
- ecs.run_domain(1, 'updates')
109
- ecs.run_domain(1, 'render')
108
+ ecs.run_domain('updates', dt=1)
109
+ ecs.run_domain('render')
110
110
 
111
111
  print(f'All entities: {ecs.eidx}')
112
112
  print(f'All components: {ecs.cidx}')
@@ -10,12 +10,12 @@ from pgcooldown import Cooldown
10
10
  from pygame import Vector2
11
11
 
12
12
 
13
- def dead_sprite_system(dt, eid, dead, sprite):
13
+ def dead_sprite_system(eid, dead, sprite):
14
14
  sprite.kill()
15
15
  ecs.remove_entity(eid)
16
16
 
17
17
 
18
- def lifetime_sprite_system(dt, eid, lifetime, sprite):
18
+ def lifetime_sprite_system(eid, lifetime, sprite):
19
19
  if lifetime.hot():
20
20
  return
21
21
  sprite.kill()
@@ -54,7 +54,7 @@ def create_shard(position, image, *groups):
54
54
  ecs.add_component(e, 'lifetime', Cooldown(random() * 0.25))
55
55
 
56
56
 
57
- def homing_missile_system(dt, eid, homing_missile, position, momentum):
57
+ def homing_missile_system(eid, homing_missile, position, momentum, *, dt):
58
58
  target_pos = ecs.comp_of_eid(homing_missile.target, 'position')
59
59
 
60
60
  target = target_pos
@@ -88,14 +88,14 @@ def homing_missile_system(dt, eid, homing_missile, position, momentum):
88
88
  momentum.rotate_ip(copysign(rotation, delta_phi))
89
89
 
90
90
 
91
- def fire_system(dt, eid, fire, position):
91
+ def fire_system(eid, fire, position):
92
92
  for i in range(fire.count):
93
93
  fire.fkt(pos=position)
94
94
 
95
95
  ecs.remove_component(eid, 'fire')
96
96
 
97
97
 
98
- def bounding_box_system(dt, eid, world, position, momentum):
98
+ def bounding_box_system(eid, world, position, momentum):
99
99
  if position.x < 0:
100
100
  position.x = -position.x
101
101
  momentum.x = -momentum.x
@@ -206,15 +206,15 @@ def main():
206
206
 
207
207
  screen.fill('black')
208
208
 
209
- ecs.run_system(dt, ecsc.mouse_system, 'mouse', 'position')
210
- ecs.run_system(dt, lifetime_sprite_system, 'lifetime', 'sprite')
211
- ecs.run_system(dt, dead_sprite_system, 'dead', 'sprite')
212
- ecs.run_system(dt, ecsc.dead_system, 'dead')
213
- ecs.run_system(dt, ecsc.momentum_system, 'momentum', 'position')
214
- ecs.run_system(dt, ecsc.sprite_system, 'sprite', 'position')
215
- ecs.run_system(dt, bounding_box_system, 'world', 'position', 'momentum')
216
- ecs.run_system(dt, homing_missile_system, 'homing_missile', 'position', 'momentum')
217
- ecs.run_system(dt, fire_system, 'fire', 'position')
209
+ ecs.run_system(ecsc.mouse_system, 'mouse', 'position')
210
+ ecs.run_system(lifetime_sprite_system, 'lifetime', 'sprite')
211
+ ecs.run_system(dead_sprite_system, 'dead', 'sprite')
212
+ ecs.run_system(ecsc.dead_system, 'dead')
213
+ ecs.run_system(ecsc.momentum_system, 'momentum', 'position', dt=dt)
214
+ ecs.run_system(ecsc.sprite_system, 'sprite', 'position')
215
+ ecs.run_system(bounding_box_system, 'world', 'position', 'momentum')
216
+ ecs.run_system(homing_missile_system, 'homing_missile', 'position', 'momentum', dt=dt)
217
+ ecs.run_system(fire_system, 'fire', 'position')
218
218
 
219
219
  screen.blit(click, click_rect)
220
220
  group.draw(screen)
@@ -10,7 +10,7 @@ from pgcooldown import Cooldown
10
10
  from pygame import Vector2
11
11
 
12
12
 
13
- def marquee_system(dt, eid, marquee, border, surface):
13
+ def marquee_system(eid, marquee, border, surface, *, dt):
14
14
  def bounce(point, momentum):
15
15
  point += momentum * dt
16
16
  if point.x < 0:
@@ -80,7 +80,7 @@ def main():
80
80
 
81
81
  screen.fill(BLACK)
82
82
 
83
- ecs.run_system(dt, marquee_system, 'marquee', 'border', 'surface')
83
+ ecs.run_system(marquee_system, 'marquee', 'border', 'surface', dt=dt)
84
84
 
85
85
  pygame.display.flip()
86
86
  clock.tick(FPS)
@@ -173,17 +173,18 @@ will be used. To run e.g. the `momentum_system`, put the following into the
173
173
  game loop:
174
174
 
175
175
  ``py
176
- ecs.run_system(dt, momentum_system, 'momentum', 'position')
176
+ ecs.run_system(momentum_system, 'momentum', 'position', dt=deltatime)
177
177
  ```
178
178
 
179
179
  When the game loop passes this call, `run_system` will find all entities that
180
180
  have both components `momentum` and `position` and pass these together with
181
- deltatime `dt` and the entity ID into the `momentum_system` function.
181
+ the entity ID into the `momentum_system` function. Remember, that kwargs can
182
+ be used to pass additional arguments from the main scope.
182
183
 
183
184
  Writing the `momentum_system` is easy:
184
185
 
185
186
  ```py
186
- def momentum_system(dt, eid, momentum, position):
187
+ def momentum_system(eid, momentum, position, *, dt):
187
188
  position += momentum * dt
188
189
  ```
189
190
 
@@ -199,11 +200,16 @@ passed in after the components. The following will be explained in the actual
199
200
  script later.
200
201
 
201
202
  ```py
202
- ecs.run_system(dt, deadzone_system, 'position', deadzone=WORLD)
203
+ ecs.run_system(deadzone_system, 'position', deadzone=WORLD)
203
204
  ```
204
205
 
205
206
  So the system is basically the `update(dt)` function in an OO driven game.
206
207
 
208
+ If you prefer to loop over the list of entities yourself, instead of your
209
+ system being called for each entity individually, theren is `run_bulk_system`,
210
+ which gets the same arguments as ``run_system``, but will pass the full list
211
+ to the system.
212
+
207
213
  At this point you might get the feeling, that you will have a very large list
208
214
  of systems in a big block in your game loop, and that's exactly right. You
209
215
  either hate that, which is fine, so the option is either to go back to an OO
@@ -216,7 +222,7 @@ There are some alternatives still.
216
222
  program and call a single function in your game loop:
217
223
 
218
224
  ```py
219
- ecs.run_all_systems(dt)
225
+ ecs.run_all_systems(dt=dt)
220
226
  ```
221
227
 
222
228
  2. To give you a more fine grained control over what systems run together, the
@@ -236,7 +242,7 @@ method of your component. You still need that block of `run_system` or above
236
242
  shortcuts in your game loop though.
237
243
 
238
244
  ```py
239
- def call_update_system(dt, eid, component):
245
+ def call_update_system(eid, component, dt=dt):
240
246
  component.update(dt)
241
247
  ```
242
248
 
@@ -359,15 +365,15 @@ completeness:
359
365
  # addition.
360
366
  WORLD = SCREEN.scale_by(1.25)
361
367
 
362
- def momentum_system(dt, eid, momentum, position):
368
+ def momentum_system(eid, momentum, position, *, dt):
363
369
  """Add a delta time scaled momentum to the position."""
364
370
  position += momentum * dt
365
371
 
366
- def sprite_position_system(dt, eid, sprite, position):
372
+ def sprite_position_system(eid, sprite, position):
367
373
  """Apply the position to the rect of the sprite for the sprite group"""
368
374
  sprite.rect.center = position
369
375
 
370
- def deadzone_system(dt, eid, position, *, world):
376
+ def deadzone_system(eid, position, *, world):
371
377
  """Kill sprites that move off screen"""
372
378
  if world.collidepoint(position):
373
379
  return
@@ -391,7 +397,7 @@ to the entity instead of overwriting the component variable.
391
397
 
392
398
  .. code-block::
393
399
 
394
- def take_damage_system(dt, eid, health, damage):
400
+ def take_damage_system(eid, health, damage):
395
401
  ecs.add_component(eid, 'health', health - damage)
396
402
 
397
403
  For semantic reasons, tinyecs also provides `update_component`, but that's
@@ -425,9 +431,9 @@ The lines marked with `>` are additions to the game loop template all above.
425
431
  #### Running the systems
426
432
 
427
433
  ```py
428
- > ecs.run_system(dt, momentum_system, 'momentum', 'position')
429
- > ecs.run_system(dt, deadzone_system, 'position', world=WORLD)
430
- > ecs.run_system(dt, sprite_position_system, 'sprite', 'position')
434
+ > ecs.run_system(momentum_system, 'momentum', 'position', dt=dt)
435
+ > ecs.run_system(deadzone_system, 'position', world=WORLD)
436
+ > ecs.run_system(sprite_position_system, 'sprite', 'position')
431
437
 
432
438
  screen.fill('black')
433
439
 
@@ -494,17 +500,17 @@ def create_box_entity(position, sprite_group):
494
500
  ecs.add_component(e, 'sprite', DemoSprite(sprite_group))
495
501
 
496
502
 
497
- def momentum_system(dt, eid, momentum, position):
503
+ def momentum_system(eid, momentum, position, *, dt):
498
504
  """Add a delta time scaled momentum to the position."""
499
505
  position += momentum * dt
500
506
 
501
507
 
502
- def sprite_position_system(dt, eid, sprite, position):
508
+ def sprite_position_system(eid, sprite, position):
503
509
  """Apply the position to the rect of the sprite for the sprite group"""
504
510
  sprite.rect.center = position
505
511
 
506
512
 
507
- def deadzone_system(dt, eid, position, *, world):
513
+ def deadzone_system(eid, position, *, world):
508
514
  """Kill sprites that move off screen"""
509
515
  if world.collidepoint(position):
510
516
  return
@@ -533,9 +539,9 @@ while running:
533
539
  pos = pygame.mouse.get_pos()
534
540
  create_box_entity(pos, group)
535
541
 
536
- ecs.run_system(dt, momentum_system, 'momentum', 'position')
537
- ecs.run_system(dt, deadzone_system, 'position', world=WORLD)
538
- ecs.run_system(dt, sprite_position_system, 'sprite', 'position')
542
+ ecs.run_system(momentum_system, 'momentum', 'position', dt=dt)
543
+ ecs.run_system(deadzone_system, 'position', world=WORLD)
544
+ ecs.run_system(sprite_position_system, 'sprite', 'position')
539
545
 
540
546
  screen.fill('black')
541
547
 
@@ -585,12 +591,12 @@ import tinyecs.compsys as ecsc
585
591
 
586
592
  UNSTABLE! DON'T RELY ON THIS YET!
587
593
 
588
- `def dead_system(dt, eid, dead)`:
594
+ `def dead_system(eid, dead)`:
589
595
  Sometimes it is useful to not remove a sprite immediately from the system.
590
596
  Instead, you can add a component tagged e.g. 'dead', and later reap all
591
597
  entities marked with that tag.
592
598
 
593
- `def deadzone_system(dt, eid, world, position, *, container)`:
599
+ `def deadzone_system(eid, world, position, *, container)`:
594
600
 
595
601
  Basically the function created in this tutorial, with one addition.
596
602
 
@@ -603,25 +609,25 @@ import tinyecs.compsys as ecsc
603
609
 
604
610
  ecs.add_component(e, 'world', True)
605
611
 
606
- `def extension_system(dt, eid, extension)`:
612
+ `def extension_system(eid, extension)`:
607
613
  Will be removed. tinyecs installs pgcooldown as a requirement and that
608
614
  comes with the `CronD` class which does the same and more.
609
615
 
610
- `def force_system(dt, eid, force, momentum)`:
616
+ `def force_system(eid, force, momentum, *, dt)`:
611
617
  Applies (adds) a constant force to a momentum.
612
618
 
613
- `def lifetime_system(dt, eid, lifetime)`:
619
+ `def lifetime_system(eid, lifetime)`:
614
620
  Kills the entity once lifetime has run out. Expects `lifetime` to be an
615
621
  instance of `pgcooldown.Cooldown`
616
622
 
617
- `def momentum_system(dt, eid, momentum, position)`:
623
+ `def momentum_system(eid, momentum, position, *, dt)`:
618
624
  The same as we wrote in the tutorial above.
619
625
 
620
- `def mouse_system(dt, eid, mouse, position)`:
626
+ `def mouse_system(eid, mouse, position)`:
621
627
  Update a position component with the position of the mouse cursor.
622
628
 
623
- `def scale_system(dt, eid, scale, momentum)`:
624
- `def friction_system(dt, eid, scale, momentum)`:
629
+ `def scale_system(eid, scale, momentum, *, dt)`:
630
+ `def friction_system(eid, scale, momentum, *, dt)`:
625
631
  Apply friction to a momentum.
626
632
 
627
633
  In contrast to a force_system, which adds a directional vector to the
@@ -630,10 +636,10 @@ import tinyecs.compsys as ecsc
630
636
 
631
637
  `friction_system` is an alias to `scale_system`.
632
638
 
633
- `def sprite_system(dt, eid, sprite, position)`:
639
+ `def sprite_system(eid, sprite, position)`:
634
640
  The same as we wrote above, apply the position to `sprite.rect.center`.
635
641
 
636
- `def wsad_system(dt, eid, wsad, position)`:
642
+ `def wsad_system(eid, wsad, position)`:
637
643
  An example system to control a playear with the `wsad` keys.
638
644
  This was just a proof of concept, but I'm currently using it, so perhaps
639
645
  it will stay.
@@ -690,17 +696,17 @@ def create_box_entity(position, sprite_group):
690
696
  ecs.add_component(e, 'sprite', DemoSprite(sprite_group))
691
697
 
692
698
 
693
- def momentum_system(dt, eid, momentum, position):
699
+ def momentum_system(eid, momentum, position, *, dt):
694
700
  """Add a delta time scaled momentum to the position."""
695
701
  position += momentum * dt
696
702
 
697
703
 
698
- def sprite_position_system(dt, eid, sprite, position):
704
+ def sprite_position_system(eid, sprite, position):
699
705
  """Apply the position to the rect of the sprite for the sprite group"""
700
706
  sprite.rect.center = position
701
707
 
702
708
 
703
- def deadzone_system(dt, eid, position, *, world):
709
+ def deadzone_system(eid, position, *, world):
704
710
  """Kill sprites that move off screen"""
705
711
  if world.collidepoint(position):
706
712
  return
@@ -730,9 +736,9 @@ def main():
730
736
  pos = pygame.mouse.get_pos()
731
737
  create_box_entity(pos, group)
732
738
 
733
- ecs.run_system(dt, momentum_system, 'momentum', 'position')
734
- ecs.run_system(dt, deadzone_system, 'position', world=WORLD)
735
- ecs.run_system(dt, sprite_position_system, 'sprite', 'position')
739
+ ecs.run_system(momentum_system, 'momentum', 'position', dt=dt)
740
+ ecs.run_system(deadzone_system, 'position', world=WORLD)
741
+ ecs.run_system(sprite_position_system, 'sprite', 'position')
736
742
 
737
743
  screen.fill('black')
738
744
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinyecs
3
- Version: 0.3.5
3
+ Version: 0.4.0
4
4
  Summary: The teeniest, tiniest ECS system
5
5
  Author-email: Michael Lamertz <michael.lamertz@gmail.com>
6
6
  License-Expression: MIT
@@ -28,20 +28,20 @@ class Ping:
28
28
  ack: bool = False
29
29
 
30
30
 
31
- def wounding_system(dt, eid, health):
31
+ def wounding_system(eid, health, **kwargs):
32
32
  health.health -= 100
33
33
  return health.health
34
34
 
35
35
 
36
- def stats_system(dt, eid, name, health):
37
- return f'{dt=}, {eid=}, {name.name=}, {health.health=}'
36
+ def stats_system(eid, name, health, **kwargs):
37
+ return f'{eid=}, {name.name=}, {health.health=}'
38
38
 
39
39
 
40
- def ping_system(dt, eid, ping):
40
+ def ping_system(eid, ping):
41
41
  ping.ack = True
42
42
 
43
43
 
44
- def move_system(dt, eid, pos, velocity):
44
+ def move_system(eid, pos, velocity, *, dt, **kwargs):
45
45
  pos.x += velocity.dx * dt
46
46
  pos.y += velocity.dy * dt
47
47
  return (pos.x, pos.y)
@@ -161,27 +161,40 @@ def test_comps_of_eid():
161
161
 
162
162
  def test_run_system():
163
163
  e1, e2 = setup()
164
- ecs.run_system(1, wounding_system, 'health')
164
+ ecs.run_system(wounding_system, 'health')
165
165
  assert ecs.eidx[e2]['health'].health == 900
166
166
  assert ecs.cidx['health'][e2].health == 900
167
167
 
168
168
  worked = False
169
169
 
170
- def sys_no_args(dt, eid):
170
+ def sys_no_args(eid):
171
171
  nonlocal worked
172
172
  worked = True
173
173
 
174
- ecs.run_system(0, sys_no_args)
174
+ ecs.run_system(sys_no_args)
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(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(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')
181
194
  ecs.add_system(wounding_system, 'health')
182
195
  ecs.add_component(e2, 'pos', SimpleNamespace(x=100, y=200))
183
196
  ecs.add_component(e2, 'velocity', SimpleNamespace(dx=5, dy=7))
184
- res = ecs.run_all_systems(1)
197
+ res = ecs.run_all_systems(dt=1)
185
198
 
186
199
  assert ecs.eidx[e2]['pos'] == SimpleNamespace(x=105, y=207)
187
200
  assert res[move_system][e2] == (105, 207)
@@ -261,7 +274,7 @@ def test_reset():
261
274
 
262
275
 
263
276
  def test_kill_from_system():
264
- def kill_system(dt, eid, kill):
277
+ def kill_system(eid, kill):
265
278
  ecs.remove_entity(eid)
266
279
 
267
280
  ecs.reset()
@@ -273,7 +286,7 @@ def test_kill_from_system():
273
286
  ecs.add_component(e, 'kill', True)
274
287
 
275
288
  assert len(ecs.eidx) == 10
276
- ecs.run_system(1, kill_system, 'kill')
289
+ ecs.run_system(kill_system, 'kill')
277
290
  assert len(ecs.eidx) == 5
278
291
 
279
292
 
@@ -305,11 +318,11 @@ def test_run_domain():
305
318
  e = ecs.create_entity()
306
319
  ecs.add_component(e, 'ping', Ping(False))
307
320
 
308
- res = ecs.run_domain(1, 'infra')
321
+ res = ecs.run_domain('infra')
309
322
  assert ping_system in res
310
323
  assert e in res[ping_system]
311
324
 
312
- res = ecs.run_domain(1, 'non-existent')
325
+ res = ecs.run_domain('non-existent')
313
326
  assert res == {}
314
327
 
315
328
 
@@ -404,12 +417,12 @@ def test_add_to_archetype():
404
417
  # Also tests remove_from_archetype
405
418
  setup()
406
419
 
407
- def test_system(dt, eid, test):
420
+ def test_system(eid, test):
408
421
  pass
409
422
 
410
423
  e = ecs.create_entity()
411
424
  ecs.add_component(e, 'test', True)
412
- ecs.run_system(0, test_system, 'test')
425
+ ecs.run_system(test_system, 'test')
413
426
  assert e in ecs.archetype[('test',)]
414
427
 
415
428
  ecs.remove_component(e, 'test')
@@ -457,10 +470,10 @@ def test_property_get_entities():
457
470
  def test_property_run_system():
458
471
  setup_property_tests()
459
472
 
460
- def flag_system(dt, eid, comp1, comp2):
473
+ def flag_system(eid, comp1, comp2):
461
474
  ecs.add_component(eid, 'flag', True)
462
475
 
463
- ecs.run_system(0, flag_system, 'comp-1', 'comp-2', has_properties={'is-even'})
476
+ ecs.run_system(flag_system, 'comp-1', 'comp-2', has_properties={'is-even'})
464
477
  assert len(ecs.eids_by_cids('flag')) == 5
465
478
 
466
479
 
File without changes
File without changes
File without changes
File without changes