From f8aa074ee170f064eb1561848c5ff85de8284d93 Mon Sep 17 00:00:00 2001 From: Justin Koh Date: Sun, 14 Sep 2025 14:52:43 +0800 Subject: [PATCH 01/14] 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 02/14] 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 03/14] 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 04/14] 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 05/14] 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 06/14] 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 07/14] 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 08/14] 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 From 789e14d9b37a23f4dcf70c57bca8edfdcbf3bd6e Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sun, 1 Feb 2026 15:59:37 +0900 Subject: [PATCH 09/14] Update benchmark.py Remove unnecessary header --- examples/benchmark.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/benchmark.py b/examples/benchmark.py index 81df088..7325b7f 100644 --- a/examples/benchmark.py +++ b/examples/benchmark.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import gc import sys From 5020dc65215f85b25da95e769267c289d79f0a5e Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sun, 1 Feb 2026 16:00:09 +0900 Subject: [PATCH 10/14] Update benchmark_cache.py Remove unnecessary header --- examples/benchmark_cache.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/benchmark_cache.py b/examples/benchmark_cache.py index 8f76f7b..1c64d91 100644 --- a/examples/benchmark_cache.py +++ b/examples/benchmark_cache.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import sys import time From 6d46017d72137356556c0df8237aa0f641b81f9b Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sun, 1 Feb 2026 16:01:59 +0900 Subject: [PATCH 11/14] Update pygame_example.py Remove unnecessary header --- examples/pygame_example.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/examples/pygame_example.py b/examples/pygame_example.py index 8ebfeb7..920c00f 100644 --- a/examples/pygame_example.py +++ b/examples/pygame_example.py @@ -1,10 +1,4 @@ -# /// script -# requires-python = ">=3.8" -# dependencies = [ -# "esper", -# "pygame", -# ] -# /// +#!/usr/bin/env python from pathlib import Path import pygame import esper From 0add13c5a2b2b3993448c1d1aa4e56fff8230959 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sun, 1 Feb 2026 16:02:47 +0900 Subject: [PATCH 12/14] Update pygame_example.py Add space between different import types --- examples/pygame_example.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/pygame_example.py b/examples/pygame_example.py index 920c00f..43bd6de 100644 --- a/examples/pygame_example.py +++ b/examples/pygame_example.py @@ -1,5 +1,6 @@ #!/usr/bin/env python from pathlib import Path + import pygame import esper From f3321c3fc083701c53f8fa3b57651bd988a8e2a1 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sun, 1 Feb 2026 16:04:50 +0900 Subject: [PATCH 13/14] Update pyglet_example.py Remove unnecessary header --- examples/pyglet_example.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/examples/pyglet_example.py b/examples/pyglet_example.py index f71e6fc..e0da6e7 100644 --- a/examples/pyglet_example.py +++ b/examples/pyglet_example.py @@ -1,10 +1,4 @@ -# /// script -# requires-python = ">=3.8" -# dependencies = [ -# "esper", -# "pyglet", -# ] -# /// +#!/usr/bin/env python import pyglet import esper From fbc5e0dd569b29739ff3fd529b448106f584675a Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sun, 1 Feb 2026 16:05:38 +0900 Subject: [PATCH 14/14] Update pysdl2_example.py Remove unnecessary header --- examples/pysdl2_example.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/examples/pysdl2_example.py b/examples/pysdl2_example.py index 743ead1..09686dc 100644 --- a/examples/pysdl2_example.py +++ b/examples/pysdl2_example.py @@ -1,10 +1,4 @@ -# /// script -# requires-python = ">=3.8" -# dependencies = [ -# "esper", -# "pysdl2", -# ] -# /// +#!/usr/bin/env python from pathlib import Path from sdl2 import * import sdl2.ext as ext