diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..6f398af --- /dev/null +++ b/.pylintrc @@ -0,0 +1,3 @@ +[MESSAGES CONTROL] +disable=all +enable= missing-docstring \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..e33c53c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "matangover.mypy", + "ms-python.pylint" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 7b0016e..144991e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,8 @@ "type": "python", "request": "launch", "module": "main", - "justMyCode": true + "justMyCode": true, + "preLaunchTask": "Python: Check", } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..367230a --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,29 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Python: MyPy", + "type": "shell", + "command": "mypy", + "args": [ + "." + ] + }, + { + "label": "Python: Pylint", + "type": "shell", + "command": "pylint", + "args": [ + "**/*.py" + ] + }, + { + "label": "Python: Check", + "dependsOrder": "sequence", + "dependsOn": [ + "Python: MyPy", + "Python: Pylint", + ] + }, + ] +} \ No newline at end of file diff --git a/engine.py b/engine.py index 2907517..cf85954 100644 --- a/engine.py +++ b/engine.py @@ -1,23 +1,33 @@ -from typing import Iterator, Callable +""" +Système de gestion d'entités, composants et ressources. +""" + +from typing import Iterator, Callable, Tuple, TypeVar, Type, Optional from logging import error +_T = TypeVar("_T") +""" +Un type générique utilisé pour les composants et les ressources. +""" + + class World: """ Un monde contenant des entités et des ressources. """ - def __init__(self): + def __init__(self) -> None: """ Permet de créer un nouveau monde vide. """ self._to_apply: set[Entity] = set() self._entities: set[Entity] = set() - self._mapping: dict[type, set[Entity]] = {} - self._to_apply_resources: list[tuple[type, object]] = [] - self._resources: dict[type, object] = {} + self._mapping: dict[Type, set[Entity]] = {} + self._to_apply_resources: list[tuple[Type, Optional[object]]] = [] + self._resources: dict[Type, object] = {} - def create_entity(self, *components): + def create_entity(self, *components: _T) -> "Entity": """ Crée une entité avec les composants donnés en paramètres. @@ -29,7 +39,7 @@ class World: """ return Entity(self, *components) - def remove_entity(self, entity: "Entity"): + def remove_entity(self, entity: "Entity") -> None: """ Supprime une entité du monde. @@ -39,7 +49,7 @@ class World: entity._deleted = True self._to_apply.add(entity) - def set(self, *resources): + def set(self, *resources: _T) -> None: """ Définit les ressources données en paramètres. Si les ressources existent deja, elles seront remplacées. @@ -50,7 +60,7 @@ class World: for resource in resources: self._to_apply_resources.append((type(resource), resource)) - def remove(self, *resource_types): + def remove(self, *resource_types: Type[_T]) -> None: """ Supprime les ressources données en paramètres. @@ -61,7 +71,7 @@ class World: if resource_type in self._resources: self._to_apply_resources.append((resource_type, None)) - def apply(self): + def apply(self) -> None: """ Applique les changements réaliser dans le monde. """ @@ -77,7 +87,7 @@ class World: del entity._components[component_type] self._mapping[component_type].remove(entity) else: - entity._components[component_type] = component + entity._components[component_type] = component # type: ignore[assignment] self._mapping.setdefault(component_type, set()).add(entity) entity._to_apply.clear() self._to_apply.clear() @@ -88,7 +98,9 @@ class World: self._resources[resource_type] = resource self._to_apply_resources.clear() - def query(self, *needed: type, without: tuple[type] = []) -> Iterator["Entity"]: + def query( + self, *needed: Type[_T], without: Tuple[Type[_T], ...] = () + ) -> Iterator["Entity"]: """ Renvoie les entités qui ont les composants de *needed et sans les composants de *without. @@ -112,7 +124,7 @@ class World: ) and all(without_type not in entity for without_type in without): yield entity - def __getitem__(self, resource_type: type) -> object: + def __getitem__(self, resource_type: Type[_T]) -> _T: """ Renvoie la ressource de type *resource_type. @@ -122,19 +134,23 @@ class World: Retourne: La ressource de type *resource_type. """ - return self._resources[resource_type] + resource: _T = self._resources[resource_type] # type: ignore[assignment] + return resource - def __contains__(self, resource_type: type) -> bool: + def __contains__(self, *resource_types: Type[_T]) -> bool: """ - Renvoie si la ressource de type *resource_type existe. + Renvoie True si le monde contient toutes les ressources de *resource_types. Paramètres: - resource_type: Le type de ressource à tester. + *resource_types: Les types de ressource à tester. Retourne: - Si la ressource de type *resource_type existe. + Si toutes les ressources de *resource_types sont dans le monde. """ - return resource_type in self._resources + for resource_type in resource_types: + if resource_type not in self._resources: + return False + return True class Entity: @@ -142,7 +158,7 @@ class Entity: Une entité du monde. """ - def __init__(self, world: World, *components): + def __init__(self, world: World, *components: _T): """ Créer une entité avec les composants en paramètres et l'ajoute au monde. @@ -151,14 +167,14 @@ class Entity: *components: Les composants de l'entité. """ self._world = world - self._to_apply: list[tuple[type, object]] = [ + self._to_apply: list[tuple[Type, Optional[object]]] = [ (type(component), component) for component in components ] - self._components: dict[type, object] = {} + self._components: dict[Type[_T], _T] = {} self._deleted = False self._world._to_apply.add(self) - def set(self, *components): + def set(self, *components: _T) -> None: """ Définit les composants de l'entité donnés en paramètres. @@ -169,7 +185,7 @@ class Entity: self._to_apply.append((type(component), component)) self._world._to_apply.add(self) - def remove(self, *component_types: type): + def remove(self, *component_types: Type[_T]) -> None: """ Supprime les composants de l'entité donnés en paramètres. @@ -181,7 +197,7 @@ class Entity: self._to_apply.append((component_type, None)) self._world._to_apply.add(self) - def __getitem__(self, component_type: type) -> object: + def __getitem__(self, component_type: Type[_T]) -> _T: """ Renvoie le composant de type *component_type. @@ -193,17 +209,20 @@ class Entity: """ return self._components[component_type] - def __contains__(self, component_type: type) -> bool: + def __contains__(self, *component_types: Type[_T]) -> bool: """ - Renvoie si le composant de type *component_type existe. + Renvoie True si l'entité contient tous les composants de *component_types. Paramètres: - component_type: Le type du composant à tester. + component_type: Les types des composants à tester. Retourne: - Si le composant de type *component_type existe. + Si tous les composants de *component_types sont dans l'entité. """ - return component_type in self._components + for component_type in component_types: + if component_type not in self._components: + return False + return True class Game: @@ -211,7 +230,7 @@ class Game: Permet de faire une simple boucle de jeu. """ - def __init__(self): + def __init__(self) -> None: """ Créer une un jeu. """ @@ -220,7 +239,7 @@ class Game: self._update_tasks: dict[int, list[Callable[[World], None]]] = {} self._shutdown_tasks: dict[int, list[Callable[[World], None]]] = {} - def add_startup_tasks(self, priority: int, *tasks: Callable[[World], None]): + def add_startup_tasks(self, priority: int, *tasks: Callable[[World], None]) -> None: """ Ajoute des taches qui s'executeront au démarrage du jeu. @@ -232,7 +251,7 @@ class Game: raise RuntimeError("Cannot add startup task while the loop is running") self._startup_tasks.setdefault(priority, []).extend(tasks) - def add_tasks(self, priority: int, *tasks: Callable[[World], None]): + def add_tasks(self, priority: int, *tasks: Callable[[World], None]) -> None: """ Ajoute des taches qui s'executeront a chaque mise à jour du jeu. @@ -244,7 +263,9 @@ class Game: raise RuntimeError("Cannot add task while the loop is running") self._update_tasks.setdefault(priority, []).extend(tasks) - def add_shutdown_tasks(self, priority: int, *tasks: Callable[[World], None]): + def add_shutdown_tasks( + self, priority: int, *tasks: Callable[[World], None] + ) -> None: """ Ajoute des taches qui s'executeront à la fin de la boucle de jeu. @@ -256,7 +277,9 @@ class Game: raise RuntimeError("Cannot add shutdown task while the loop is running") self._shutdown_tasks.setdefault(priority, []).extend(tasks) - def _run_tasks(self, tasks: dict[int, list[Callable[[World], None]]], world: World): + def _run_tasks( + self, tasks: dict[int, list[Callable[[World], None]]], world: World + ) -> None: """ Execute toutes les taches donnes en paramètres en respectant la priorité. """ @@ -277,7 +300,7 @@ class Game: self._running = True # On initialize le monde - world = World() + world: World = World() world.set(self) # On applique les moddifications pour l'ajout de la ressource @@ -305,7 +328,7 @@ class Game: # On retourne le monde return world - def stop(self): + def stop(self) -> None: """ Demande la fin de la boucle de jeu. La boucle s'arretera a la prochaine mise à jour. """ diff --git a/examples/ecs.py b/examples/ecs.py deleted file mode 100644 index 69cf714..0000000 --- a/examples/ecs.py +++ /dev/null @@ -1,90 +0,0 @@ -from engine import World # Doit être mis dans le dossier principale pour fonctionner - - -# Création de composants pouvant être ajouté a des entitées -class Name(str): - pass - - -class Age(int): - pass - - -# Création d'un monde -world = World() - -# Création de plusieurs entités -david = world.create_entity(Name("David"), Age(25)) -fred = world.create_entity(Name("Fred"), Age(30)) -paul_sans_age = world.create_entity(Name("Paul")) -age_tout_cour = world.create_entity(Age(14)) - -# On applique les moddifications -world.apply() - -print("Récupération de toutes les entitées qui ont un nom") -for entity in world.query(Name): - print(entity[Name]) - -print("Récupération de toutes les entitées qui ont un age") -for entity in world.query(Age): - print(entity[Age]) - -# On change l'age de Fred -fred.set(Age(45)) - -# On applique les moddifications -world.apply() - -print("Récupération de toutes les entités qui ont un nom et un age") -for entity in world.query(Name, Age): - print(entity[Name], entity[Age]) - -print("Récupération de toutes les entitées qui ont un nom mais pas d'age") -for entity in world.query(Name, without=(Age,)): - print(entity[Name]) - -print("Récupération de toutes les entités qui ont un age mais pas de nom") -for entity in world.query(Age, without=(Name,)): - print(entity[Age]) - -print("Récupération de toutes les entités") -for entity in world.query(): - if Name in entity: - print(entity[Name], end=" ") - if Age in entity: - print(entity[Age], end=" ") - print() - - -# Création d'une ressource pouvant être ajoutée a un monde -class Gravity(float): - pass - - -# On peut aussi ajouter des ressources globales -world.set(Gravity(9.81)) - -print("On vérifie que la ressource Gravity existe") -print(Gravity in world) - -# On applique les moddifications -world.apply() - -print("On vérifie que la ressource Gravity existe après l'application") -print(Gravity in world) - -print("Récupération de la ressource Gravity") -print(world[Gravity]) - -# On supprime la ressource Gravity -world.remove(Gravity) - -print("On vérifie que la ressource Gravity n'existe plus") -print(Gravity in world) - -# On applique les moddifications -world.apply() - -print("On vérifie que la ressource Gravity n'existe plus") -print(Gravity in world) diff --git a/main.py b/main.py index 9f67c6f..1dba759 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,8 @@ +""" +Ceci est un exemple de comment l'on peut utiliser le moteur du jeu. +""" + + from engine import Game diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..8b89639 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +check_untyped_defs = True +disallow_untyped_defs = True \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e69de29..8120074 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1,2 @@ +mypy +pylint \ No newline at end of file