esper 3.7__tar.gz → 3.8__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.
- {esper-3.7 → esper-3.8}/.github/workflows/unit-tests.yml +2 -2
- {esper-3.7 → esper-3.8}/PKG-INFO +1 -1
- {esper-3.7 → esper-3.8}/RELEASE_NOTES +10 -0
- {esper-3.7 → esper-3.8}/esper/__init__.py +16 -8
- {esper-3.7 → esper-3.8}/examples/benchmark.py +8 -10
- {esper-3.7 → esper-3.8}/examples/benchmark_cache.py +7 -10
- {esper-3.7 → esper-3.8}/examples/pygame_example.py +7 -3
- {esper-3.7 → esper-3.8}/examples/pyglet_example.py +2 -1
- {esper-3.7 → esper-3.8}/examples/pysdl2_example.py +8 -4
- {esper-3.7 → esper-3.8}/tests/test_world.py +50 -0
- {esper-3.7 → esper-3.8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {esper-3.7 → esper-3.8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {esper-3.7 → esper-3.8}/.github/ISSUE_TEMPLATE/question-or-comment.md +0 -0
- {esper-3.7 → esper-3.8}/.github/workflows/type-checking.yml +0 -0
- {esper-3.7 → esper-3.8}/.gitignore +0 -0
- {esper-3.7 → esper-3.8}/.mypy.ini +0 -0
- {esper-3.7 → esper-3.8}/.readthedocs.yaml +0 -0
- {esper-3.7 → esper-3.8}/.ruff.toml +0 -0
- {esper-3.7 → esper-3.8}/LICENSE +0 -0
- {esper-3.7 → esper-3.8}/MANIFEST.in +0 -0
- {esper-3.7 → esper-3.8}/README.md +0 -0
- {esper-3.7 → esper-3.8}/docs/Makefile +0 -0
- {esper-3.7 → esper-3.8}/docs/conf.py +0 -0
- {esper-3.7 → esper-3.8}/docs/index.rst +0 -0
- {esper-3.7 → esper-3.8}/docs/make.bat +0 -0
- {esper-3.7 → esper-3.8}/esper/py.typed +0 -0
- {esper-3.7 → esper-3.8}/examples/bluesquare.png +0 -0
- {esper-3.7 → esper-3.8}/examples/headless_example.py +0 -0
- {esper-3.7 → esper-3.8}/examples/pythonista_ios_example.py +0 -0
- {esper-3.7 → esper-3.8}/examples/redsquare.png +0 -0
- {esper-3.7 → esper-3.8}/make.py +0 -0
- {esper-3.7 → esper-3.8}/pyproject.toml +0 -0
- {esper-3.7 → esper-3.8}/tests/__init__.py +0 -0
|
@@ -14,9 +14,9 @@ jobs:
|
|
|
14
14
|
python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13', '3.14', 'pypy-3.10' ]
|
|
15
15
|
steps:
|
|
16
16
|
- name: Python ${{ matrix.python-version }} ${{ matrix.os }}
|
|
17
|
-
uses: actions/checkout@
|
|
17
|
+
uses: actions/checkout@v7
|
|
18
18
|
- name: Set up Python ${{ matrix.python-version }}
|
|
19
|
-
uses: actions/setup-python@
|
|
19
|
+
uses: actions/setup-python@v6
|
|
20
20
|
with:
|
|
21
21
|
python-version: ${{ matrix.python-version }}
|
|
22
22
|
- name: Install test dependencies
|
{esper-3.7 → esper-3.8}/PKG-INFO
RENAMED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
esper 3.8
|
|
2
|
+
=========
|
|
3
|
+
Maintenance release
|
|
4
|
+
|
|
5
|
+
Changes
|
|
6
|
+
-------
|
|
7
|
+
- Fix incorrect return type hints for try_components. #120
|
|
8
|
+
- Fix bug where cached components could be lost when switching worlds. #121
|
|
9
|
+
|
|
10
|
+
|
|
1
11
|
esper 3.7
|
|
2
12
|
=========
|
|
3
13
|
Maintenance release
|
|
@@ -22,7 +22,7 @@ from math import inf
|
|
|
22
22
|
|
|
23
23
|
from itertools import count as _count
|
|
24
24
|
|
|
25
|
-
__version__ = version = '3.
|
|
25
|
+
__version__ = version = '3.8'
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
###################
|
|
@@ -203,9 +203,6 @@ def switch_world(name: str) -> None:
|
|
|
203
203
|
|
|
204
204
|
.. note:: At startup, a "default" World context is active.
|
|
205
205
|
"""
|
|
206
|
-
if name not in _context_map:
|
|
207
|
-
_context_map[name] = (_count(start=1), {}, {}, set(), {}, {}, [], {}, False, {}, {})
|
|
208
|
-
|
|
209
206
|
global _current_world
|
|
210
207
|
global _entity_count
|
|
211
208
|
global _components
|
|
@@ -220,6 +217,17 @@ def switch_world(name: str) -> None:
|
|
|
220
217
|
global event_registry
|
|
221
218
|
global current_world
|
|
222
219
|
|
|
220
|
+
if _current_world is not None:
|
|
221
|
+
_context_map[_current_world] = (
|
|
222
|
+
_entity_count, _components, _entities, _dead_entities,
|
|
223
|
+
_get_component_cache, _get_components_cache,
|
|
224
|
+
_processors, _processors_dict, _cache_dirty,
|
|
225
|
+
process_times, event_registry,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if name not in _context_map:
|
|
229
|
+
_context_map[name] = (_count(start=1), {}, {}, set(), {}, {}, [], {}, False, {}, {})
|
|
230
|
+
|
|
223
231
|
(_entity_count, _components, _entities, _dead_entities, _get_component_cache,
|
|
224
232
|
_get_components_cache, _processors, _processors_dict, _cache_dirty,
|
|
225
233
|
process_times, event_registry) = _context_map[name]
|
|
@@ -461,7 +469,7 @@ def try_remove_component(entity: int, component_type: type[_C]) -> _C | None:
|
|
|
461
469
|
del _components[component_type]
|
|
462
470
|
|
|
463
471
|
clear_cache()
|
|
464
|
-
return _entities[entity].pop(component_type) # type: ignore[no-any-return]
|
|
472
|
+
return _entities[entity].pop(component_type, None) # type: ignore[no-any-return]
|
|
465
473
|
|
|
466
474
|
return None
|
|
467
475
|
|
|
@@ -579,17 +587,17 @@ def try_component(entity: int, component_type: type[_C]) -> _C | None:
|
|
|
579
587
|
|
|
580
588
|
|
|
581
589
|
@_overload
|
|
582
|
-
def try_components(entity: int, __c1: type[_C], __c2: type[_C2]) -> tuple[_C, _C2]:
|
|
590
|
+
def try_components(entity: int, __c1: type[_C], __c2: type[_C2]) -> tuple[_C, _C2] | None:
|
|
583
591
|
...
|
|
584
592
|
|
|
585
593
|
|
|
586
594
|
@_overload
|
|
587
|
-
def try_components(entity: int, __c1: type[_C], __c2: type[_C2], __c3: type[_C3]) -> tuple[_C, _C2, _C3]:
|
|
595
|
+
def try_components(entity: int, __c1: type[_C], __c2: type[_C2], __c3: type[_C3]) -> tuple[_C, _C2, _C3] | None:
|
|
588
596
|
...
|
|
589
597
|
|
|
590
598
|
|
|
591
599
|
@_overload
|
|
592
|
-
def try_components(entity: int, __c1: type[_C], __c2: type[_C2], __c3: type[_C3], __c4: type[_C4]) -> tuple[_C, _C2, _C3, _C4]:
|
|
600
|
+
def try_components(entity: int, __c1: type[_C], __c2: type[_C2], __c3: type[_C3], __c4: type[_C4]) -> tuple[_C, _C2, _C3, _C4] | None:
|
|
593
601
|
...
|
|
594
602
|
|
|
595
603
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
|
|
4
3
|
import gc
|
|
5
4
|
import random
|
|
@@ -26,8 +25,7 @@ parser.add_option("-e", "--entities", dest="entities", action="store", default=5
|
|
|
26
25
|
|
|
27
26
|
MAX_ENTITIES = options.entities
|
|
28
27
|
if MAX_ENTITIES <= 500:
|
|
29
|
-
|
|
30
|
-
sys.exit(1)
|
|
28
|
+
sys.exit("The number of entities must be greater than 500.")
|
|
31
29
|
|
|
32
30
|
if options.walltime:
|
|
33
31
|
print("Benchmarking wall clock time...\n")
|
|
@@ -35,6 +33,12 @@ if options.walltime:
|
|
|
35
33
|
else:
|
|
36
34
|
time_query = time.process_time
|
|
37
35
|
|
|
36
|
+
if options.plot:
|
|
37
|
+
try:
|
|
38
|
+
from matplotlib import pyplot as plt
|
|
39
|
+
except ImportError:
|
|
40
|
+
sys.exit("The matplotlib module is required for plotting results.")
|
|
41
|
+
|
|
38
42
|
|
|
39
43
|
##########################
|
|
40
44
|
# Simple timing decorator:
|
|
@@ -264,13 +268,7 @@ for amount in range(500, MAX_ENTITIES, MAX_ENTITIES//50):
|
|
|
264
268
|
if not options.plot:
|
|
265
269
|
print("\nRun 'benchmark.py --help' for details on plotting this benchmark.")
|
|
266
270
|
|
|
267
|
-
|
|
268
|
-
try:
|
|
269
|
-
from matplotlib import pyplot as plt
|
|
270
|
-
except ImportError:
|
|
271
|
-
print("\nThe matplotlib module is required for plotting results.")
|
|
272
|
-
sys.exit(1)
|
|
273
|
-
|
|
271
|
+
else:
|
|
274
272
|
plt.figure(1)
|
|
275
273
|
lines = []
|
|
276
274
|
for num, result in results.items():
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
|
|
4
3
|
import sys
|
|
5
4
|
import time
|
|
@@ -9,12 +8,6 @@ from dataclasses import dataclass as component
|
|
|
9
8
|
|
|
10
9
|
import esper
|
|
11
10
|
|
|
12
|
-
try:
|
|
13
|
-
from matplotlib import pyplot
|
|
14
|
-
except ImportError:
|
|
15
|
-
print("The matplotlib module is required for this benchmark.")
|
|
16
|
-
raise Exception
|
|
17
|
-
|
|
18
11
|
######################
|
|
19
12
|
# Commandline options:
|
|
20
13
|
######################
|
|
@@ -25,9 +18,13 @@ parser.add_option("-e", "--entities", dest="entities", action="store", default=5
|
|
|
25
18
|
(options, arguments) = parser.parse_args()
|
|
26
19
|
|
|
27
20
|
MAX_ENTITIES = options.entities
|
|
28
|
-
if MAX_ENTITIES <=
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
if MAX_ENTITIES <= 500:
|
|
22
|
+
sys.exit("The number of entities must be greater than 500.")
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
from matplotlib import pyplot
|
|
26
|
+
except ImportError:
|
|
27
|
+
sys.exit("The matplotlib module is required for plotting results.")
|
|
31
28
|
|
|
32
29
|
|
|
33
30
|
##########################
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
|
|
4
|
+
import pygame
|
|
3
5
|
import esper
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
FPS = 60
|
|
7
9
|
RESOLUTION = 720, 480
|
|
8
10
|
|
|
11
|
+
# Parent dir of this script, to find the PNGs regardless of cwd
|
|
12
|
+
path = Path(__file__).parent
|
|
9
13
|
|
|
10
14
|
##################################
|
|
11
15
|
# Define some Components:
|
|
@@ -81,10 +85,10 @@ def run():
|
|
|
81
85
|
# Initialize Esper world, and create a "player" Entity with a few Components.
|
|
82
86
|
player = esper.create_entity()
|
|
83
87
|
esper.add_component(player, Velocity(x=0, y=0))
|
|
84
|
-
esper.add_component(player, Renderable(image=pygame.image.load("redsquare.png"), posx=100, posy=100))
|
|
88
|
+
esper.add_component(player, Renderable(image=pygame.image.load(path / "redsquare.png"), posx=100, posy=100))
|
|
85
89
|
# Another motionless Entity:
|
|
86
90
|
enemy = esper.create_entity()
|
|
87
|
-
esper.add_component(enemy, Renderable(image=pygame.image.load("bluesquare.png"), posx=400, posy=250))
|
|
91
|
+
esper.add_component(enemy, Renderable(image=pygame.image.load(path / "bluesquare.png"), posx=400, posy=250))
|
|
88
92
|
|
|
89
93
|
# Create some Processor instances, and asign them to be processed.
|
|
90
94
|
render_processor = RenderProcessor(window=window)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
1
2
|
import pyglet
|
|
2
3
|
import esper
|
|
3
4
|
|
|
@@ -29,8 +30,8 @@ class MovementProcessor:
|
|
|
29
30
|
def __init__(self, minx, maxx, miny, maxy):
|
|
30
31
|
super().__init__()
|
|
31
32
|
self.minx = minx
|
|
32
|
-
self.miny = miny
|
|
33
33
|
self.maxx = maxx
|
|
34
|
+
self.miny = miny
|
|
34
35
|
self.maxy = maxy
|
|
35
36
|
|
|
36
37
|
def process(self, dt):
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from pathlib import Path
|
|
1
3
|
from sdl2 import *
|
|
2
4
|
import sdl2.ext as ext
|
|
3
5
|
import esper
|
|
@@ -5,6 +7,8 @@ import esper
|
|
|
5
7
|
|
|
6
8
|
RESOLUTION = 720, 480
|
|
7
9
|
|
|
10
|
+
# Parent dir of this script, to find the PNGs regardless of cwd
|
|
11
|
+
path = Path(__file__).parent
|
|
8
12
|
|
|
9
13
|
##################################
|
|
10
14
|
# Define some Components:
|
|
@@ -66,7 +70,7 @@ class RenderProcessor:
|
|
|
66
70
|
destination.y = int(rend.y)
|
|
67
71
|
destination.w = rend.w
|
|
68
72
|
destination.h = rend.h
|
|
69
|
-
SDL_RenderCopy(self.renderer.
|
|
73
|
+
SDL_RenderCopy(self.renderer.sdlrenderer, rend.texture, None, destination)
|
|
70
74
|
self.renderer.present()
|
|
71
75
|
|
|
72
76
|
|
|
@@ -76,7 +80,7 @@ class RenderProcessor:
|
|
|
76
80
|
def texture_from_image(renderer, image_name):
|
|
77
81
|
"""Create an SDL2 Texture from an image file"""
|
|
78
82
|
soft_surface = ext.load_image(image_name)
|
|
79
|
-
texture = SDL_CreateTextureFromSurface(renderer.
|
|
83
|
+
texture = SDL_CreateTextureFromSurface(renderer.sdlrenderer, soft_surface)
|
|
80
84
|
SDL_FreeSurface(soft_surface)
|
|
81
85
|
return texture
|
|
82
86
|
|
|
@@ -94,11 +98,11 @@ def run():
|
|
|
94
98
|
# Initialize Esper world, and create a "player" Entity with a few Components.
|
|
95
99
|
player = esper.create_entity()
|
|
96
100
|
esper.add_component(player, Velocity(x=0, y=0))
|
|
97
|
-
esper.add_component(player, Renderable(texture=texture_from_image(renderer, "redsquare.png"),
|
|
101
|
+
esper.add_component(player, Renderable(texture=texture_from_image(renderer, str(path / "redsquare.png")),
|
|
98
102
|
width=64, height=64, posx=100, posy=100))
|
|
99
103
|
# Another motionless Entity:
|
|
100
104
|
enemy = esper.create_entity()
|
|
101
|
-
esper.add_component(enemy, Renderable(texture=texture_from_image(renderer, "bluesquare.png"),
|
|
105
|
+
esper.add_component(enemy, Renderable(texture=texture_from_image(renderer, str(path / "bluesquare.png")),
|
|
102
106
|
width=64, height=64, posx=400, posy=250))
|
|
103
107
|
|
|
104
108
|
# Create some Processor instances, and asign them to be processed.
|
|
@@ -11,6 +11,7 @@ def _reset_to_zero():
|
|
|
11
11
|
# Wipe out all world contexts
|
|
12
12
|
# and re-create the default.
|
|
13
13
|
esper._context_map.clear()
|
|
14
|
+
esper._current_world = None # type: ignore[assignment]
|
|
14
15
|
esper.switch_world("default")
|
|
15
16
|
|
|
16
17
|
|
|
@@ -709,6 +710,55 @@ def test_delete_world():
|
|
|
709
710
|
with pytest.raises(KeyError):
|
|
710
711
|
esper.delete_world("non_existent")
|
|
711
712
|
|
|
713
|
+
|
|
714
|
+
def test_switch_world_cache_dirty():
|
|
715
|
+
"""Verify that switching worlds preserves the cache dirty flag.
|
|
716
|
+
|
|
717
|
+
This test reproduces the bug from issue #121: get_component returns
|
|
718
|
+
stale data after switching to an already-existing world that was
|
|
719
|
+
modified by add_component.
|
|
720
|
+
"""
|
|
721
|
+
esper.switch_world("issue121_test")
|
|
722
|
+
|
|
723
|
+
class Component:
|
|
724
|
+
pass
|
|
725
|
+
|
|
726
|
+
e = esper.create_entity()
|
|
727
|
+
|
|
728
|
+
c = esper.get_component(Component)
|
|
729
|
+
assert len(c) == 0
|
|
730
|
+
|
|
731
|
+
esper.add_component(e, Component())
|
|
732
|
+
|
|
733
|
+
esper.switch_world("issue121_test")
|
|
734
|
+
|
|
735
|
+
c = esper.get_component(Component)
|
|
736
|
+
assert len(c) == 1
|
|
737
|
+
|
|
738
|
+
esper.switch_world("default")
|
|
739
|
+
esper.delete_world("issue121_test")
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def test_switch_world_same_entities_preserved():
|
|
743
|
+
"""Verify that switching to a world and back preserves entities and components."""
|
|
744
|
+
esper.switch_world("world_a")
|
|
745
|
+
e = esper.create_entity()
|
|
746
|
+
esper.add_component(e, ComponentA())
|
|
747
|
+
|
|
748
|
+
esper.switch_world("world_b")
|
|
749
|
+
c = esper.get_component(ComponentA)
|
|
750
|
+
assert len(c) == 0
|
|
751
|
+
|
|
752
|
+
esper.switch_world("world_a")
|
|
753
|
+
c = esper.get_component(ComponentA)
|
|
754
|
+
assert len(c) == 1
|
|
755
|
+
assert c[0][1] is not None
|
|
756
|
+
|
|
757
|
+
esper.switch_world("default")
|
|
758
|
+
esper.delete_world("world_a")
|
|
759
|
+
esper.delete_world("world_b")
|
|
760
|
+
|
|
761
|
+
|
|
712
762
|
##################################################
|
|
713
763
|
# Some helper functions and Component templates:
|
|
714
764
|
##################################################
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{esper-3.7 → esper-3.8}/LICENSE
RENAMED
|
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
|
{esper-3.7 → esper-3.8}/make.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|