from collections.abc import Callable, Generator, Iterable
from dataclasses import dataclass
+from pathlib import Path
from typing import Any, cast, get_args, overload
import pygame.sprite
args = get_args(ty)
if len(args) == 0:
return
- for entity in self._entities:
- try:
- yield cast(
- tuple[*T],
- tuple(
- entity if e is Entity else self._components[e][entity]
- for e in args
- ),
- )
- except KeyError:
+ sets: list[dict[Entity, Any]] = sorted(
+ (
+ self._entities if arg is Entity else self._components[arg]
+ for arg in args
+ if arg is Entity or arg in self._components
+ ),
+ key=len,
+ reverse=True,
+ )
+ if len(sets) == 0:
+ return
+ for entity in sets.pop():
+ if any(entity not in e for e in sets):
continue
+ yield cast(
+ tuple[*T],
+ tuple(
+ entity if e is Entity else self._components[e][entity]
+ for e in args
+ ),
+ )
@overload
def __getitem__[T](self, arg: Resource[T]) -> T: ...
-
@overload
def __getitem__[*T](
self, arg: type[tuple[*T]]
) -> Generator[tuple[*T]]: ...
-
@overload
def __getitem__(self, arg: Entity) -> EntityThunk: ...
-
def __getitem__(
self,
- arg,
- ):
+ arg: Any,
+ ) -> Any:
if isinstance(arg, Entity):
return self.entity(arg)
if isinstance(arg, Resource):
@overload
def __setitem__[T](self, key: Resource[T], val: T) -> None: ...
-
@overload
def __setitem__[*T](self, key: Entity, val: tuple[*T]) -> None: ...
-
- def __setitem__(self, key, val):
- if isinstance(key, Entity):
+ def __setitem__(self, key: Any, val: Any) -> None:
+ if isinstance(key, Entity) and isinstance(val, tuple):
if key in self:
del self[key]
self._entities[key] = set()
for component in val:
self[key][type(component)] = component
- else:
+ elif isinstance(key, Resource) and isinstance(val, key.storage):
self._resources[key.storage] = val
+ else:
+ raise TypeError()
@overload
def __delitem__[T](self, key: Resource[T]) -> None: ...
-
@overload
def __delitem__(self, key: Entity) -> None: ...
-
- def __delitem__(self, key):
+ def __delitem__[T](self, key: Entity | Resource[T]) -> None:
if isinstance(key, Entity):
components = self._entities[key]
- del self._entities[entity]
+ del self._entities[key]
for component in components:
- del self._components[component][entity]
+ del self._components[component][key]
else:
del self._resources[key.storage]
@overload
def __contains__(self, item: Entity) -> bool: ...
- def __contains__(self, item):
+ def __contains__[T](self, item: Entity | Resource[T]) -> bool:
if isinstance(item, Resource):
return item.storage in self._resources
return item in self._entities
@dataclass
class ScreenCoord2D:
vec: Vector2
+ depth: float
class ShouldRender:
@dataclass
class Window:
surface: pygame.Surface
-
-
-def init_window(world: World) -> None:
- world[Resource(Window)] = Window(pygame.display.set_mode())
+ clock: pygame.time.Clock
@dataclass
-class Sprite2D:
- surface: pygame.Surface
+class WindowEvents:
+ events: list[pygame.event.Event]
-def render_sprite2d(world: World) -> None:
- window = world.res_s(Window)
- sprites = [
- (sprite.surface, coord.vec)
- for coord, sprite, _ in world.query(
- tuple[ScreenCoord2D, Sprite2D, ShouldRender]
- )
- ]
- window.surface.blits(sprites)
+def init_window(world: World) -> None:
+ pygame.init()
+ world[Resource(Window)] = Window(
+ pygame.display.set_mode(), pygame.time.Clock()
+ )
+ world[Resource(WindowEvents)] = WindowEvents([])
@dataclass
-class ShapeColor:
- color: pygame.Color
+class Sprite2D:
+ surface: pygame.Surface
-@dataclass
-class Circle:
- radius: float
+def poll_events(world: World) -> None:
+ world.res_s(WindowEvents).events = pygame.event.get()
-def render_circle2d(world: World) -> None:
- window = world.res_s(Window)
- for circle, coord, color, _ in world.query(
- tuple[Circle, ScreenCoord2D, ShapeColor, ShouldRender]
+def close_on_close(world: World) -> None:
+ if pygame.QUIT in (
+ event.type for event in world.res_s(WindowEvents).events
):
- pygame.draw.circle(
- window.surface, color.color, coord.vec, circle.radius
- )
-
-
-@dataclass
-class Rectangle:
- dims: Vector2
+ world[Resource(WorldShouldExit)] = WorldShouldExit()
-def render_rectangle2d(world: World) -> None:
+def render_sprite2d(world: World) -> None:
window = world.res_s(Window)
- for rect, coord, color, _ in world.query(
- tuple[Rectangle, ScreenCoord2D, ShapeColor, ShouldRender]
- ):
- pygame.draw.rect(window.surface, color.color, (coord.vec, rect.dims))
-
-
-world = World()
-a = Entity()
-b = Entity()
-c = Entity()
-world[a] = (3, "hello")
-world[b] = (2,)
-world[c] = ("hai",)
-world[b][tuple[str]] = ("wow",)
-del world[a][str]
-for entity, component in world[tuple[Entity, int]]:
- print(entity)
- print(component)
-for string, integer in world[tuple[str, int]]:
- print(string)
- print(integer)
-for string, integer in world[tuple[tuple[str], int]]:
- print(string)
- print(integer)
-
-world[Resource(int)] = 15155
-world[Resource(str)] = "hello ecs"
-print(world[Resource(int)])
-del world[Resource(int)]
-print(Resource(int) in world)
-print(world[Resource(str)])
-world[Resource(int)] = 15
-print(Resource(int) in world)
-print(world[Resource(int)])
+ sprites_raw = sorted(
+ world[tuple[ScreenCoord2D, Sprite2D, ShouldRender]],
+ key=lambda e: e[0].depth,
+ reverse=True,
+ )
+ sprites = [(sprite.surface, coord.vec) for coord, sprite, _ in sprites_raw]
+ window.surface.fill((0, 0, 0))
+ window.surface.blits(sprites)
+ window.clock.tick(60)
+ pygame.display.flip()
+
+
+def graphics_plugins(world: World) -> None:
+ world.with_systems(StartupSchedule, init_window).with_systems(
+ UpdateSchedule, poll_events, close_on_close, render_sprite2d
+ )
+
+
+def move_sprites(world: World) -> None:
+ import math
+
+ time = pygame.time.get_ticks() / 100
+ for (coord,) in world[tuple[ScreenCoord2D]]:
+ pos = max(time - coord.depth * 0.3, 0.0)
+ coord.vec.y = 500.0 + math.sin(pos) * 30.0
+
+
+world = (
+ World()
+ .with_plugins(minimal_plugins, graphics_plugins)
+ .with_systems(UpdateSchedule, move_sprites)
+)
+assets = list(Path("./asset").rglob("*.png"))
+for i, asset in enumerate(assets):
+ world[Entity()] = (
+ ScreenCoord2D(Vector2(30.0 * i, 500.0), 1.0 * i),
+ Sprite2D(pygame.image.load(asset)),
+ ShouldRender(),
+ )
+world.run_main()