--- /dev/null
+from collections.abc import Callable, Generator, Iterable
+from dataclasses import dataclass
+from typing import Any, cast, get_args, overload
+
+import pygame.sprite
+
+
+class Entity:
+ pass
+
+
+class Schedule:
+ pass
+
+
+@dataclass
+class Resource[T]:
+ storage: type[T]
+
+
+type System = Callable[["World"], None]
+
+type Plugin = Callable[["World"], None] | tuple[Plugin, ...]
+
+
+@dataclass
+class EntityThunk:
+ world: "World"
+ entity: Entity
+
+ def __getitem__[T](self, ty: type[T]) -> T:
+ return cast(T, self.world._components[ty][self.entity])
+
+ def __setitem__[T](self, ty: type[T], val: T) -> None:
+ if self.entity not in self.world._entities:
+ self.world._entities[self.entity] = set()
+ if ty not in self.world._entities[self.entity]:
+ self.world._entities[self.entity].add(ty)
+ if ty not in self.world._components:
+ self.world._components[ty] = {}
+ self.world._components[ty][self.entity] = val
+
+ def __delitem__[T](self, ty: type[T]) -> None:
+ if ty not in self.world._entities[self.entity]:
+ return
+ self.world._entities[self.entity].remove(ty)
+ del self.world._components[ty][self.entity]
+
+ def __contains__[T](self, ty: type[T]) -> bool:
+ return ty in self.world._entities[self.entity]
+
+
+class World:
+ def __init__(self) -> None:
+ self._entities: dict[Entity, set[type]] = {}
+ self._components: dict[type, dict[Entity, Any]] = {}
+ self._resources: dict[type, Any] = {}
+ self._schedules: dict[Schedule, set[System]] = {}
+
+ def entity(self, entity: Entity) -> EntityThunk:
+ return EntityThunk(self, entity)
+
+ def query[*T](self, ty: type[tuple[*T]]) -> Generator[tuple[*T]]:
+ 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:
+ continue
+
+ @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,
+ ):
+ if isinstance(arg, Entity):
+ return self.entity(arg)
+ if isinstance(arg, Resource):
+ return self._resources[arg.storage]
+ return self.query(arg)
+
+ @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):
+ if key in self:
+ del self[key]
+ self._entities[key] = set()
+ for component in val:
+ self[key][type(component)] = component
+ else:
+ self._resources[key.storage] = val
+
+ @overload
+ def __delitem__[T](self, key: Resource[T]) -> None: ...
+
+ @overload
+ def __delitem__(self, key: Entity) -> None: ...
+
+ def __delitem__(self, key):
+ if isinstance(key, Entity):
+ components = self._entities[key]
+ del self._entities[entity]
+ for component in components:
+ del self._components[component][entity]
+ else:
+ del self._resources[key.storage]
+
+ @overload
+ def __contains__[T](self, item: Resource[T]) -> bool: ...
+ @overload
+ def __contains__(self, item: Entity) -> bool: ...
+
+ def __contains__(self, item):
+ if isinstance(item, Resource):
+ return item.storage in self._resources
+ return item in self._entities
+
+ def tick(self, schedule: Schedule) -> None:
+ if schedule not in self._schedules:
+ return
+ for system in self._schedules[schedule]:
+ system(self)
+
+ def run(
+ self,
+ schedule: Schedule,
+ stop_cond: Callable[["World"], bool] = lambda _: False,
+ ) -> None:
+ while not stop_cond(self):
+ self.tick(schedule)
+
+ def with_systems(self, schedule: Schedule, *systems: System) -> "World":
+ if schedule not in self._schedules:
+ self._schedules[schedule] = set()
+ self._schedules[schedule].update(systems)
+ return self
+
+ def with_plugins(self, *plugins: Plugin) -> "World":
+ for plugin in plugins:
+ if isinstance(plugin, tuple):
+ self.with_plugins(plugin)
+ continue
+ plugin(self)
+ return self
+
+ def run_main(self) -> None:
+ self.run(
+ MainSchedule, lambda world: Resource(WorldShouldExit) in world
+ )
+
+
+MainSchedule = Schedule()
+StartupSchedule = Schedule()
+UpdateSchedule = Schedule()
+
+
+class WorldIsInit:
+ pass
+
+
+class WorldShouldExit:
+ pass
+
+
+def run_minimal_subschedules(world: World) -> None:
+ if Resource(WorldIsInit) not in world:
+ world.tick(StartupSchedule)
+ world[Resource(WorldIsInit)] = WorldIsInit()
+ world.tick(UpdateSchedule)
+
+
+def minimal_plugins(world: World) -> None:
+ world.with_systems(MainSchedule, run_minimal_subschedules)
+
+
+@dataclass
+class Vec2:
+ x: float
+ y: float
+
+
+@dataclass
+class Pos:
+ vec: Vec2
+
+
+@dataclass
+class Model2D:
+ sprite: pygame.Surface
+
+
+class ShouldRender:
+ pass
+
+
+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)])