From f8aa074ee170f064eb1561848c5ff85de8284d93 Mon Sep 17 00:00:00 2001 From: Justin Koh Date: Sun, 14 Sep 2025 14:52:43 +0800 Subject: [PATCH 1/8] Add inline metadata to example scripts This allows easy demos without prior setup, using tools that support PEP 723 inline metadata, e.g.: git clone cd esper uv run --script examples/pyglet_example.py --- examples/pygame_example.py | 7 +++++++ examples/pyglet_example.py | 7 +++++++ examples/pysdl2_example.py | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/examples/pygame_example.py b/examples/pygame_example.py index 3480c8a..cb6619e 100644 --- a/examples/pygame_example.py +++ b/examples/pygame_example.py @@ -1,3 +1,10 @@ +# /// script +# requires-python = ">=3.8" +# dependencies = [ +# "esper", +# "pygame", +# ] +# /// import pygame import esper diff --git a/examples/pyglet_example.py b/examples/pyglet_example.py index f263df7..c2522c0 100644 --- a/examples/pyglet_example.py +++ b/examples/pyglet_example.py @@ -1,3 +1,10 @@ +# /// script +# requires-python = ">=3.8" +# dependencies = [ +# "esper", +# "pyglet", +# ] +# /// import pyglet import esper diff --git a/examples/pysdl2_example.py b/examples/pysdl2_example.py index c3bf53f..e78a586 100644 --- a/examples/pysdl2_example.py +++ b/examples/pysdl2_example.py @@ -1,3 +1,10 @@ +# /// script +# requires-python = ">=3.8" +# dependencies = [ +# "esper", +# "pysdl2", +# ] +# /// from sdl2 import * import sdl2.ext as ext import esper From 067ee5d55668922d9be5d3e88c80a6d89dab3039 Mon Sep 17 00:00:00 2001 From: Justin Koh Date: Sun, 14 Sep 2025 15:24:34 +0800 Subject: [PATCH 2/8] Fix running pygame and pysdl2 examples from different cwd Unlike pyglet.resource, pygame.image.load() and sdl2.ext.load_image() do not search relative to the script. This caused the latter two to fail when running from a different dir like the repo root: uv run --script examples/pygame_example.py # FileNotFoundError cd examples uv run --script pygame_example.py # ok Now both cases work. --- examples/pygame_example.py | 8 +++++--- examples/pysdl2_example.py | 7 +++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/pygame_example.py b/examples/pygame_example.py index cb6619e..8ebfeb7 100644 --- a/examples/pygame_example.py +++ b/examples/pygame_example.py @@ -5,14 +5,16 @@ # "pygame", # ] # /// +from pathlib import Path import pygame - import esper FPS = 60 RESOLUTION = 720, 480 +# Parent dir of this script, to find the PNGs regardless of cwd +path = Path(__file__).parent ################################## # Define some Components: @@ -88,10 +90,10 @@ def run(): # Initialize Esper world, and create a "player" Entity with a few Components. player = esper.create_entity() esper.add_component(player, Velocity(x=0, y=0)) - esper.add_component(player, Renderable(image=pygame.image.load("redsquare.png"), posx=100, posy=100)) + esper.add_component(player, Renderable(image=pygame.image.load(path / "redsquare.png"), posx=100, posy=100)) # Another motionless Entity: enemy = esper.create_entity() - esper.add_component(enemy, Renderable(image=pygame.image.load("bluesquare.png"), posx=400, posy=250)) + esper.add_component(enemy, Renderable(image=pygame.image.load(path / "bluesquare.png"), posx=400, posy=250)) # Create some Processor instances, and asign them to be processed. render_processor = RenderProcessor(window=window) diff --git a/examples/pysdl2_example.py b/examples/pysdl2_example.py index e78a586..aafe3a5 100644 --- a/examples/pysdl2_example.py +++ b/examples/pysdl2_example.py @@ -5,6 +5,7 @@ # "pysdl2", # ] # /// +from pathlib import Path from sdl2 import * import sdl2.ext as ext import esper @@ -12,6 +13,8 @@ RESOLUTION = 720, 480 +# Parent dir of this script, to find the PNGs regardless of cwd +path = Path(__file__).parent ################################## # Define some Components: @@ -101,11 +104,11 @@ def run(): # Initialize Esper world, and create a "player" Entity with a few Components. player = esper.create_entity() esper.add_component(player, Velocity(x=0, y=0)) - esper.add_component(player, Renderable(texture=texture_from_image(renderer, "redsquare.png"), + esper.add_component(player, Renderable(texture=texture_from_image(renderer, str(path / "redsquare.png")), width=64, height=64, posx=100, posy=100)) # Another motionless Entity: enemy = esper.create_entity() - esper.add_component(enemy, Renderable(texture=texture_from_image(renderer, "bluesquare.png"), + esper.add_component(enemy, Renderable(texture=texture_from_image(renderer, str(path / "bluesquare.png")), width=64, height=64, posx=400, posy=250)) # Create some Processor instances, and asign them to be processed. From 84dec1cf34faf8612a66fba7e287e4fb124942a4 Mon Sep 17 00:00:00 2001 From: Justin Koh Date: Sun, 14 Sep 2025 15:30:15 +0800 Subject: [PATCH 3/8] Reorder vars to match arg order and other examples --- examples/pyglet_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/pyglet_example.py b/examples/pyglet_example.py index c2522c0..f71e6fc 100644 --- a/examples/pyglet_example.py +++ b/examples/pyglet_example.py @@ -36,8 +36,8 @@ class MovementProcessor: def __init__(self, minx, maxx, miny, maxy): super().__init__() self.minx = minx - self.miny = miny self.maxx = maxx + self.miny = miny self.maxy = maxy def process(self, dt): From cdcf304a103f4e407d16f4cd9aee8e3aa9929322 Mon Sep 17 00:00:00 2001 From: Justin Koh Date: Sun, 14 Sep 2025 16:08:24 +0800 Subject: [PATCH 4/8] Fix pysdl2 deprecation warnings --- examples/pysdl2_example.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/pysdl2_example.py b/examples/pysdl2_example.py index aafe3a5..743ead1 100644 --- a/examples/pysdl2_example.py +++ b/examples/pysdl2_example.py @@ -76,7 +76,7 @@ def process(self): destination.y = int(rend.y) destination.w = rend.w destination.h = rend.h - SDL_RenderCopy(self.renderer.renderer, rend.texture, None, destination) + SDL_RenderCopy(self.renderer.sdlrenderer, rend.texture, None, destination) self.renderer.present() @@ -86,7 +86,7 @@ def process(self): def texture_from_image(renderer, image_name): """Create an SDL2 Texture from an image file""" soft_surface = ext.load_image(image_name) - texture = SDL_CreateTextureFromSurface(renderer.renderer, soft_surface) + texture = SDL_CreateTextureFromSurface(renderer.sdlrenderer, soft_surface) SDL_FreeSurface(soft_surface) return texture From e3769e66b65d7dd6b790ce7faab958248f3ca0bf Mon Sep 17 00:00:00 2001 From: Justin Koh Date: Sun, 14 Sep 2025 16:20:37 +0800 Subject: [PATCH 5/8] Exit early if --plot is used without matplotlib --- examples/benchmark.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/benchmark.py b/examples/benchmark.py index c8a33eb..d36eb09 100644 --- a/examples/benchmark.py +++ b/examples/benchmark.py @@ -34,6 +34,12 @@ else: time_query = time.process_time +if options.plot: + try: + from matplotlib import pyplot as plt + except ImportError: + sys.exit("The matplotlib module is required for plotting results.") + ########################## # Simple timing decorator: @@ -169,14 +175,7 @@ def three_comp_query(): if not options.plot: print("\nRun 'benchmark.py --help' for details on plotting this benchmark.") - -if options.plot: - try: - from matplotlib import pyplot as plt - except ImportError: - print("\nThe matplotlib module is required for plotting results.") - sys.exit(1) - +else: lines = [] for num, result in results.items(): x, y = zip(*sorted(result.items())) From f1a1e5c85a628b1538158f45c1b453451ad680ab Mon Sep 17 00:00:00 2001 From: Justin Koh Date: Sun, 14 Sep 2025 16:56:51 +0800 Subject: [PATCH 6/8] Move expensive import after optparse This doesn't change the benchmark, it was just annoying waiting ~0.4s for --help to return. Now should be instant. --- examples/benchmark_cache.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/benchmark_cache.py b/examples/benchmark_cache.py index f8ad90b..08f804e 100644 --- a/examples/benchmark_cache.py +++ b/examples/benchmark_cache.py @@ -9,12 +9,6 @@ import esper -try: - from matplotlib import pyplot -except ImportError: - print("The matplotlib module is required for this benchmark.") - raise Exception - ###################### # Commandline options: ###################### @@ -29,6 +23,11 @@ print("The number of entities must be greater than 500.") sys.exit(1) +try: + from matplotlib import pyplot +except ImportError: + sys.exit("The matplotlib module is required for plotting results.") + ########################## # Simple timing decorator: From d8d5cce2f0c2f5cf6b65278b9b0b56dc8fa55514 Mon Sep 17 00:00:00 2001 From: Justin Koh Date: Sun, 14 Sep 2025 16:42:05 +0800 Subject: [PATCH 7/8] Fix typo allowing too few entities --- examples/benchmark_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/benchmark_cache.py b/examples/benchmark_cache.py index 08f804e..3bf4538 100644 --- a/examples/benchmark_cache.py +++ b/examples/benchmark_cache.py @@ -19,7 +19,7 @@ (options, arguments) = parser.parse_args() MAX_ENTITIES = options.entities -if MAX_ENTITIES <= 50: +if MAX_ENTITIES <= 500: print("The number of entities must be greater than 500.") sys.exit(1) From b436c7a3bb7be39183a3a91d3c307ada624dfbf0 Mon Sep 17 00:00:00 2001 From: Justin Koh Date: Sun, 14 Sep 2025 17:30:17 +0800 Subject: [PATCH 8/8] Simplify sys.exit This also conveniently prints to stderr instead of stdout. Return code is still 1. --- examples/benchmark.py | 3 +-- examples/benchmark_cache.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/benchmark.py b/examples/benchmark.py index d36eb09..81df088 100644 --- a/examples/benchmark.py +++ b/examples/benchmark.py @@ -25,8 +25,7 @@ MAX_ENTITIES = options.entities if MAX_ENTITIES <= 500: - print("The number of entities must be greater than 500.") - sys.exit(1) + sys.exit("The number of entities must be greater than 500.") if options.walltime: print("Benchmarking wall clock time...\n") diff --git a/examples/benchmark_cache.py b/examples/benchmark_cache.py index 3bf4538..8f76f7b 100644 --- a/examples/benchmark_cache.py +++ b/examples/benchmark_cache.py @@ -20,8 +20,7 @@ MAX_ENTITIES = options.entities if MAX_ENTITIES <= 500: - print("The number of entities must be greater than 500.") - sys.exit(1) + sys.exit("The number of entities must be greater than 500.") try: from matplotlib import pyplot