Ajout du fichier ecs.py (oublié précédament)

This commit is contained in:
Tipragot 2023-10-30 17:39:22 +01:00
parent 26bfb318ff
commit 2bb643e3fe

242
src/ecs.py Normal file
View 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}