]> Untitled Git - axy/ft/pacman.git/commitdiff
Assets
author= <=>
Mon, 28 Sep 2026 11:26:08 +0000 (13:26 +0200)
committer= <=>
Mon, 28 Sep 2026 11:26:08 +0000 (13:26 +0200)
src/pacman/ecs/__init__.py
src/pacman/ecs/assets.py [new file with mode: 0644]

index df086dd608e36e88172a5f862c3d40cbfa2c1a77..6b3c0e2fefbe75a7c3b1c091a629292476ecde90 100644 (file)
@@ -1,9 +1,11 @@
 from dataclasses import dataclass
 from pathlib import Path
+from typing import override
 
 import pygame
 from pygame import Vector3
 
+from pacman.ecs.assets import Asset, Assets
 from pacman.ecs.hierarchy import (
     ChildOf,
     Hierarchy,
@@ -114,9 +116,15 @@ def init_window(world: World) -> None:
 
 
 @dataclass
-class Sprite2D(Component):
+class Sprite2D(Component, Asset):
     surface: pygame.Surface
 
+    @override
+    @classmethod
+    def load(cls, ty: type, path: str) -> "Sprite2D":
+        del ty
+        return Sprite2D(pygame.image.load(path))
+
 
 def poll_events(events: WindowEvents) -> None:
     events.events = pygame.event.get()
@@ -178,17 +186,8 @@ def move_root(root: Query[Coord2D, WiggleRoot]) -> None:
         coord.x = 30.0 + math.cos(tick) * 30.0
 
 
-if __name__ == "__main__":
-    world = (
-        World()
-        .with_plugins(minimal_plugins, graphics_plugins)
-        .with_systems(
-            UpdateSchedule,
-            move_sprites,
-            move_root,
-        )
-    )
-    assets = list(Path("./asset").rglob("*.png"))
+def setup_world(world: World, assets: Assets[Sprite2D]) -> None:
+    asset_paths = list(Path("./asset").rglob("*.png"))
     root = world.spawn(
         tuple[Coord2D, CoordAbs2D, WiggleRoot],
         (
@@ -209,19 +208,32 @@ if __name__ == "__main__":
         [
             (
                 Coord2D(
-                    30.0 * (n := (i + j) % len(assets)),
+                    30.0 * (n := (i + j) % len(asset_paths)),
                     500.0 + 10.0,
                     1.0 * n + j,
                 ),
                 CoordAbs2D(),
-                Sprite2D(pygame.image.load(asset)),
+                assets.load(asset_path.as_posix()),
                 ShouldRender(),
                 WiggleSprite(),
                 ChildOf(root),
             )
             for j in range(3)
-            for i, asset in enumerate(assets)
+            for i, asset_path in enumerate(asset_paths)
         ],
     )
+    pass
 
+
+if __name__ == "__main__":
+    world = (
+        World()
+        .with_plugins(minimal_plugins, graphics_plugins)
+        .with_systems(StartupSchedule, setup_world)
+        .with_systems(
+            UpdateSchedule,
+            move_sprites,
+            move_root,
+        )
+    )
     world.run_main()
diff --git a/src/pacman/ecs/assets.py b/src/pacman/ecs/assets.py
new file mode 100644 (file)
index 0000000..0b80925
--- /dev/null
@@ -0,0 +1,38 @@
+from abc import ABC, abstractmethod
+from collections.abc import Callable
+from os import PathLike
+from os.path import abspath
+from typing import Self, cast, get_args, override
+
+from pacman.ecs.world import Resource, World
+from pacman.utils.type_resolve import resolve_type_aliases
+
+
+class Asset(ABC):
+    @classmethod
+    @abstractmethod
+    def load(cls, ty: type, path: str) -> Self: ...
+
+
+class Assets[T: Asset](Resource):
+    def __init__(self, ty: type) -> None:
+        self._lookup: dict[str, T] = {}
+        self._asset_ty: type[T] = get_args(resolve_type_aliases(ty))[0]
+
+    def load(self, s: str | PathLike[str]) -> T:
+        s = abspath(s)
+        if s not in self._lookup:
+            self._lookup[s] = self._asset_ty.load(self._asset_ty, s)
+        return self._lookup[s]
+
+    @override
+    @classmethod
+    def extract_from_world(
+        cls, ty: type, world: World
+    ) -> Callable[[], Self | None]:
+        inner = cast(
+            Callable[[], Self | None], Resource.extract_from_world(ty, world)
+        )
+        return lambda: cast(
+            Self, (res if (res := inner()) is not None else Assets(ty))
+        )