ecs #58
242
src/ecs.py
Normal file
242
src/ecs.py
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
"""
|
||||||
|
Un système de gestion d'entitées par les composants.
|
||||||
|
|
||||||
|
Dans ce système, un monde contient des entitiés qui contiennent des composants.
|
||||||
|
Pour moddifier le monde, on n'agis que sur les composants.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Iterator, Optional, Sequence, TypeVar
|
||||||
|
|
||||||
|
|
||||||
|
class Entity:
|
||||||
|
"""
|
||||||
|
Une entité dans le monde.
|
||||||
|
|
||||||
|
Cette classe ne contient pas les composants, elle est juste un
|
||||||
|
utilitaire pour acceder au monde plus facilement.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__T = TypeVar("__T")
|
||||||
|
"""
|
||||||
|
Ce type est utilisé pour permettre la gestion des types
|
||||||
|
pour certaines fonctions. Par exemple, la fonction `__getitem__`
|
||||||
|
utilise cette variable pour retourner un object du type demandé.
|
||||||
|
Cela permet d'avoir des vérifications de types et de l'autocomplétion
|
||||||
|
en utilisant notre IDE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, world: "World", identifier: int) -> None:
|
||||||
|
self.__world = world
|
||||||
|
self.__identifier = identifier
|
||||||
|
|
||||||
|
@property
|
||||||
|
def world(self) -> "World":
|
||||||
|
"""
|
||||||
|
Le monde dans lequel se trouve l'entité.
|
||||||
|
"""
|
||||||
|
return self.__world
|
||||||
|
|
||||||
|
@property
|
||||||
|
def identifier(self) -> int:
|
||||||
|
"""
|
||||||
|
L'identifiant de l'entité dans le monde.
|
||||||
|
"""
|
||||||
|
return self.__identifier
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Entity({self.__identifier})"
|
||||||
|
|
||||||
|
def __getitem__(self, component_type: type[__T]) -> __T:
|
||||||
|
return self.__world.get_component(self, component_type)
|
||||||
|
|
||||||
|
def __delitem__(self, component_type: type[object]):
|
||||||
|
self.__world.remove_component(self, component_type)
|
||||||
|
|
||||||
|
def __setitem__(self, component_type: type[__T], component: __T):
|
||||||
|
if component_type != type(component):
|
||||||
|
component = component_type(component)
|
||||||
|
self.__world.set_component(self, component)
|
||||||
|
|
||||||
|
def __contains__(self, component_type: type[object]) -> bool:
|
||||||
|
return self.__world.has_component(self, component_type)
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self.__world.all_components(self))
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[object]:
|
||||||
|
return iter(self.__world.all_components(self))
|
||||||
|
|
||||||
|
def get(self, component_type: type[__T], default: Optional[__T] = None) -> __T:
|
||||||
|
"""
|
||||||
|
Renvoie le composant de type `component_type` de l'entité.
|
||||||
|
Si aucun composant de type `component_type` n'est dans l'entité:
|
||||||
|
- Si `default` est None, une exception sera levé.
|
||||||
|
- Sinon, `default` sera renvoyé.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
- `component_type`: le type du composant à obtenir.
|
||||||
|
- `default`: le composant renvoyé si aucun composant de type
|
||||||
|
`component_type` n'est dans l'entité.
|
||||||
|
"""
|
||||||
|
return self.__world.get_component(self, component_type, default)
|
||||||
|
|
||||||
|
def set(self, *components: object):
|
||||||
|
"""
|
||||||
|
Ajoute des composants a l'entité. Si un composant du même type est
|
||||||
|
déja dans l'entité, le composant sera remplacé par le nouveau.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
- `*components`: les composants à ajouter.
|
||||||
|
"""
|
||||||
|
for component in components:
|
||||||
|
self.__world.set_component(self, component)
|
||||||
|
|
||||||
|
def remove(self, *component_types: type[object]):
|
||||||
|
"""
|
||||||
|
Supprime les composants de type `*component_types` de l'entité. Si il n'y a pas de
|
||||||
|
composant de type `*component_types` dans l'entité, la fonction ne fait rien.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
- `*component_types`: les types de composant à supprimer.
|
||||||
|
"""
|
||||||
|
for component_type in component_types:
|
||||||
|
self.__world.remove_component(self, component_type)
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
"""
|
||||||
|
Supprime tous les composants de l'entité.
|
||||||
|
"""
|
||||||
|
self.remove(*[type(component) for component in self])
|
||||||
|
|
||||||
|
|
||||||
|
class World(Entity):
|
||||||
|
"""
|
||||||
|
Un monde qui contient les entités.
|
||||||
|
|
||||||
|
Le monde hérite de `Entity` ce qui permet de stoquer des composants
|
||||||
|
globaux, relatif au monde.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__T = TypeVar("__T")
|
||||||
|
"""
|
||||||
|
Ce type est utilisé pour permettre la gestion des types
|
||||||
|
pour certaines fonctions. Par exemple, la fonction `get_component`
|
||||||
|
utilise cette variable pour retourner un object du type demandé.
|
||||||
|
Cela permet d'avoir des vérifications de types et de l'autocomplétion
|
||||||
|
en utilisant notre IDE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(self, 0)
|
||||||
|
self.__components: dict[int, dict[type[object], object]] = {}
|
||||||
|
self.__entities: dict[type[object], set[int]] = {}
|
||||||
|
self.__next_id: int = 1
|
||||||
|
|
||||||
|
def new_entity(self) -> "Entity":
|
||||||
|
"""
|
||||||
|
Créer une nouvelle entité dans le monde et la renvoie.
|
||||||
|
|
||||||
|
Techiquelement, cette fonction ne créer pas de nouvelle entité,
|
||||||
|
car une entité n'est stoqué que si elle a au moins un composant.
|
||||||
|
Cette fonction renvoie la classe utilitaire `Entity` qui contient
|
||||||
|
juste un identifiant unique pour cette entité afin de pouvoir
|
||||||
|
ajouter des composants à cette entité plus tard.
|
||||||
|
"""
|
||||||
|
entity = Entity(self, self.__next_id)
|
||||||
|
self.__next_id += 1
|
||||||
|
return entity
|
||||||
|
|
||||||
|
def set_component(self, entity: "Entity", component: object):
|
||||||
|
"""
|
||||||
|
Ajoute un composant a une entité. Si un composant du même type est
|
||||||
|
déja dans l'entité, le composant sera remplacé par le nouveau.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
- `entity`: l'entité dans lequelle ajouter le composant.
|
||||||
|
- `component`: le composant à ajouter.
|
||||||
|
"""
|
||||||
|
self.__components.setdefault(entity.identifier, {})[type(component)] = component
|
||||||
|
self.__entities.setdefault(type(component), set()).add(entity.identifier)
|
||||||
|
|
||||||
|
def get_component(
|
||||||
|
self, entity: "Entity", component_type: type[__T], default: Optional[__T] = None
|
||||||
|
) -> __T:
|
||||||
|
"""
|
||||||
|
Renvoie le composant de type `component_type` de l'entité `entity`.
|
||||||
|
Si aucun composant de type `component_type` n'est dans l'entité:
|
||||||
|
- Si `default` est None, une exception sera levé.
|
||||||
|
- Sinon, `default` sera renvoyé.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
- `entity`: l'entité dont on veut obtenir le composant.
|
||||||
|
- `component_type`: le type du composant à obtenir.
|
||||||
|
- `default`: le composant renvoyé si aucun composant de type
|
||||||
|
`component_type` n'est dans l'entité.
|
||||||
|
"""
|
||||||
|
component = self.__components.get(entity.identifier, {}).get(
|
||||||
|
component_type, default
|
||||||
|
)
|
||||||
|
if component is None:
|
||||||
|
raise ValueError(
|
||||||
|
f"Entity {entity} has no component of type {component_type}."
|
||||||
|
)
|
||||||
|
return component # type: ignore
|
||||||
|
|
||||||
|
def has_component(self, entity: "Entity", component_type: type[object]) -> bool:
|
||||||
|
"""
|
||||||
|
Renvoie si l'entité `entity` a un composant de type `component_type`.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
- `entity`: l'entité dont on veut savoir si il a un composant.
|
||||||
|
- `component_type`: le type du composant à verifier.
|
||||||
|
"""
|
||||||
|
return component_type in self.__components.get(entity.identifier, {})
|
||||||
|
|
||||||
|
def remove_component(self, entity: "Entity", component_type: type[object]):
|
||||||
|
"""
|
||||||
|
Supprime le composant de type `component_type` de l'entité `entity`. Si il n'y a pas de
|
||||||
|
composant de type `component_type` dans l'entité, la fonction ne fait rien.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
- `entity`: l'entité dont on veut supprimer le composant.
|
||||||
|
- `component_type`: le type du composant à supprimer.
|
||||||
|
"""
|
||||||
|
entity_components = self.__components.get(entity.identifier, {})
|
||||||
|
component = entity_components.pop(component_type, None)
|
||||||
|
if component is not None:
|
||||||
|
self.__entities[component_type].remove(entity.identifier)
|
||||||
|
if len(entity_components) == 0:
|
||||||
|
del self.__components[entity.identifier]
|
||||||
|
|
||||||
|
def all_components(self, entity: "Entity") -> list[object]:
|
||||||
|
"""
|
||||||
|
Renvoie une liste de tous les composants qui sont dans l'entité `entity`.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
- `entity`: l'entité dont on veut obtenir les composants.
|
||||||
|
"""
|
||||||
|
return list(self.__components.get(entity.identifier, {}).values())
|
||||||
|
|
||||||
|
def query(
|
||||||
|
self, *needed: type[object], without: Sequence[type[object]] = ()
|
||||||
|
) -> set["Entity"]:
|
||||||
|
"""
|
||||||
|
Renvoie un set d'`Entity` qui ont tous les composants de type *needed
|
||||||
|
et qui n'ont aucun des composants de type *without.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
- `*needed`: les types de composants des entités a renvoyer.
|
||||||
|
- `without`: les types de composants des entités a exclure.
|
||||||
|
"""
|
||||||
|
entities = (
|
||||||
|
set(self.__components.keys())
|
||||||
|
if len(needed) == 0
|
||||||
|
else set(self.__entities.get(needed[0], set()))
|
||||||
|
)
|
||||||
|
entities.difference_update((0,))
|
||||||
|
for component_type in needed[1:]:
|
||||||
|
entities.intersection_update(self.__entities.get(component_type, set()))
|
||||||
|
for component_type in without:
|
||||||
|
entities.difference_update(self.__entities.get(component_type, set()))
|
||||||
|
return {Entity(self, entity_id) for entity_id in entities}
|
Loading…
Reference in a new issue