from collections.abc import Callable, Generator, Iterable
-from typing import Self, cast, get_args, override
-
-from pacman.ecs.world import Component, Entity, FromWorld, World
+from typing import get_args, override
+
+from pacman.ecs.world import (
+ CommandQueue,
+ Component,
+ Entity,
+ FromWorld,
+ Not,
+ Query,
+ RawComponentArg,
+ RawComponentHook,
+ World,
+)
+from pacman.utils.type_resolve import resolve_type_aliases
class Children(Component):
yield from self._children
-__no_hook = False
-
-
-class Hierarchy(FromWorld):
- @override
- @classmethod
- def extract_from_world(
- cls, ty: type, world: "World"
- ) -> Callable[[], Self | None]:
- pass
-
- def cleanup(self) -> None:
- pass
-
-
-def get_parents(world: World, entity: Entity) -> Generator[Entity]:
- """Generate parents of entity including self"""
- yield entity
- while Parent in world[entity]:
- nxt: Entity = world[entity][Parent]._parent
- entity = nxt
- yield entity
-
-
-def children_insert_hook(
- world: World, entity: Entity, component: Children
-) -> None:
- global __no_hook
- if __no_hook:
- return
- invalid_children = set(get_parents(world, entity))
- if any(child in invalid_children for child in component.children):
- raise Exception("Cyclic child hierarchy")
- for child in map(lambda e: world[e], component.children):
- if Parent in child:
- del child[Parent]
- __no_hook = True
- child[Parent] = Parent(entity)
- __no_hook = False
-
-
-def children_remove_hook(
- world: World, _entity: Entity, component: Children
-) -> None:
- global __no_hook
- if __no_hook:
- return
- __no_hook = True
- for child in component.children:
- del world[child][Parent]
- __no_hook = False
-
-
-class Parent(Component):
+class ChildOf(Component):
def __init__(self, parent: Entity) -> None:
self._parent = parent
return self._parent
-def parent_insert_hook(
- world: World, entity: Entity, component: Parent
-) -> None:
- global __no_hook
- if __no_hook:
- return
- parent = world[component.parent]
- if Children not in parent:
- __no_hook = True
- parent[Children] = Children((entity,))
- __no_hook = False
- else:
- parent[Children]._children.add(entity)
+def parent_insert_hook(queue: CommandQueue) -> RawComponentHook[Children]:
+ def inner(itr: RawComponentArg[Children]) -> None:
+ inserts = [
+ (child, ChildOf(ent))
+ for ent, inserted in itr
+ for child in inserted.children
+ ]
+ queue.queue(lambda world: world.insert_many(ChildOf, inserts))
+
+ return inner
+
+
+def parent_remove_hook(queue: CommandQueue) -> RawComponentHook[Children]:
+ def inner(itr: RawComponentArg[Children]) -> None:
+ removes = [e for _, children in itr for e in children.children]
+ queue.queue(lambda world: world.remove_many(Children, removes))
+
+ return inner
+
+
+def child_insert_hook(
+ queue: CommandQueue,
+ parents: Query[Children],
+) -> RawComponentHook[ChildOf]:
+ def inner(itr: RawComponentArg[ChildOf]) -> None:
+ inserts = {}
+ for ent, inserted in itr:
+ parent = parents.get(inserted.parent)
+ if parent is None:
+ if inserted.parent not in inserts:
+ inserts[inserted.parent] = Children([])
+ inserts[inserted.parent]._children.add(ent)
+ else:
+ parent._children.add(ent)
+ queue.queue(lambda world: world.insert_many(Children, inserts.items()))
-def parent_remove_hook(
- world: World, entity: Entity, component: Parent
-) -> None:
- if __no_hook:
- return
- world[component.parent][Children]._children.remove(entity)
+ return inner
+
+
+def child_remove_hook(parents: Query[Children]) -> RawComponentHook[ChildOf]:
+ def inner(itr: RawComponentArg[ChildOf]) -> None:
+ for ent, removed in itr:
+ parent = parents.get(removed.parent)
+ if parent is not None:
+ parent._children.remove(ent)
+
+ return inner
def hierachy_plugins(world: World) -> None:
- world.with_hook("insert", Children, children_insert_hook).with_hook(
- "remove", Children, children_remove_hook
- ).with_hook("insert", Parent, parent_insert_hook).with_hook(
- "remove", Parent, parent_remove_hook
+ world.with_hook("insert", Children, parent_insert_hook).with_hook(
+ "remove", Children, parent_remove_hook
+ ).with_hook("insert", ChildOf, child_insert_hook).with_hook(
+ "remove", ChildOf, child_remove_hook
)
-def traverse_hierarchy[*T](
- world: World, query: type[tuple[*T]]
-) -> Generator[tuple[tuple[*T] | None, tuple[*T]]]:
- params = sorted(
- get_args(query), key=lambda e: len(world._components.get(e, {}))
- )
- work: list[Entity] = []
- for e in world.query(query, without=tuple[Parent, Children]):
- yield (None, e)
- for entity, children in world.query(
- tuple[Entity, Children], without=tuple[Parent]
- ):
- work.extend(children.children)
- ent = world[entity]
- if all(param in ent for param in params):
- yield (None, cast(tuple[*T], (*(ent[param] for param in params),)))
- while work:
- curr = work.pop()
- child = world[curr]
- parent = world[world[curr][Parent].parent]
- if all(param in child for param in params):
- parent_res = None
- if all(param in parent for param in params):
- parent_res = cast(
- tuple[*T], (*(parent[param] for param in params),)
- )
- child_res = cast(tuple[*T], (*(child[param] for param in params),))
- yield (parent_res, child_res)
-
- if Children in child:
- work.extend(child[Children].children)
+type RootsQuery[Items, Filters] = Query[
+ tuple[Items, Children], tuple[Filters, Not[ChildOf]]
+]
+type NodesQuery[Items, Filters] = Query[
+ tuple[Items, Children | None, ChildOf], Filters
+]
+
+
+class Hierarchy[Items, Filters = tuple[()]](FromWorld):
+ def __init__(
+ self,
+ roots: RootsQuery[Items, Filters],
+ nodes: NodesQuery[Items, Filters],
+ ) -> None:
+ self._roots = roots
+ self._nodes = nodes
+
+ @override
+ @classmethod
+ def extract_from_world(
+ cls, ty: type, world: "World"
+ ) -> Callable[[], "Hierarchy[Items, Filters]"]:
+ items, filters = get_args(ty)
+ roots_ty: type[RootsQuery[Items, Filters]] = resolve_type_aliases(
+ RootsQuery[items, filters]
+ )
+ nodes_ty: type[NodesQuery[Items, Filters]] = resolve_type_aliases(
+ NodesQuery[items, filters]
+ )
+ roots = roots_ty.extract_from_world(roots_ty, world)
+ nodes = nodes_ty.extract_from_world(nodes_ty, world)
+
+ return lambda: Hierarchy(roots(), nodes())
+
+ @override
+ def cleanup(self) -> None:
+ self._roots.cleanup()
+ self._nodes.cleanup()
return lambda archetype: (
False
if (filter_fetch := preprocessed(archetype)) is None
- else lambda i: filter_fetch(i) is not None
+ else lambda i: filter_fetch(i) is not _NoFetch
)
@classmethod
return lambda archetype: lambda i: archetype._entities[i]
-class Not[T: FromWorld | Any](FromWorld, QueryFilter):
+class Not[T: FromWorld | Any](FromWorld, QueryItem):
+ @override
+ @classmethod
+ def filter_fetch(
+ cls, ty: type
+ ) -> Callable[[Archetype], None | Callable[[int], Self | _NoFetchCls]]:
+ preprocessed = cls.filter(ty)
+ return lambda archetype: (
+ (lambda _: Not())
+ if (filt := preprocessed(archetype)) == False
+ else (
+ None
+ if filt == True
+ else (lambda i: _NoFetch if filt(i) else Not())
+ )
+ )
+
@override
@classmethod
def filter(
if (arch_idx := self._archetypes_idx.get(arch)) is None:
return False
components_idx = arch._positions[entity]
- return self._filters[arch_idx](components_idx)
+ if not self._filters[arch_idx](components_idx):
+ return False
+ return self._fetchers[arch_idx](components_idx) is not _NoFetch
+
+ def get(self, entity: Entity) -> Items | None:
+ arch: Archetype | None = self._world._entities.get(entity)
+ if arch is None:
+ return None
+ if (arch_idx := self._archetypes_idx.get(arch)) is None:
+ return None
+ components_idx = arch._positions[entity]
+ if not self._filters[arch_idx](components_idx):
+ return None
+ if (res := self._fetchers[arch_idx](components_idx)) is _NoFetch:
+ return None
+ return cast(Items, res)
+
+ def __getitem__(self, entity: Entity) -> Items:
+ arch: Archetype | None = self._world._entities.get(entity)
+ if arch is None:
+ raise KeyError("Entity not in world", entity)
+ if (arch_idx := self._archetypes_idx.get(arch)) is None:
+ raise KeyError("Entity not in query", entity)
+ components_idx = arch._positions[entity]
+ if not self._filters[arch_idx](components_idx):
+ raise KeyError("Entity does not pass filters", entity)
+ if (res := self._fetchers[arch_idx](components_idx)) is _NoFetch:
+ raise KeyError("Entity does not pass fetchers", entity)
+ return cast(Items, res)
def single(self) -> Items | None:
itr: Iterator[Items] = self.__iter__()
) -> "World":
comp_id = self.component_id(resolve_type_aliases(ty))
hooks = self._components_cache[comp_id][1]
- sys = self.add_raw_system(hook)
+ sys = self.add_system(hook, default=lambda _: None)
match cycle:
case "insert":
- hooks.add_insert(lambda e: sys()(e))
+ hooks.add_insert(lambda e: sys(e))
case "remove":
- hooks.add_remove(lambda e: sys()(e))
+ hooks.add_remove(lambda e: sys(e))
return self
def with_plugins(self, *plugins: Plugin) -> "World":