Union,
cast,
get_args,
- get_origin,
override,
runtime_checkable,
)
ScheduleLabel,
SystemSet,
)
+from pacman.utils.panic import panic
+from pacman.utils.type_resolve import origin_or_cls, resolve_type_aliases
from pacman.utils.variadics_please import (
RecursiveTuple,
fast_tuple_constructor,
storage: type[T]
-type Components = RecursiveTuple["Component"]
+class ComponentLike(Protocol):
+ @abstractmethod
+ def components_iter(self) -> Iterator["Component"]: ...
+ @classmethod
+ @abstractmethod
+ def components_type_iter(cls) -> Iterator[type["Component"]]: ...
+
+
+type Components = RecursiveTuple["Component | ComponentLike"]
def components_iter(components: Components) -> Iterator["Component"]:
- pass
+ if isinstance(components, Component):
+ yield components
+ elif isinstance(components, tuple):
+ yield from (e for sub in components for e in components_iter(sub))
+ else:
+ yield from components.components_iter()
def components_type_iter(
components: type[Components],
) -> Iterator[type["Component"]]:
- pass
+ if issubclass(components, Component):
+ yield components
+ elif origin_or_cls(components) is tuple:
+ yield from (
+ e
+ for sub in get_args(components)
+ for e in components_type_iter(sub)
+ )
+ else:
+ yield from cast(type[ComponentLike], components).components_type_iter()
class Archetype:
ptr = id(ty)
if res := self._add_cache.get(ptr):
return (res[1], res[2])
- comps = list(components_type_iter(ty))
+ comps = list(components_type_iter(resolve_type_aliases(ty)))
res_arch: Archetype
types = frozenset(comps) | self._types
if prev := self._world._archetypes_lookup.get(types):
ptr = id(ty)
if res := self._remove_cache.get(ptr):
return (res[1], res[2])
- comps = list(components_type_iter(ty))
+ comps = list(components_type_iter(resolve_type_aliases(ty)))
res_arch: Archetype
types = self._types - frozenset(comps)
if prev := self._world._archetypes_lookup.get(types):
return (res_arch, comps)
def add_components[T: Components](
- self, ty: type[T], components: Iterable[tuple["Entity", T]]
+ self, ty: type[T], components: list[tuple["Entity", T]]
) -> None:
dst, comps_ty = self._compute_add(ty)
comps = [components_iter(e) for _, e in components]
self._positions[entity] = len(self._entities)
self._entities.append(entity)
for ty, comp in self._components.items():
- comp.extend(cb(ty))
+ itr = cb(ty)
+ comp.extend(itr)
assert all(
len(comp) == len(self._entities)
for comp in self._components.values()
@classmethod
@abstractmethod
def extract_from_world(
- cls, world: "World"
+ cls, ty: type, world: "World"
) -> Callable[[], Self | None]: ...
def cleanup(self) -> None:
FromWorld, Iterable[Items], Container[Entity]
):
def __init__(
- self, world: "World", fetchers: type[Items], filters: type[Filters]
+ self,
+ world: "World",
+ fetchers: type[Items],
+ filters: type[Filters] | None = None,
) -> None:
+ if filters is None:
+ filters = cast(type[Filters], tuple[()])
self._world: World = world
self._fetchers_raw = cast(
Callable[[Archetype], Callable[[int], Items] | None],
- Query.preprocess_fetchers(fetchers),
+ Query.preprocess_fetchers(resolve_type_aliases(fetchers)),
+ )
+ self._filters_raw = Query.preprocess_filters(
+ resolve_type_aliases(filters)
)
- self._filters_raw = Query.preprocess_filters(filters)
self._seen_archetypes: int = 0
self._archetypes_idx: dict[Archetype, int] = {}
self._archetypes: list[Archetype] = []
return None
return lst.pop()
- if get_origin(fetcher) is tuple:
+ if origin_or_cls(fetcher) is tuple:
return tuple_constructor
- if get_origin(fetcher) is Union:
+ if origin_or_cls(fetcher) is Union:
return union_constructor
return cast(QueryItem, fetcher).filter_fetch
return lambda _: False
return cond
- if get_origin(filter) is tuple:
+ if origin_or_cls(filter) is tuple:
return tuple_constructor
- if get_origin(filter) is Union:
+ if origin_or_cls(filter) is Union:
return union_constructor
return cast(QueryFilter, filter).filter
@override
@classmethod
- def extract_from_world(cls, world: "World") -> Callable[[], Self]:
- slf: Self = cls(world, *get_args(cls))
+ def extract_from_world(
+ cls, ty: type, world: "World"
+ ) -> Callable[[], Self]:
+ slf: Self = cls(world, *get_args(ty))
return slf.updated_from_world
@override
class Resource(FromWorld, Component):
@override
@classmethod
- def extract_from_world(cls, world: "World") -> Callable[[], Self | None]:
- _query = Query[cls] # type: ignore
+ def extract_from_world(
+ cls, ty: type, world: "World"
+ ) -> Callable[[], Self | None]:
+ _query = Query[ty] # type: ignore
query: Callable[[], Query[Self]] = cast(
type[Query[Self]], _query
- ).extract_from_world(world)
+ ).extract_from_world(_query, world)
return lambda: query().single()
+class CommandQueue(FromWorld):
+ def __init__(self, world: "World"):
+ self._world = world
+ self._queue: list[Callable[[World], Any]] = []
+
+ @override
+ @classmethod
+ def extract_from_world(
+ cls, ty: type, world: "World"
+ ) -> Callable[[], Self]:
+ return lambda: cls(world)
+
+ @override
+ def cleanup(self) -> None:
+ for sys in self._queue:
+ sys(self._world)
+
+ def queue(self, sys: Callable[["World"], Any]) -> None:
+ self._queue.append(sys)
+
+
def _system_failed[T]() -> T:
raise ValueError("Failed to fetch arguments for system")
def _run_cond(self, _: RawSystem | SystemSet) -> bool:
return True
+ def spawn_empty(self) -> Entity:
+ return self.spawn_many_empty(1)[0]
+
+ def spawn_many_empty(self, n: int) -> list[Entity]:
+ res = [Entity() for _ in range(n)]
+ self._base_archetype._extend(
+ lambda ty: (
+ res
+ if ty is Entity
+ else panic(Exception("Should be unreachable"))
+ )
+ )
+ return res
+
+ def spawn[T: Components](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]:
+ ents = self.spawn_many_empty(len(comps))
+ self._base_archetype.add_components(ty, list(zip(ents, comps)))
+ return ents
+
def add_system_raw[T](
self,
system: Callable[..., T],
if err := next(
filter(
lambda kv: (
- not issubclass(get_origin(kv[1].annotation), FromWorld)
+ not issubclass(origin_or_cls(kv[1].annotation), FromWorld)
),
sig.parameters.items(),
),
):
raise TypeError(f"System {system}'s {err[0]} parameter is invalid")
extractors = tuple(
- cast(type[FromWorld], param.annotation).extract_from_world(self)
+ cast(type[FromWorld], param.annotation).extract_from_world(
+ param.annotation, self
+ )
for param in sig.parameters.values()
)
@classmethod
@override
- def extract_from_world(cls, world: "World") -> Callable[[], "World"]:
+ def extract_from_world(
+ cls, ty: type, world: "World"
+ ) -> Callable[[], "World"]:
return lambda: world
def tick(self, schedule: ScheduleLabel) -> None:
def should_exit(_: WorldShouldExit) -> bool:
return True
+
+
+class Marker(Component):
+ pass
+
+
+def test_system(q: Query[Marker], queue: CommandQueue) -> None:
+ queue.queue(lambda world: world.spawn(Marker, Marker()))
+ print("ran")
+ if len(list(q)) == 10:
+ queue.queue(
+ lambda world: world.spawn(WorldShouldExit, WorldShouldExit())
+ )
+
+
+if __name__ == "__main__":
+ world = World()
+ # world.spawn(WorldShouldExit, WorldShouldExit())
+ world.with_systems(MainSchedule, world.add_system_raw(test_system))
+ world.run_main()