From: Axy Date: Sat, 19 Sep 2026 19:42:37 +0000 (+0200) Subject: Start of work for moving to type-based systems X-Git-Url: https://git.uwuaxy.net/sitemap.xml?a=commitdiff_plain;h=4e206fc57a841173328e49e894f713455f5c21b6;p=axy%2Fft%2Fpacman.git Start of work for moving to type-based systems --- diff --git a/src/pacman/ecs/__init__.py b/src/pacman/ecs/__init__.py index 1bee6e4..fb4577c 100644 --- a/src/pacman/ecs/__init__.py +++ b/src/pacman/ecs/__init__.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from pathlib import Path import pygame -from pygame import Vector2, Vector3 +from pygame import Vector3 from pacman.ecs.hierarchy import ( Parent, @@ -17,7 +17,7 @@ from pacman.ecs.schedule import ( ) from pacman.ecs.world import ( Entity, - Resource, + Resource2, Systems, World, WorldIsInit, @@ -26,9 +26,9 @@ from pacman.ecs.world import ( def run_minimal_subschedules(world: World) -> None: - if Resource(WorldIsInit) not in world: + if Resource2(WorldIsInit) not in world: world.tick(StartupSchedule) - world[Resource(WorldIsInit)] = WorldIsInit() + world[Resource2(WorldIsInit)] = WorldIsInit() world.tick(UpdateSchedule) @@ -44,12 +44,10 @@ def schedule_plugins(world: World) -> None: class CoordAbs2D: - vec: Vector2 - depth: float + _vec: Vector3 def __init__(self) -> None: - self.vec = Vector2(0.0) - self.depth = 0.0 + self._vec = Vector3(0.0) class Coord2D(Vector3): @@ -58,15 +56,14 @@ class Coord2D(Vector3): def update_coord(world: World) -> None: for abs_coord, coord in world.query(tuple[CoordAbs2D, Coord2D]): - abs_coord.vec = coord.xy - abs_coord.depth = coord.z + abs_coord._vec = coord.xyz def update_coord_hierarchy(world: World) -> None: for parent, child in traverse_hierarchy(world, tuple[CoordAbs2D]): if parent is None: continue - child[0].vec += parent[0].vec + child[0]._vec += parent[0]._vec PropagateTransform = SystemSet("propagate-transform") @@ -104,10 +101,10 @@ class WindowEvents: def init_window(world: World) -> None: pygame.init() - world[Resource(Window)] = Window( + world[Resource2(Window)] = Window( pygame.display.set_mode(), pygame.time.Clock() ) - world[Resource(WindowEvents)] = WindowEvents([]) + world[Resource2(WindowEvents)] = WindowEvents([]) @dataclass @@ -123,17 +120,19 @@ def close_on_close(world: World) -> None: if pygame.QUIT in ( event.type for event in world.res_s(WindowEvents).events ): - world[Resource(WorldShouldExit)] = WorldShouldExit() + world[Resource2(WorldShouldExit)] = WorldShouldExit() def render_sprite2d(world: World) -> None: window = world.res_s(Window) sprites_raw = sorted( world[tuple[CoordAbs2D, Sprite2D, ShouldRender]], - key=lambda e: e[0].depth, + key=lambda e: e[0]._vec.z, reverse=True, ) - sprites = [(sprite.surface, coord.vec) for coord, sprite, _ in sprites_raw] + sprites = [ + (sprite.surface, coord._vec.xy) for coord, sprite, _ in sprites_raw + ] window.surface.fill((0, 0, 0)) window.surface.blits(sprites) window.clock.tick(60) diff --git a/src/pacman/ecs/collide.py b/src/pacman/ecs/collide.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pacman/ecs/world.py b/src/pacman/ecs/world.py index bdbf8c2..b92df32 100644 --- a/src/pacman/ecs/world.py +++ b/src/pacman/ecs/world.py @@ -1,7 +1,17 @@ import contextlib -from collections.abc import Callable, Generator +from abc import abstractmethod +from collections.abc import Callable, Generator, Iterable, Iterator from dataclasses import dataclass -from typing import Any, Literal, cast, get_args, overload +from typing import ( + Any, + Literal, + Protocol, + Self, + cast, + get_args, + overload, + override, +) from pacman.ecs.schedule import ( MainSchedule, @@ -9,6 +19,7 @@ from pacman.ecs.schedule import ( ScheduleLabel, SystemSet, ) +from pacman.utils.variadics_please import VarargCallable, VarargTuple class Entity: @@ -16,7 +27,7 @@ class Entity: @dataclass -class Resource[T]: +class Resource2[T]: storage: type[T] @@ -24,13 +35,65 @@ class SkipSystemError(Exception): pass -type System = Callable[["World"], None] +type RawSystem = Callable[["World"], None] type Plugin = Callable[["World"], None] | tuple[Plugin, ...] type ComponentHook[T] = Callable[["World", "Entity", T], None] +class FromWorld(Protocol): + @classmethod + @abstractmethod + def extract_from_world(cls, world: "World") -> Self | None: ... + + +type SystemParam = VarargTuple[SystemParam] | World | FromWorld + +type System = VarargCallable[SystemParam, None] + + +class QueryItem: + pass + + +type QueryItems = VarargTuple[QueryItems] | QueryItem + + +class QueryFilter: + pass + + +type QueryFilters = VarargTuple[QueryFilters] | QueryFilter + + +class Query[Items: QueryItems, Filters: QueryFilters = tuple[()]]( + FromWorld, Iterable[Items] +): + def __init__( + self, world: "World", items: type[Items], filters: type[Filters] + ) -> None: + pass + + @override + @classmethod + def extract_from_world(cls, world: "World") -> Self | None: + return cls(world, *get_args(cls)) + + @override + def __iter__(self) -> Iterator[Items]: + if False: + yield + + +class Resource(FromWorld): + @override + @classmethod + def extract_from_world(cls, world: "World") -> Self | None: + + pass + + @dataclass class EntityThunk: world: "World" @@ -73,7 +136,7 @@ class World: self._entities: dict[Entity, set[type]] = {} self._components: dict[type, dict[Entity, Any]] = {} self._resources: dict[type, Any] = {} - self._schedules: dict[ScheduleLabel, Schedule[System]] = {} + self._schedules: dict[ScheduleLabel, Schedule[RawSystem]] = {} self._insert_hooks: dict[type, ComponentHook[Any]] = {} self._remove_hooks: dict[type, ComponentHook[Any]] = {} @@ -125,7 +188,7 @@ class World: ) @overload - def __getitem__[T](self, arg: Resource[T]) -> T: ... + def __getitem__[T](self, arg: Resource2[T]) -> T: ... @overload def __getitem__[*T]( @@ -141,12 +204,12 @@ class World: ) -> Any: if isinstance(arg, Entity): return self.entity(arg) - if isinstance(arg, Resource): + if isinstance(arg, Resource2): return self._resources[arg.storage] return self.query(arg) @overload - def __setitem__[T](self, key: Resource[T], val: T) -> None: ... + def __setitem__[T](self, key: Resource2[T], val: T) -> None: ... @overload def __setitem__[*T](self, key: Entity, val: tuple[*T]) -> None: ... @@ -158,18 +221,18 @@ class World: self._entities[key] = set() for component in val: self[key][type(component)] = component - elif isinstance(key, Resource) and isinstance(val, key.storage): + elif isinstance(key, Resource2) and isinstance(val, key.storage): self._resources[key.storage] = val else: raise TypeError() @overload - def __delitem__[T](self, key: Resource[T]) -> None: ... + def __delitem__[T](self, key: Resource2[T]) -> None: ... @overload def __delitem__(self, key: Entity) -> None: ... - def __delitem__[T](self, key: Entity | Resource[T]) -> None: + def __delitem__[T](self, key: Entity | Resource2[T]) -> None: if isinstance(key, Entity): components = self._entities[key] del self._entities[key] @@ -179,16 +242,16 @@ class World: del self._resources[key.storage] @overload - def __contains__[T](self, item: Resource[T]) -> bool: ... + def __contains__[T](self, item: Resource2[T]) -> bool: ... @overload def __contains__(self, item: Entity) -> bool: ... - def __contains__[T](self, item: Entity | Resource[T]) -> bool: - if isinstance(item, Resource): + def __contains__[T](self, item: Entity | Resource2[T]) -> bool: + if isinstance(item, Resource2): return item.storage in self._resources return item in self._entities - def _run_cond(self, _: System | SystemSet) -> bool: + def _run_cond(self, _: RawSystem | SystemSet) -> bool: return True def tick(self, schedule: ScheduleLabel) -> None: @@ -199,9 +262,9 @@ class World: system(self) def res_s[T](self, ty: type[T]) -> T: - if Resource(ty) not in self: + if Resource2(ty) not in self: raise SkipSystemError() - return self[Resource(ty)] + return self[Resource2(ty)] def run( self, @@ -214,7 +277,7 @@ class World: def with_systems( self, schedule: ScheduleLabel, - *systems: "System | Systems", + *systems: "RawSystem | Systems", sets: set[SystemSet] | None = None, ) -> "World": if sets is None: @@ -258,7 +321,7 @@ class World: def run_main(self) -> None: self.run( - MainSchedule, lambda world: Resource(WorldShouldExit) in world + MainSchedule, lambda world: Resource2(WorldShouldExit) in world ) @@ -266,22 +329,22 @@ _order_groups: int = 0 class Systems: - def __init__(self, *systems: "System | SystemSet | Systems") -> None: + def __init__(self, *systems: "RawSystem | SystemSet | Systems") -> None: self._systems = systems self._chain = False - self._pre: list[System | SystemSet] = [] - self._post: list[System | SystemSet] = [] + self._pre: list[RawSystem | SystemSet] = [] + self._post: list[RawSystem | SystemSet] = [] self._in_set: list[SystemSet] = [] def chain(self) -> "Systems": self._chain = True return self - def after(self, *systems: System | SystemSet) -> "Systems": + def after(self, *systems: RawSystem | SystemSet) -> "Systems": self._pre.extend(systems) return self - def before(self, *systems: System | SystemSet) -> "Systems": + def before(self, *systems: RawSystem | SystemSet) -> "Systems": self._post.extend(systems) return self @@ -289,7 +352,7 @@ class Systems: self._in_set.extend(sets) return self - def _apply(self, sched: Schedule[System]) -> SystemSet: + def _apply(self, sched: Schedule[RawSystem]) -> SystemSet: global _order_groups _order_groups += 1 label = SystemSet(f"order-group-{_order_groups}") @@ -299,7 +362,7 @@ class Systems: ) sched.add_ordering((label,), self._pre, self._post, self._in_set) if self._chain: - prev: tuple[SystemSet | System] | tuple[()] = () + prev: tuple[SystemSet | RawSystem] | tuple[()] = () for system in systems: sched.add_ordering((system,), prev, (), (label,)) prev = (system,) diff --git a/src/pacman/utils/__init__.py b/src/pacman/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pacman/utils/type_resolve.py b/src/pacman/utils/type_resolve.py new file mode 100644 index 0000000..d555c13 --- /dev/null +++ b/src/pacman/utils/type_resolve.py @@ -0,0 +1,52 @@ +# ruff: noqa: ANN401 + +from collections.abc import Iterable +from typing import ( + Any, + get_args, + get_origin, +) + + +def map_type_generics(ty: Any, mapping: dict[Any, type]) -> Any: + """Map generics for this type, maybe creating a new type.""" + if ty in mapping: + return mapping[ty] + if (orig := get_origin(ty)) is None: + return ty + args = tuple(map_type_generics(e, mapping) for e in get_args(ty)) + if hasattr(ty, "copy_with") and callable(ty.copy_with): + return ty.copy_with(args) + return orig[args] + + +def resolve_type_aliases(ty: Any) -> Any: + """Resolve any type aliases for a given type, recursively.""" + while hasattr(ty, "__value__"): + if not ( + hasattr(ty, "__type_params__") + and hasattr(ty, "__args__") + and isinstance(ty.__args__, Iterable) + ): + return ty.__value__ + mapping = dict(zip(ty.__type_params__, ty.__args__, strict=True)) + ty = map_type_generics(ty.__value__, mapping) + return map_type_generics( + ty, {e: resolve_type_aliases(e) for e in get_args(ty)} + ) + + +type B[T] = list[list[T]] + +type A[T] = list[tuple[T, B[int]]] + +if __name__ == "__main__": + alp = A[str] + print(alp) + al = resolve_type_aliases(alp) + print(al) + al2 = list[tuple[str, list[list[int]]]] + print(al2) + al3 = resolve_type_aliases(al2) + print(al3) + print(al == al2) diff --git a/src/pacman/utils/variadics_please.py b/src/pacman/utils/variadics_please.py new file mode 100644 index 0000000..bd773fa --- /dev/null +++ b/src/pacman/utils/variadics_please.py @@ -0,0 +1,37 @@ +from collections.abc import Callable + +type VarargCallable[T, U] = ( + Callable[[T], U] + | Callable[[T, T], U] + | Callable[[T, T, T], U] + | Callable[[T, T, T, T], U] + | Callable[[T, T, T, T, T], U] + | Callable[[T, T, T, T, T, T], U] + | Callable[[T, T, T, T, T, T, T], U] + | Callable[[T, T, T, T, T, T, T, T], U] + | Callable[[T, T, T, T, T, T, T, T, T], U] + | Callable[[T, T, T, T, T, T, T, T, T, T], U] + | Callable[[T, T, T, T, T, T, T, T, T, T, T], U] + | Callable[[T, T, T, T, T, T, T, T, T, T, T, T], U] + | Callable[[T, T, T, T, T, T, T, T, T, T, T, T, T], U] + | Callable[[T, T, T, T, T, T, T, T, T, T, T, T, T, T], U] + | Callable[[T, T, T, T, T, T, T, T, T, T, T, T, T, T, T], U] +) +type VarargTuple[T] = ( + tuple[()] + | tuple[T] + | tuple[T, T] + | tuple[T, T, T] + | tuple[T, T, T, T] + | tuple[T, T, T, T, T] + | tuple[T, T, T, T, T, T] + | tuple[T, T, T, T, T, T, T] + | tuple[T, T, T, T, T, T, T, T] + | tuple[T, T, T, T, T, T, T, T, T] + | tuple[T, T, T, T, T, T, T, T, T, T] + | tuple[T, T, T, T, T, T, T, T, T, T, T] + | tuple[T, T, T, T, T, T, T, T, T, T, T, T] + | tuple[T, T, T, T, T, T, T, T, T, T, T, T, T] + | tuple[T, T, T, T, T, T, T, T, T, T, T, T, T, T] + | tuple[T, T, T, T, T, T, T, T, T, T, T, T, T, T, T] +)