UpdateSchedule,
)
from pacman.ecs.world import (
+ Component,
Entity,
- Resource2,
+ Not,
+ Query,
+ Resource,
Systems,
World,
WorldIsInit,
)
-def run_minimal_subschedules(world: World) -> None:
- if Resource2(WorldIsInit) not in world:
- world.tick(StartupSchedule)
- world[Resource2(WorldIsInit)] = WorldIsInit()
+def run_startup(world: World, _: Not[WorldIsInit]) -> None:
+ world.tick(StartupSchedule)
+ world.spawn(WorldIsInit, WorldIsInit())
+
+
+def run_update(world: World, _: WorldIsInit) -> None:
world.tick(UpdateSchedule)
def schedule_plugins(world: World) -> None:
- world.with_systems(MainSchedule, run_minimal_subschedules).with_systems(
+ world.with_systems(MainSchedule, run_startup, run_update).with_systems(
UpdateSchedule, Systems(PreUpdate, Update, PostUpdate).chain()
)
-class CoordAbs2D:
+class CoordAbs2D(Component):
_vec: Vector3
def __init__(self) -> None:
self._vec = Vector3(0.0)
-class Coord2D(Vector3):
+class Coord2D(Vector3, Component):
pass
-def update_coord(world: World) -> None:
- for abs_coord, coord in world.query(tuple[CoordAbs2D, Coord2D]):
+def update_coord(entities: Query[tuple[CoordAbs2D, Coord2D]]) -> None:
+ for abs_coord, coord in entities:
abs_coord._vec = coord.xyz
def update_coord_hierarchy(world: World) -> None:
+ return
for parent, child in traverse_hierarchy(world, tuple[CoordAbs2D]):
if parent is None:
continue
)
-class ShouldRender:
+class ShouldRender(Component):
pass
@dataclass
-class Window:
+class Window(Resource):
surface: pygame.Surface
clock: pygame.time.Clock
@dataclass
-class WindowEvents:
+class WindowEvents(Resource):
events: list[pygame.event.Event]
def init_window(world: World) -> None:
pygame.init()
- world[Resource2(Window)] = Window(
- pygame.display.set_mode(), pygame.time.Clock()
- )
- world[Resource2(WindowEvents)] = WindowEvents([])
+ world.spawn(Window, Window(pygame.display.set_mode(), pygame.time.Clock()))
+ world.spawn(WindowEvents, WindowEvents([]))
@dataclass
-class Sprite2D:
+class Sprite2D(Component):
surface: pygame.Surface
-def poll_events(world: World) -> None:
- world.res_s(WindowEvents).events = pygame.event.get()
+def poll_events(events: WindowEvents) -> None:
+ events.events = pygame.event.get()
-def close_on_close(world: World) -> None:
- if pygame.QUIT in (
- event.type for event in world.res_s(WindowEvents).events
- ):
- world[Resource2(WorldShouldExit)] = WorldShouldExit()
+def close_on_close(world: World, events: WindowEvents) -> None:
+ if pygame.QUIT in (event.type for event in events.events):
+ world.spawn(WorldShouldExit, WorldShouldExit())
-def render_sprite2d(world: World) -> None:
- window = world.res_s(Window)
+def render_sprite2d(
+ window: Window, sprites_q: Query[tuple[CoordAbs2D, Sprite2D, ShouldRender]]
+) -> None:
sprites_raw = sorted(
- world[tuple[CoordAbs2D, Sprite2D, ShouldRender]],
+ sprites_q,
key=lambda e: e[0]._vec.z,
reverse=True,
)
)
-class WiggleSprite:
+class WiggleSprite(Component):
pass
-def move_sprites(world: World) -> None:
+def move_sprites(sprites: Query[Coord2D, WiggleSprite]) -> None:
import math
tick = pygame.time.get_ticks() / 100
- for coord, _ in world[tuple[Coord2D, WiggleSprite]]:
+ for coord in sprites:
pos = max(tick - coord.z * 0.3, 0.0)
coord.y = 500.0 + math.sin(pos) * 30.0
-class WiggleRoot:
+class WiggleRoot(Component):
pass
-def move_root(world: World) -> None:
+def move_root(root: Query[Coord2D, WiggleRoot]) -> None:
import math
tick = pygame.time.get_ticks() / 100
- for coord, _ in world[tuple[Coord2D, WiggleRoot]]:
+ for coord in root:
coord.x = 30.0 + math.cos(tick) * 30.0
.with_systems(UpdateSchedule, move_sprites, move_root)
)
assets = list(Path("./asset").rglob("*.png"))
- root = Entity()
- world[root] = (
- Coord2D(100.0, 0.0, 0.0),
- CoordAbs2D(),
- WiggleRoot(),
+ root = world.spawn(
+ tuple[Coord2D, CoordAbs2D, WiggleRoot],
+ (
+ Coord2D(100.0, 0.0, 0.0),
+ CoordAbs2D(),
+ WiggleRoot(),
+ ),
)
for j in range(10):
for i, asset in enumerate(assets):
i += j
i = i % len(assets)
- world[Entity()] = (
- Coord2D(30.0 * i, 500.0 + 10.0, 1.0 * i + j),
- CoordAbs2D(),
- Sprite2D(pygame.image.load(asset)),
- ShouldRender(),
- WiggleSprite(),
- Parent(root),
+ world.spawn(
+ tuple[
+ Coord2D,
+ CoordAbs2D,
+ Sprite2D,
+ ShouldRender,
+ WiggleSprite,
+ Parent,
+ ],
+ (
+ Coord2D(30.0 * i, 500.0 + 10.0, 1.0 * i + j),
+ CoordAbs2D(),
+ Sprite2D(pygame.image.load(asset)),
+ ShouldRender(),
+ WiggleSprite(),
+ Parent(root),
+ ),
)
world.run_main()
def components_type_iter(
components: type[Components],
) -> Iterator[type["Component"]]:
- if issubclass(components, Component):
- yield components
- elif origin_or_cls(components) is tuple:
+ if origin_or_cls(components) is tuple:
yield from (
e
for sub in get_args(components)
for e in components_type_iter(sub)
)
+ elif issubclass(components, Component):
+ yield components
else:
yield from cast(type[ComponentLike], components).components_type_iter()
type RawSystem[T] = Callable[[], T]
+type System[T] = Callable[..., T]
+
type Plugin = RecursiveTuple[Callable[["World"], None]]
type ComponentHook[T] = Callable[["World", "Entity", T], None]
@classmethod
@abstractmethod
def filter(
- cls, archetype: "Archetype"
- ) -> bool | Callable[[int], bool]: ...
+ cls, ty: type
+ ) -> Callable[[Archetype], bool | Callable[[int], bool]]: ...
type QueryFilters = RecursiveTuple[QueryFilter]
class QueryItem(QueryFilter):
@override
@classmethod
- def filter(cls, archetype: "Archetype") -> bool | Callable[[int], bool]:
- filter_fetch = cls.filter_fetch(archetype)
- if filter_fetch is None:
- return lambda _: False
- return lambda i: filter_fetch(i) is not None
+ def filter(
+ cls, ty: type
+ ) -> Callable[[Archetype], bool | Callable[[int], bool]]:
+ preprocessed = cls.filter_fetch(ty)
+ return lambda archetype: (
+ False
+ if (filter_fetch := preprocessed(archetype)) is None
+ else lambda i: filter_fetch(i) is not None
+ )
@classmethod
@abstractmethod
def filter_fetch(
- cls, archetype: "Archetype"
- ) -> None | Callable[[int], Self]: ...
+ cls, ty: type
+ ) -> Callable[[Archetype], None | Callable[[int], Self]]: ...
type QueryItems = RecursiveTuple[QueryItem]
class Component(QueryItem):
@override
@classmethod
- def filter(cls, archetype: "Archetype") -> bool:
- return cls in archetype._components
+ def filter(cls, ty: type) -> Callable[[Archetype], bool]:
+ return lambda archetype: ty in archetype._components
@override
@classmethod
def filter_fetch(
- cls, archetype: "Archetype"
- ) -> None | Callable[[int], Self]:
- idx = archetype._components.get(cls)
- if idx is None:
- return None
- return cast(Callable[[int], Self], lambda i: idx[i])
+ cls, ty: type
+ ) -> Callable[[Archetype], None | Callable[[int], Self]]:
+ return lambda archetype: (
+ None
+ if (idx := archetype._components.get(ty)) is None
+ else cast(Callable[[int], Self], lambda i: idx[i])
+ )
class Entity(QueryItem):
@override
@classmethod
- def filter(cls, archetype: "Archetype") -> bool:
- return True
+ def filter(cls, ty: type) -> Callable[[Archetype], bool]:
+ return lambda _: True
@override
@classmethod
def filter_fetch(
- cls, archetype: "Archetype"
- ) -> None | Callable[[int], "Entity"]:
- return lambda i: archetype._entities[i]
+ cls, ty: type
+ ) -> Callable[[Archetype], None | Callable[[int], "Entity"]]:
+ return lambda archetype: lambda i: archetype._entities[i]
+
+
+class Not[T: FromWorld | Any](FromWorld, QueryFilter):
+ @override
+ @classmethod
+ def filter(
+ cls, ty: type
+ ) -> Callable[[Archetype], bool | Callable[[int], bool]]:
+ inner = cast(type[QueryFilters], get_args(ty)[0])
+ preprocessed = Query.preprocess_filters(inner)
+ return lambda archetype: (
+ not filt
+ if isinstance((filt := preprocessed(archetype)), bool)
+ else lambda i: not filt(i)
+ )
+
+ @override
+ @classmethod
+ def extract_from_world(
+ cls, ty: type, world: "World"
+ ) -> Callable[[], "Not[T] | None"]:
+ inner = cast(type[FromWorld], get_args(ty)[0])
+ extractor = inner.extract_from_world(inner, world)
+
+ def inner() -> Not[T] | None:
+ if (res := extractor()) is None:
+ return Not()
+ res.cleanup()
+ return None
+
+ return inner
-class Query[Items: QueryItems, Filters: QueryFilters = tuple[()]](
+class Query[Items, Filters = tuple[()]](
FromWorld, Iterable[Items], Container[Entity]
):
def __init__(
if origin_or_cls(fetcher) is Union:
return union_constructor
- return cast(QueryItem, fetcher).filter_fetch
+ return cast(QueryItem, fetcher).filter_fetch(fetcher)
@staticmethod
def preprocess_filters(
if origin_or_cls(filter) is Union:
return union_constructor
- return cast(QueryFilter, filter).filter
+ return cast(QueryFilter, filter).filter(filter)
def add_archetype(self, archetype: Archetype) -> None:
cond: Callable[[int], bool]
)
return res
- def spawn[T: Components](self, ty: type[T], comp: T) -> Entity:
+ def spawn[T](self, ty: type[T], comp: T) -> Entity:
return self.spawn_many(ty, [comp])[0]
- def spawn_many[T: Components](
- self, ty: type[T], comps: list[T]
- ) -> list[Entity]:
+ def spawn_many[T](self, ty: type[T], comps: list[T]) -> list[Entity]:
ents = self.spawn_many_empty(len(comps))
- self._base_archetype.add_components(ty, list(zip(ents, comps)))
+ self._base_archetype.add_components(
+ cast(type[Components], ty),
+ list(zip(ents, cast(list[Components], comps))),
+ )
return ents
def add_system_raw[T](
def with_systems(
self,
schedule: ScheduleLabel,
- *systems: "RawSystem | Systems",
+ *systems: "System | Systems",
sets: set[SystemSet] | None = None,
) -> "World":
if sets is None:
sets = set()
if schedule not in self._schedules:
self._schedules[schedule] = Schedule()
- Systems(*systems).in_set(*sets)._apply(self._schedules[schedule])
+ Systems(*systems).in_set(*sets)._apply(self, self._schedules[schedule])
return self
def with_hook[T](
class Systems:
- def __init__(self, *systems: "RawSystem | SystemSet | Systems") -> None:
+ def __init__(self, *systems: "System | SystemSet | Systems") -> None:
self._systems = systems
self._chain = False
- self._pre: list[RawSystem | SystemSet] = []
- self._post: list[RawSystem | SystemSet] = []
+ self._pre: list[System | SystemSet] = []
+ self._post: list[System | SystemSet] = []
self._in_set: list[SystemSet] = []
def chain(self) -> "Systems":
self._chain = True
return self
- def after(self, *systems: RawSystem | SystemSet) -> "Systems":
+ def after(self, *systems: System | SystemSet) -> "Systems":
self._pre.extend(systems)
return self
- def before(self, *systems: RawSystem | SystemSet) -> "Systems":
+ def before(self, *systems: System | SystemSet) -> "Systems":
self._post.extend(systems)
return self
self._in_set.extend(sets)
return self
- def _apply(self, sched: Schedule[RawSystem]) -> SystemSet:
+ def _apply(self, world: World, sched: Schedule[RawSystem]) -> SystemSet:
global _order_groups
_order_groups += 1
label = SystemSet(f"order-group-{_order_groups}")
systems = (
- e._apply(sched) if isinstance(e, Systems) else e
+ e._apply(world, sched)
+ if isinstance(e, Systems)
+ else (
+ e
+ if isinstance(e, SystemSet)
+ else world.add_system_raw(e, default=lambda: None)
+ )
for e in self._systems
)
sched.add_ordering((label,), self._pre, self._post, self._in_set)
return label
-class WorldIsInit:
+class WorldIsInit(Resource):
pass
def test_system(q: Query[Marker], queue: CommandQueue) -> None:
queue.queue(lambda world: world.spawn(Marker, Marker()))
+ queue.queue(lambda world: world.spawn_empty())
print("ran")
if len(list(q)) == 10:
queue.queue(
if __name__ == "__main__":
world = World()
# world.spawn(WorldShouldExit, WorldShouldExit())
- world.with_systems(MainSchedule, world.add_system_raw(test_system))
+ world.with_systems(MainSchedule, test_system)
world.run_main()