esper 3.2__tar.gz → 3.3__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 (35) hide show
  1. esper-3.3/.ruff.toml +2 -0
  2. {esper-3.2 → esper-3.3}/LICENSE +1 -1
  3. {esper-3.2 → esper-3.3}/PKG-INFO +2 -2
  4. {esper-3.2 → esper-3.3}/README.md +1 -1
  5. {esper-3.2 → esper-3.3}/RELEASE_NOTES +11 -0
  6. {esper-3.2 → esper-3.3}/esper/__init__.py +18 -22
  7. {esper-3.2 → esper-3.3}/examples/pyglet_example.py +1 -1
  8. esper-3.2/.ruff.toml +0 -3
  9. esper-3.2/examples/pyglet_example_batch.py +0 -245
  10. {esper-3.2 → esper-3.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  11. {esper-3.2 → esper-3.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  12. {esper-3.2 → esper-3.3}/.github/ISSUE_TEMPLATE/question-or-comment.md +0 -0
  13. {esper-3.2 → esper-3.3}/.github/workflows/type-checking.yml +0 -0
  14. {esper-3.2 → esper-3.3}/.github/workflows/unit-tests.yml +0 -0
  15. {esper-3.2 → esper-3.3}/.gitignore +0 -0
  16. {esper-3.2 → esper-3.3}/.mypy.ini +0 -0
  17. {esper-3.2 → esper-3.3}/.readthedocs.yaml +0 -0
  18. {esper-3.2 → esper-3.3}/MANIFEST.in +0 -0
  19. {esper-3.2 → esper-3.3}/docs/Makefile +0 -0
  20. {esper-3.2 → esper-3.3}/docs/conf.py +0 -0
  21. {esper-3.2 → esper-3.3}/docs/index.rst +0 -0
  22. {esper-3.2 → esper-3.3}/docs/make.bat +0 -0
  23. {esper-3.2 → esper-3.3}/esper/py.typed +0 -0
  24. {esper-3.2 → esper-3.3}/examples/benchmark.py +0 -0
  25. {esper-3.2 → esper-3.3}/examples/benchmark_cache.py +0 -0
  26. {esper-3.2 → esper-3.3}/examples/bluesquare.png +0 -0
  27. {esper-3.2 → esper-3.3}/examples/headless_example.py +0 -0
  28. {esper-3.2 → esper-3.3}/examples/pygame_example.py +0 -0
  29. {esper-3.2 → esper-3.3}/examples/pysdl2_example.py +0 -0
  30. {esper-3.2 → esper-3.3}/examples/pythonista_ios_example.py +0 -0
  31. {esper-3.2 → esper-3.3}/examples/redsquare.png +0 -0
  32. {esper-3.2 → esper-3.3}/make.py +0 -0
  33. {esper-3.2 → esper-3.3}/pyproject.toml +0 -0
  34. {esper-3.2 → esper-3.3}/tests/__init__.py +0 -0
  35. {esper-3.2 → esper-3.3}/tests/test_world.py +0 -0
esper-3.3/.ruff.toml ADDED
@@ -0,0 +1,2 @@
1
+ lint.select = ["F", "E", "W", "C90", "B", "A", "C4", "G", "T20", "PT", "SLF", "TCH", "RET", "N", "ARG"]
2
+ lint.ignore = ["E501"]
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2023 Benjamin Moran
3
+ Copyright (c) 2024 Benjamin Moran
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: esper
3
- Version: 3.2
3
+ Version: 3.3
4
4
  Summary: esper is a lightweight Entity System (ECS) for Python, with a focus on performance
5
5
  Author-email: Benjamin Moran <benmoran@protonmail.com>
6
6
  Requires-Python: >=3.8
@@ -75,7 +75,7 @@ Design
75
75
  * World Context
76
76
 
77
77
  Esper uses the concept of "World" contexts. When you first `import esper`, a default context is
78
- active. You create Entities, assign Components, register Processesors, etc., by calling functions
78
+ active. You create Entities, assign Components, register Processors, etc., by calling functions
79
79
  on the `esper` module. Entities, Components and Processors can be created, assigned, or deleted
80
80
  while your game is running. A simple call to `esper.process()` is all that's needed for each
81
81
  iteration of your game loop. Advanced users can switch contexts, which can be useful for
@@ -65,7 +65,7 @@ Design
65
65
  * World Context
66
66
 
67
67
  Esper uses the concept of "World" contexts. When you first `import esper`, a default context is
68
- active. You create Entities, assign Components, register Processesors, etc., by calling functions
68
+ active. You create Entities, assign Components, register Processors, etc., by calling functions
69
69
  on the `esper` module. Entities, Components and Processors can be created, assigned, or deleted
70
70
  while your game is running. A simple call to `esper.process()` is all that's needed for each
71
71
  iteration of your game loop. Advanced users can switch contexts, which can be useful for
@@ -1,3 +1,14 @@
1
+ esper 3.3
2
+ =========
3
+ Maintenance release
4
+
5
+ Changes
6
+ -------
7
+ - Fix unreadable `esper.current_world` property. (#100)
8
+ - Minor typing configuration updates.
9
+ - Remove outdated pyglet example. (Another one already exists).
10
+
11
+
1
12
  esper 3.2
2
13
  =========
3
14
  Maintenance release
@@ -23,7 +23,7 @@ from weakref import WeakMethod as _WeakMethod
23
23
 
24
24
  from itertools import count as _count
25
25
 
26
- __version__ = version = '3.2'
26
+ __version__ = version = '3.3'
27
27
 
28
28
 
29
29
  ###################
@@ -131,7 +131,7 @@ class Processor:
131
131
  # ECS functions
132
132
  ###################
133
133
 
134
- _current_context: str = "default"
134
+ _current_world: str = "default"
135
135
  _entity_count: "_count[int]" = _count(start=1)
136
136
  _components: _Dict[_Type[_Any], _Set[_Any]] = {}
137
137
  _entities: _Dict[int, _Dict[_Type[_Any], _Any]] = {}
@@ -141,8 +141,16 @@ _get_components_cache: _Dict[_Tuple[_Type[_Any], ...], _List[_Any]] = {}
141
141
  _processors: _List[Processor] = []
142
142
  process_times: _Dict[str, int] = {}
143
143
  event_registry: _Dict[str, _Any] = {}
144
+ current_world: str = "default"
145
+ """The name of the currently active World context.
144
146
 
145
- # {context_name: (entity_count, components, entities, dead_entities, comp_cache, comps_cache, processors, process_times, event_registry)}
147
+ This attribute can be checked to confirm the name of the
148
+ currently active World context. Modifying this has no effect;
149
+ to switch Worlds, use the :py:func:`~switch_world` function.
150
+ """
151
+
152
+ # {context_name: (entity_count, components, entities, dead_entities,
153
+ # comp_cache, comps_cache, processors, process_times, event_registry)}
146
154
  _context_map: _Dict[str, _Tuple[
147
155
  "_count[int]",
148
156
  _Dict[_Type[_Any], _Set[_Any]],
@@ -387,9 +395,7 @@ def get_component(component_type: _Type[_C]) -> _List[_Tuple[int, _C]]:
387
395
  try:
388
396
  return _get_component_cache[component_type]
389
397
  except KeyError:
390
- return _get_component_cache.setdefault(
391
- component_type, list(_get_component(component_type))
392
- )
398
+ return _get_component_cache.setdefault(component_type, list(_get_component(component_type)))
393
399
 
394
400
 
395
401
  @_overload
@@ -404,7 +410,7 @@ def get_components(__c1: _Type[_C], __c2: _Type[_C2], __c3: _Type[_C3]) -> _List
404
410
 
405
411
  @_overload
406
412
  def get_components(__c1: _Type[_C], __c2: _Type[_C2], __c3: _Type[_C3], __c4: _Type[_C4]) -> _List[
407
- _Tuple[int, _Tuple[_C, _C2, _C3, _C4]]]:
413
+ _Tuple[int, _Tuple[_C, _C2, _C3, _C4]]]:
408
414
  ...
409
415
 
410
416
 
@@ -413,9 +419,7 @@ def get_components(*component_types: _Type[_Any]) -> _Iterable[_Tuple[int, _Tupl
413
419
  try:
414
420
  return _get_components_cache[component_types]
415
421
  except KeyError:
416
- return _get_components_cache.setdefault(
417
- component_types, list(_get_components(*component_types))
418
- )
422
+ return _get_components_cache.setdefault(component_types, list(_get_components(*component_types)))
419
423
 
420
424
 
421
425
  def try_component(entity: int, component_type: _Type[_C]) -> _Optional[_C]:
@@ -525,7 +529,7 @@ def delete_world(name: str) -> None:
525
529
  Raises `PermissionError` if you attempt to delete the currently
526
530
  active World context.
527
531
  """
528
- if _current_context == name:
532
+ if _current_world == name:
529
533
  raise PermissionError("The active World context cannot be deleted.")
530
534
 
531
535
  del _context_map[name]
@@ -550,7 +554,7 @@ def switch_world(name: str) -> None:
550
554
  # Create a new context if the name does not already exist:
551
555
  _context_map[name] = (_count(start=1), {}, {}, set(), {}, {}, [], {}, {})
552
556
 
553
- global _current_context
557
+ global _current_world
554
558
  global _entity_count
555
559
  global _components
556
560
  global _entities
@@ -560,17 +564,9 @@ def switch_world(name: str) -> None:
560
564
  global _processors
561
565
  global process_times
562
566
  global event_registry
567
+ global current_world
563
568
 
564
569
  # switch the references to the objects in the named context_map:
565
570
  (_entity_count, _components, _entities, _dead_entities, _get_component_cache,
566
571
  _get_components_cache, _processors, process_times, event_registry) = _context_map[name]
567
- _current_context = name
568
-
569
-
570
- @property # type: ignore
571
- def current_world() -> str:
572
- """The currently active World context.
573
-
574
- To switch World contexts, see :py:func:`esper.switch_world`.
575
- """
576
- return _current_context
572
+ _current_world = current_world = name
@@ -36,7 +36,7 @@ class MovementProcessor:
36
36
  def process(self, dt):
37
37
  # This will iterate over every Entity that has BOTH of these components:
38
38
  for ent, (vel, rend) in esper.get_components(Velocity, Renderable):
39
- # Update the Renderable Component's position by it's Velocity:
39
+ # Update the Renderable Component's position by its Velocity:
40
40
  # An example of keeping the sprite inside screen boundaries. Basically,
41
41
  # adjust the position back inside screen boundaries if it is outside:
42
42
  new_x = max(self.minx, rend.sprite.x + vel.x)
esper-3.2/.ruff.toml DELETED
@@ -1,3 +0,0 @@
1
- select = ["F", "E", "W", "C90", "B", "A", "C4", "G", "T20", "PT", "SLF", "TCH", "RET", "N", "ARG"]
2
- ignore = ["E501"]
3
-
@@ -1,245 +0,0 @@
1
- import pyglet
2
- from pyglet.gl import *
3
- from pyglet.window import key
4
-
5
- import esper
6
-
7
-
8
- FPS = 60
9
- RESOLUTION = 720, 480
10
- BGCOLOR = (0, 0, 0, 255)
11
-
12
-
13
- ##################################
14
- # Define some Components:
15
- ##################################
16
- class Velocity:
17
- def __init__(self, x=0.0, y=0.0):
18
- self.x = x
19
- self.y = y
20
-
21
-
22
- class Renderable:
23
- def __init__(self, texture, width, height, posx, posy):
24
- self.texture = texture
25
- self._x = posx
26
- self._y = posy
27
- self.w = width
28
- self.h = height
29
- self.group = TextureBindGroup(texture)
30
- self.vertex_list = None
31
- self._dirty = True
32
-
33
- @property
34
- def x(self):
35
- return self._x
36
-
37
- @x.setter
38
- def x(self, val):
39
- if val != self._x:
40
- self._x = val
41
- self._dirty = True
42
-
43
- @property
44
- def y(self):
45
- return self._y
46
-
47
- @y.setter
48
- def y(self, val):
49
- if val != self._y:
50
- self._y = val
51
- self._dirty = True
52
-
53
-
54
- ################################
55
- # Define some Processors:
56
- ################################
57
- class MovementProcessor:
58
- def __init__(self, minx, miny, maxx, maxy):
59
- super().__init__()
60
- self.minx = minx
61
- self.miny = miny
62
- self.maxx = maxx
63
- self.maxy = maxy
64
-
65
- def process(self):
66
- # This will iterate over every Entity that has BOTH of these components:
67
- for ent, (vel, rend) in esper.get_components(Velocity, Renderable):
68
- # Update the Renderable Component's position by it's Velocity:
69
- rend.x += vel.x
70
- rend.y += vel.y
71
- # An example of keeping the sprite inside screen boundaries. Basically,
72
- # adjust the position back inside screen boundaries if it tries to go outside:
73
- rend.x = max(self.minx, rend.x)
74
- rend.y = max(self.miny, rend.y)
75
- rend.x = min(self.maxx - rend.w, rend.x)
76
- rend.y = min(self.maxy - rend.h, rend.y)
77
-
78
-
79
- class TextureRenderProcessor():
80
- def __init__(self, batch):
81
- super().__init__()
82
- self.batch = batch
83
-
84
- def process(self):
85
- # This will iterate over every Entity that has this Component, and
86
- # add the texture associated with the Renderable Component instance
87
- # and its vertice_list to the render batch. The batch will then be
88
- # drawn by the 'on_draw' event handler of teh main window:
89
- for entity, renderable in esper.get_component(Renderable):
90
- self.draw_texture(renderable)
91
-
92
- def draw_texture(self, renderable):
93
- texture = renderable.texture
94
-
95
- if renderable.vertex_list is None:
96
- vertex_format = 'v2i/dynamic'
97
- renderable.vertex_list = self.batch.add(4, GL_QUADS,
98
- renderable.group,
99
- vertex_format, 'c4B',
100
- ('t3f', texture.tex_coords))
101
-
102
- if renderable._dirty:
103
- x1 = renderable.x - texture.anchor_x
104
- y1 = renderable.y - texture.anchor_y
105
- x2 = x1 + texture.width
106
- y2 = y1 + texture.height
107
- renderable.vertex_list.vertices[:] = [x1, y1, x2, y1, x2, y2, x1, y2]
108
- renderable.vertex_list.colors[:] = [255, 255, 255, 255] * 4
109
- renderable._dirty = False
110
-
111
-
112
- ############################################
113
- # Some pyglet helper classes and functions:
114
- ############################################
115
- def texture_from_image(image_name):
116
- """Create a pyglet Texture from an image file"""
117
- return pyglet.resource.image(image_name).get_texture()
118
-
119
-
120
- # Code below cobbled together from
121
- # https://pyglet.readthedocs.org/en/latest/programming_guide/graphics.html#hierarchical-state
122
- # and pyglet.sprite.SpriteGroup
123
-
124
- class TextureEnableGroup(pyglet.graphics.Group):
125
- def set_state(self):
126
- glEnable(GL_TEXTURE_2D)
127
-
128
- def unset_state(self):
129
- glDisable(GL_TEXTURE_2D)
130
-
131
-
132
- texture_enable_group = TextureEnableGroup()
133
-
134
-
135
- class TextureBindGroup(pyglet.graphics.Group):
136
- def __init__(self, texture):
137
- super().__init__(parent=texture_enable_group)
138
- assert texture.target == GL_TEXTURE_2D
139
- self.texture = texture
140
- self.blend_src = GL_SRC_ALPHA
141
- self.blend_dest = GL_ONE_MINUS_SRC_ALPHA
142
-
143
- def set_state(self):
144
- glBindTexture(GL_TEXTURE_2D, self.texture.id)
145
- glPushAttrib(GL_COLOR_BUFFER_BIT)
146
- glEnable(GL_BLEND)
147
- glBlendFunc(self.blend_src, self.blend_dest)
148
-
149
- def unset_state(self):
150
- glPopAttrib()
151
- glDisable(self.texture.target)
152
-
153
- def __eq__(self, other):
154
- return (other.__class__ is self.__class__ and
155
- self.parent is other.parent and
156
- self.texture.target == other.texture.target and
157
- self.texture.id == other.texture.id and
158
- self.blend_src == other.blend_src and
159
- self.blend_dest == other.blend_dest)
160
-
161
- def __hash__(self):
162
- return hash((id(self.parent),
163
- self.texture.id, self.texture.target,
164
- self.blend_src, self.blend_dest))
165
-
166
-
167
- ################################
168
- # The main core of the program:
169
- ################################
170
- def run(args=None):
171
- # Initialize the main window stuff
172
- window = pyglet.window.Window(width=RESOLUTION[0], height=RESOLUTION[1])
173
- window.set_caption("Esper pyglet Example")
174
- pyglet.gl.glClearColor(*BGCOLOR)
175
- # pyglet graphics batch for efficient rendering
176
- renderbatch = pyglet.graphics.Batch()
177
-
178
- # Initialize Esper world, and create a "player" Entity with a few Components.
179
- player = esper.create_entity()
180
- esper.add_component(player, Velocity(x=0, y=0))
181
- redsquare = Renderable(texture=texture_from_image("redsquare.png"),
182
- width=64,
183
- height=64,
184
- posx=100,
185
- posy=100)
186
- esper.add_component(player, redsquare)
187
-
188
- # Another motionless Entity:
189
- enemy = esper.create_entity()
190
- bluesquare = Renderable(texture=texture_from_image("bluesquare.png"),
191
- width=64,
192
- height=64,
193
- posx=400,
194
- posy=250)
195
- esper.add_component(enemy, bluesquare)
196
-
197
- # Create some Processor instances, and asign them to be processed.
198
- render_processor = TextureRenderProcessor(batch=renderbatch)
199
- movement_processor = MovementProcessor(minx=0, maxx=RESOLUTION[0], miny=0,
200
- maxy=RESOLUTION[1])
201
-
202
- @window.event
203
- def on_key_press(symbol, modifiers):
204
- if symbol == key.UP:
205
- # Here is a way to directly access a specific Entity's Velocity
206
- # Component's attribute (y) without making a temporary variable.
207
- esper.component_for_entity(player, Velocity).y = 3
208
- elif symbol == key.DOWN:
209
- # For clarity, here is an alternate way in which a temporary variable
210
- # is created and modified. The previous way above is recommended instead.
211
- player_velocity_component = esper.component_for_entity(player, Velocity)
212
- player_velocity_component.y = -3
213
- elif symbol == key.LEFT:
214
- esper.component_for_entity(player, Velocity).x = -3
215
- elif symbol == key.RIGHT:
216
- esper.component_for_entity(player, Velocity).x = 3
217
- elif symbol == key.ESCAPE:
218
- pyglet.app.exit()
219
-
220
- @window.event
221
- def on_key_release(symbol, modifiers):
222
- if symbol in (key.UP, key.DOWN):
223
- esper.component_for_entity(player, Velocity).y = 0
224
- if symbol in (key.LEFT, key.RIGHT):
225
- esper.component_for_entity(player, Velocity).x = 0
226
-
227
- @window.event
228
- def on_draw():
229
- # Clear the window:
230
- window.clear()
231
- # Draw the batch of Renderables:
232
- renderbatch.draw()
233
-
234
- def update(dt):
235
- # A single call to esper.process() will update all Processors:
236
- render_processor.process()
237
- movement_processor.process()
238
-
239
- pyglet.clock.schedule_interval(update, 1.0 / FPS)
240
- pyglet.app.run()
241
-
242
-
243
- if __name__ == "__main__":
244
- import sys
245
- sys.exit(run(sys.argv[1:]) or 0)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes