Je suis en train de faire un jeu en C++ avec la bibliothèque SDL2, et je programme une implémentation d'un Entity Component System. Mon programme se lance bien, le renderer a bien mis à jour la fenêtre avec la couleur passée dans le SetRenderDrawColor, mais directement après la fenêtre ne répond pas et le programme retourne le code d'erreur -805306369... J'ai tenté d'utiliser le debugger mais il ne m'indique aucune erreur... Quelqu'un saurait d'où vient le problème ? Je n'ai absolument rien trouvé quand j'ai cherché...
Merci d'avance pour votre aide !
Si vous avez besoin de mon code source je reste dispo pour vous le passer
Est-ce que tu as lancé ton programme pas à pas ? Par exemple met un breakpoint jusqu' où ton programme tourne normalement puis fait l'exécution pas à pas pour voir où ça plante. Sinon, est-ce que tu pense à utiliser les assertions ?Aussi, il faut vérifier le retour des fonctions qui peuvent échouer comme IMG_Load() par exemple. Sans code on ne va pas pouvoir t' aider.
Quel est l' IDE que tu utilises (si c'est le cas) ? Sinon tu peux poster du code avec la balise '</>' dans la fenêtre d' édition des messages. Garde bien en tête que plus tu donneras d'informations, meilleure sera l'aide apportée.
Déjà, ce que tu devrais faire, c'est lancer une exception pour chacun des tests que tu effectue dans la fonction init, car, pour l'instant, il n'y a absolument rien qui empêche la fonction d'aller plus loin si "quelque chose" vient à ne pas fonctionner correctement.
Or, chaque étape est "éliminatoire", dans le sens où, si une étape donnée ne fonctionne pas, l'étape suivante ne pourra pas fonctionner non plus. Et il ne suffit pas d'afficher un message d'erreur du type "hey, tu sais que tel truc n'a pas fonctionné" pour pouvoir considérer que le problème a été traité
Ta fonction init pourrait donc prendre une forme proche de
void Engine::init() {
if(SDL_Init(SDL_INIT_VIDEO) != 0) {
/* on crée directement une chaine de caractères qui
* contient l'ensemble des informations intéressantes
*/
std::string error="Engine init #sdl :";
error+=std::string{SDL_GetError()};
/* on affiche cette chaine de caractères */
std::cerr << error<<"\n";
/* on lance une exception contenant cette chaine de
* caractères car il ne sert à rien d'aller plus loin
*/
trhow std::runitme_error(error);
}
auto winFlags = (SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI); //|SDL_WINDOW_MAXIMIZED
window = SDL_CreateWindow("Game Engine ECS",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,SCREEN_WIDTH,SCREEN_HEIGHT,winFlags);
if(!window) {
std::string error="Engine init #window :";
error+=std::string{SDL_GetError()};
std::cerr << error<<"\n";
trhow std::runitme_error(error);
}
auto renderFlags = (SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC);
renderer = SDL_CreateRenderer(window,-1,renderFlags);
if(!renderer) {
std::string error="Engine init #renderer:";
error+=std::string{SDL_GetError()};
std::cerr << error<<"\n";
trhow std::runitme_error(error);
}
/* ouch, là, on va ver un problème */
eManager = new EntityManager();
clearColor = DARK;
running = true;
}
La deuxième chose à faire, ce serait d'abandonner cet horrible anti-pattern qu'est le singleton, mais, comme l'idée nécessite pas mal d'explications que je n'ai pas le temps de donner maintenant, on va se contenter de l'améliorer un tout petit peu
en supprimant le besoin de faire appel à la fonction init (car c'est une étape qu'il est "trop facile" d'oublier)
en supprimant le besoin d'une allocation dynamique de la mémoire
en faisant renvoyer une référence au lieu d'un pointeur (car elles offrent, entre autres, la garantie d'existence de la donnée, alors que tu devrais tester systématiquement le pointeur)
en rendant quelques fonctions qui n'ont aucun besoin de modifier l'état de Engine constantes
Ta classe Engine pourrait donc prendre une forme proche de
Mais, du coup, il faut encore corriger un tout petit peu la fonction init pour qu'elle fasse passer la valeur de inititialized à true (et, accessoirement, qu'elle ne s'occupe plus du EntityManager:
(j'en ai profité pour homogénéiser le noms des différentes données )
Il va bien sur de soi que l'on serait sans doute bien inspirés de faire la même chose pour toutes les classes qui se présentent comme un singleton (je pense principalement à la classe TextureManager)
Ensuite, ce qui me choque le plus, c'est qu'il y a, dans la classe EntityManager, un pointeur sur SystemManager qui ... n'est jamais initialisé.
Il n'y a donc rien d'étonnant au fait que la fonction
nous envoie dans les choux, car, au mieux, sManager vaut nullptr, et nous aurions sans doute une erreur de fragmentation, au pire, sManager vaut "n'importe quoi" (peut être les "crasses" laissées par une utilisation plus ancienne de la mémoire) et l'on se retrouve donc à hacher la mémoire, menu .
Enfin, et ce n'est qu'un avis, tu devrais éviter le terme "manager" autant que faire se peut. Car ce terme est beaucoup trop générique, vu qu'il englobe les fait
de créer des données
de maintenir des données en mémoire
de manipuler les données
de décider de détruire les données
j'en passe, et peut-être de meilleures
Bref, tu te retrouve avec une classe qui présente au moins quatre responsabilités, et cela en fait définitivement trois de trop
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Je pense en plus de ce que dit Koala01, que le PO devrait wrapper les pointeurs de la SDL2 avec des std::unique_ptr, par exemple:
#ifndef H_UNIQ_PTR_SDL
#define H_UNIQ_PTR_SDL
#include "SDL.h"
#include "SDL_image.h"
#include "SDL_ttf.h"
#include "SDL_mixer.h"
#include <memory>
namespace sdl2{
struct SDL_Deleter {
void operator()(SDL_Window* ptr) { if (ptr) SDL_DestroyWindow(ptr); }
void operator()(SDL_Renderer* ptr) { if (ptr) SDL_DestroyRenderer(ptr); }
void operator()(SDL_Surface* ptr) { if (ptr) SDL_FreeSurface(ptr); }
void operator()(SDL_Texture* ptr) { if (ptr) SDL_DestroyTexture(ptr); }
void operator()(SDL_RWops* ptr) { if (ptr) SDL_RWclose(ptr); }
void operator()(SDL_Joystick* ptr) { if (ptr) SDL_JoystickClose(ptr); }
void operator()(TTF_Font* ptr) { if (ptr) TTF_CloseFont(ptr); }
void operator()(Mix_Chunk* ptr) { if (ptr) Mix_FreeChunk(ptr); }
void operator()(Mix_Music* ptr) { if (ptr) Mix_FreeMusic(ptr); }
void operator()(SDL_Haptic* ptr) { if (ptr) SDL_HapticClose(ptr); }
};
using WindowPtr = std::unique_ptr<SDL_Window, SDL_Deleter>;
using RendererPtr = std::unique_ptr<SDL_Renderer, SDL_Deleter>;
using SurfacePtr = std::unique_ptr<SDL_Surface, SDL_Deleter>;
using TexturePtr = std::unique_ptr<SDL_Texture, SDL_Deleter>;
using RWopsPtr = std::unique_ptr<SDL_RWops, SDL_Deleter>;
using JoystickPtr = std::unique_ptr<SDL_Joystick, SDL_Deleter>;
using FontPtr = std::unique_ptr<TTF_Font, SDL_Deleter>;
using Mix_ChunkPtr = std::unique_ptr<Mix_Chunk, SDL_Deleter>;
using Mix_MusicPtr = std::unique_ptr<Mix_Music, SDL_Deleter>;
using HapticPtr = std::unique_ptr<SDL_Haptic, SDL_Deleter>;
}
#endif // H_UNIQ_PTR_SDL
Le PO aurait mis les fonctions de libération des pointeurs dans un destructeur, je n'aurais rien dit mais là il les met dans une fonction nommée clean, c'est pas terrible.
Je peux pas utiliser la fonction get(), parce que init() n'est pas utilisé avec un objet... Je devrais mettre cette méthode en static ?
Je peux pas utiliser runtime_error parce que... "runtime_error is not a member of std"... Alors que j'ai bien inclus les headers <stdexcept> et <exception>...
Pour l'instant c'est les seules erreurs que j'ai, mais comme l'erreur pour laquelle j'avais demandé de l'aide était renvoyée après lancement du programme je peux pas savoir si elle persiste
D'ailleurs, avec la fonction get() qui renvoie une référence, on est bien d'accord que si je veux utiliser mon objet je dois pas faire (par exemple) :
Engine::get()->getRenderer();
Mais bien :
Engine::get().getRenderer();
C'est ça ? Parce que j'avais vu une vidéo où quelqu'un avait une fonction qui renvoyait une référence, mais il utilisait -> malgré tout, alors que quand je fais ça mon IDE m'envoie balader
- Edité par EthanWright 25 septembre 2021 à 20:13:04
En fait la classe template std::unique_ptr se charge de libérer la ressource qu' elle détient (on parle d' ownership ) au moment opportun, ou alors lorsque la fin de la portée du unique_ptr est atteinte. Explication avec du code:
constexpr char ARIALFONTPATH[] = "fonts/arial.ttf";
void fooBar()
{
FontPtr font{ TTF_OpenFont( ARIALFONTPATH, 22 ) }
for( unsigned i{0} ; i < 8 ; ++i )
{
doSomething(i);
}
//Ici on arrive à la fin de la portée de 'font' , la police est libérée grâce au RFID.
//Pas besoin d' écrire le code pour libérer le pointeur sur police
}
Et j'ai pas trop compris pour le Deleter Warren79, il devrait remplacer quoi ?
- Edité par EthanWright il y a 31 minutes
Ben, pour faire simple, std::unique_ptr utilise "normalement" deux paramètres template:
le premier correspond au type du pointeur sous-jacent
le deuxième correspond à "comment faut-il libérer correctement les ressources allouées à ce pointeur?"
L'astuce, c'est que le deuxième paramètre template utilise une valeur "par défaut" (std::default_deleter) qui ... appellera automatiquement delete (ou delete[]) sur le pointeur sous-jacent, qui s'applique "dans la plupart des cas", mais dont on peut décider de modifier le comportement si cela ne correspond pas à nos besoins.
Or, il se fait justement que, lorsque l'on manipule des données issues de la SDL, le comportement "par défaut" (l'appel à delete) ne nous convient pas, vu qu'il faut normalement appeler les fonctions spécifiques de la SDL pour libérer les ressources.
Ce que l'on va donc faire, c'est créer ce que l'on appelle "un foncteur" -- on pourrait dire "une classe fonction" car elle n'expose que l'opérateur () permettant de traiter la classe comme s'il s'agissait d'une "simple fonction" -- qui va fournir le "bon" comportement pour la libération des ressources.
Autrement dit, ce foncteur va "tout simplement" se contenter de faire appel à la bonne fonction permettant de libérer les ressources allouées au pointeurs des différents types de données exposés par la SDL.
Une fois que ce foncteur sera défini, il suffira de l'utiliser pour dire "voici les comportements que l'on veut te voir utiliser pour libérer les ressources allouées aux différents types de données fournis par la SDL", et le tour sera joué, car ce seront ces comportements qui seront utilisés.
Et, pour que ce soit encore plus facile, on va déclarer dans un espace de nom (le namespace sdl2) le foncteur en question (SDL_Deleter) et des alias de noms pour les différents types de données, qui seront en réalité ... des spécialisations totale de std::unique_ptr prenant, en premier paramètre, les types exposés par la SDL et, en deuxième paramètre, le foncteur que l'on vient de créer, à savoir
using WindowPtr = std::unique_ptr<SDL_Window, SDL_Deleter>; //SDL_Window *
using RendererPtr = std::unique_ptr<SDL_Renderer, SDL_Deleter>; //SDL_Renderer *
using SurfacePtr = std::unique_ptr<SDL_Surface, SDL_Deleter>; //SDL_Surface *
using TexturePtr = std::unique_ptr<SDL_Texture, SDL_Deleter>; //SDL_Texture *
using RWopsPtr = std::unique_ptr<SDL_RWops, SDL_Deleter>; //SDL_RWops *
using JoystickPtr = std::unique_ptr<SDL_Joystick, SDL_Deleter>; //SDL_Joystick *
using FontPtr = std::unique_ptr<TTF_Font, SDL_Deleter>; //TTF_Font *
using Mix_ChunkPtr = std::unique_ptr<Mix_Chunk, SDL_Deleter>; //Mix_Chunk *
using Mix_MusicPtr = std::unique_ptr<Mix_Music, SDL_Deleter>; //Mix_Music *
using HapticPtr = std::unique_ptr<SDL_Haptic, SDL_Deleter>; //SDL_Haptic *
Et nous pourrons utiliser ces alias de types -- comme sdl2::WindowPtr -- en sachant que les ressources allouées au SDL_Window * sous-jacent seront correctement libérées une fois que l'on n'aura plus besoin de la donnée
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
J'ai essayé la technique avec les unique_ptr, mais je dois pas avoir compris comment faire réellement, parce que quand je fais ça, je mets, par exemple :
sdl2::WindowPtr window{nullptr};
Donc dans ma fonction init(), j'aurais ça :
window = SDL_CreateWindow(/*arguments*/);
Mais mon IDE me dit qu'il ne peut pas convertir SDL_Window* en std::unique_ptr<SDL_Window, SDL_Deleter> Il faudrait que je remplace, par exemple, ma variable window par window->get(), qui me retourne le pointeur en lui-même ?
En dehors de ça, j'ai toujours ce problème-là :
EthanWright a écrit:
J'ai testé, et j'ai quelques soucis :
Je peux pas utiliser la fonction get(), parce que init() n'est pas utilisé avec un objet... Je devrais mettre cette méthode en static ?
Je peux pas utiliser runtime_error parce que... "runtime_error is not a member of std"... Alors que j'ai bien inclus les headers <stdexcept> et <exception>...
- Edité par EthanWright il y a environ 16 heures
Vous avez une idée pour résoudre ces problèmes ? :p
===================================
EDIT : J'ai remplacé (par exemple toujours) window par window.get(), et ça m'annule bien le problème de conversion du pointeur vers unique_ptr, par contre il me met maintenant l'erreur "lvalue required as left operand of assignment", qu'est-ce que je devrais faire je suis perdu
===================================
EDIT2 : J'ai réglé ce problème du coup, à la place de ça :
Du coup mes seuls problèmes restants, c'est le runtime_error qui fonctionne pas et la fonction init() dans le Engine::get() qui demande un objet pour être exécutée
- Edité par EthanWright 26 septembre 2021 à 13:02:07
Sinon prend l'habitude de regarder la documentation officielle sur cppreference , ça te permettra de gagner du temps par rapport à une demande sur un forum (en principe).
Ça fonctionne aussi, merci et merci du conseil ! ^^ Bon par contre j'ai toujours les problèmes avec le runtime_error et la fonction Engine::get()
=============================
EDIT :
J'ai mis de côté les problèmes avec runtime_error et Engine::get() pour l'instant, mais j'ai un problème avec ma classe TextureManager, il me renvoie l'erreur suivante :
|error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = _TTF_Font; _Dp = sdl2::SDL_Deleter]'|
J'avais 4 fois cette erreur, j'ai réussi à en supprimer 2 occurences (en mettant std::move(texture) au lieu de texture elle-même), mais pour les 2 autres occurrences je comprends pas trop où est le soucis Du coup je mets ici les fonctions incriminées :
Dans le sens où l'on doit créer l'engin avant de voir dans quel état il est ;), vu que ses données membres ne sont pas statiques.
Ceci dit, a bien y réfléchir, et parce que j'étais un peu "à coté de mes pompes" lorsque j'ai répondu, il faudrait idéalement même encore modifier un tout petit peu la classe Engine, car ce que l'on veut, c'est que l'on ne puisse y accéder qu'en faisant appel à la fonction get.
Du coup, nous devrions donc lui donner une forme proche de
Pour tes problèmes de runtime, je suis pour aiinsi dire persuadé que c'est parce que ton pointeur sur SystemManager (dans ta classe EntityManager) nous envoie "dans les choux"...
Cela serait d'ailleurs sans doute confirmé en ajoutant une assertion dans la fonction update:
void EntityManager::update() {
assert(sManager && "system manager not initialized");
for(auto& e : entities) {
sManager->update(e.get());
}
}
(requière l'inclusion du fichier d'en-tête <cassert> )
Ceci étant dit, je ne crois sincèrement pas qu'il soit du ressort du gestionnaire d'entités de maintenir le ... gestionnaire de systèmes.
A priori, ces deux gestionnaires devraient se trouver "au même niveau", c'est à dire, dans l'état actuel des choses, que ton gestionnaire de systèmes devrait être accessible ... à partir de ta classe Engine, exactement de la même manière que ta classe EntityManager.
Ce qu'il faudra alors, c'est une fonction qui "fasse le lien" entre les deux gestionnaires, sous une forme qui pourrait ressembler à
/*on s'assure que toutes les entités du gestionnaire d'entités
* soient mises à jour par tous les systèmes du gestionnaire de systèmes
*/
void updateAll(){
auto & entities = Engine::get().entities();
// fonction à créer, sur le meme modèle que la fonction entities()
auto & systems = Engine::get().systems();
/* et pour ce faire, on transmet le gestionnaire d'entités au gestionnaire de systèmes */
systems.update(entities);
}
Cette fonction update ressemblerait donc sans doute à quelque chose comme
/* on s'assure que toutes les entités soient traitées par tous les
* systèmes
void SystemManager::update(EntityManager & entities){
/* et pour ce faire, on passe le gestionnaire d'entité à tous les systèmes
* du gestionnaire de systèmes
*/
for(auto & system : systems)
system.update(entities);
}
Et, enfin, au niveau des différents systèmes, on parcourt l'ensemble des entités et on met à jour celles qui correspondent aux prérequis:
void System::update(EntityManager & entities){
for(auto & entity: entities){ // il faudra les fonction
// begin et end
if(hasComponent(entity.get()))
update(entity.get());
}
}
Alors, bien sur, on peut inverser la logique générale: j'ai décidé de parcourir l'ensemble des systèmes avant de parcourir l'ensemble des entités, mais on pourrait envisager de commencer par parcourir l'ensemble des entités et transmettre l'ensemble des systèmes à chacune d'elle séparément. Il faudrait peut être faire des tests et des benchmarks pour savoir ce qui sera le plus efficace dans ta situation
PS: Je viens de constater que tu fais la même erreur avec ton gestionnaire de textures que celle que tu faisais avec ta classe Engine: si tu n'arrive pas à charger une texture, tu te contente d'afficher un message du genre "coucou, je n'ai pas su charger la texture", mais tu laisse continuer le programme.
Or, le problème reste toujours le même: si ta ressource (ta texture ou ta TTF_Font, en l'occurrence) n'est pas chargée, ben, elle n'existe tout simplement pas pour l'ordinateur (le pointeur est sans doute égal à nullptr).
Et si tu essaye d'accéder à cette ressource (autrement que pour t'assurer qu'elle existe), ben, tu vas partir dans "les choux". Il faut donc décider de l'approche que l'on a du problème, car on peut décider:
Soit, que "c'est bloquant" si une de ces ressources n'existe pas, et donc, qu'on doit faire planter le programme directement (en lançant une exception, comme pour m_window ou m_renderer)
soit que "c'est pas grave, et que l'on "skip" tout le processus qui aurait du utiliser la ressource, si elle n'existe pas (mais cela implique de tester l'existence de la ressource de manière systématique).
Le truc, avec la deuxième solution, c'est que, du coup, on risque d'avoir des textes ou des textures qui ne seront pas affichées, et ca risque de donner un "drôle de gueule" à ton jeu
D'un autre coté, si on fait planter le jeu de manière systématique, ben, cela t'obligera à apporter une solution au problème (en gros: à t'assurer que la ressource que tu essaye de charger se trouve bel et bien à l'endroit où tu espères la trouver ).
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Pour commencer, mon erreur avec le runtime_error est corrigée, c'était simplement que j'avais écrit runitme_error... (oui je suis pas doué :p)
J'ai rajouté un renvoie d'erreur pour le chargement des ressources du coup, parce que c'est vrai qu'un jeu sans les ressources nécessaires ça peut donner des visuels assez spéciaux
Pour ce qui est de la fonction update, je devrais rajouter une méthode qui retourne une référence vers le vector<unique_ptr<Entity>> du coup ? Comme les Entity sont stockées dans le vector qui est privé
Merci énormément pour vos réponses et votre aide en tout cas !
Ben, tes manager devraient sans doute avoir les fonction begin() et end() (en version const et non const), qui renvoient respectivement un itérateur sur le premier élément du conteneur sous-jacent et un itérateur sur ce qui suit le dernier élément du container...
Un peu à la manière de ce que font toutes les collections de la bibliothèque standard (et, pour être précis, il ne s'agit que d'un déport de ces fonctions )
Cela te permettra d'utiliser les même mécanique (boucles et algorithmes) que celles que tu utiliserais avec les containers sous-jacent, et ca, ca n'a pas de prix
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
class EntityManager {
/* un premier alias pour les unique_pointer
* c'est -- typiquement -- un alias qui ne servira qu'ici
* il n'y a donc pas besoin de l'exposer dans
* l'accessibilité publique
*/
using EntityPointer = sd::unique_ptr<Entity>;
/* Un autre alias qui ne servira qu'ici: le tableau de
* (unique_pointer sur) des entités
*/
using EntityArray = std::vector<EntityPointer>;
public:
/* on a besoin des itérateur (constants et non
* constants sur le tableau (de unique_pointer)
* d'entités
* on reprend exactement les mêmes noms que les noms
* officiels (par facilité)
*/
using const_iterator = typename EntityArry::const_iteroatr;
using iterator = typename EntityArray::iterator;
EntityManager() = default;
/* toute la partie publique existante, à laquelle on ajoute */
/* les fonctions begin et end en version non const */
iterator begin(){
return entities.begin();
}
iterator end(){
return entities.end();
}
/* les fonctions begin et end en version const */
const_iterator begin(){
return entities.begin();
}
const_iterator end(){
return entities.end();
}
/* et, pour faire bonne mesure, quelques fonctions
* "exportés" supplémentaire
*/
size_t size() const{// parce qu'il est toujours intéressant
// de savoir combien d'entités il y a
return entities.size();
}
bool empty() const{// parce que c'est plus facile
// de savoir s'il est vide que de
// commencer à se demander combien
// d'entités il y a
return entities.empty();
}
void clear(){ // pour le cas où l'on voudrait virer
// toutes les entités
entities.clear();
}
/* il y aura peut-être d'autres fonctions qui te paraitront utiles à l'usage
* mais celles-ci seront déjà "pas mal" et "utiles"
*/
private:
/* et, tant qu'à faire, on utilise les alias créés
* ici aussi
*/
EntityArray entities;
};
Comme tu le vois, il n'y a rien de bien compliqué dans l'histoire
EDIT: Le gros avantage, si tu suis le même raisonnement pour ton gestionnaire de services, les deux gestionnaires (de services et d'entités) n'ont même plus besoin de se connaitre l'un l'autre: il n'y a plus que la notion de service qui doive connaitre la notion d'entité.
Et, du coup, tu peux déporter toute la logique que je t'expliquais plus haut dans une simple fonction (éventuellement libre) proche de
void updateAll(){
/* j'ai besoin des deux gestionnaires */
auto & services = Engine::get().services();
auto & entities = Engine::get().entities();
for(auto & service : services) { // pour chaque service qui existe
for(auto & entity : entities){ // je passe toutes les
// entités en revue
/* je ne me souviens plus du nom de la fonction
* si l'entité doit être manipulée par le service
* j'ai choisi de l'appeler match et de lui donner
* une entité par référence
*/
if(service.get()->match(*(entity.get())){
/* si l'entité correspond, on demande au
* service de la mettre à jour (on la passe
* aussi par référence pour le coup
*/
service.get()->update(*(entity.get()));
}
}
}
}
Et, bien sur, rien ne t'empêche d'intervertir les deux boucles ("pour chaque entité qui existe, je passe tous les services en revue") en fonction de ce que les benchmark mettront en évidence comme étant "le plus efficace"
- Edité par koala01 27 septembre 2021 à 12:10:10
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Mais il me dit que TextureManager n'est pas déclaré (Components\Sprite.h|21|error: 'TextureManager' has not been declared|), alors que comme on peut le voir je l'ai bien inclus en tête du code... J'ai fait quelque chose qu'il fallait pas ? ^^'
- Edité par EthanWright 27 septembre 2021 à 22:01:46
Mea culpa, j'ai oublié deux const dans le code, je te donne la corection ici:
class EntityManager {
/* un premier alias pour les unique_pointer
* c'est -- typiquement -- un alias qui ne servira qu'ici
* il n'y a donc pas besoin de l'exposer dans
* l'accessibilité publique
*/
using EntityPointer = sd::unique_ptr<Entity>;
/* Un autre alias qui ne servira qu'ici: le tableau de
* (unique_pointer sur) des entités
*/
using EntityArray = std::vector<EntityPointer>;
public:
/* on a besoin des itérateur (constants et non
* constants sur le tableau (de unique_pointer)
* d'entités
* on reprend exactement les mêmes noms que les noms
* officiels (par facilité)
*/
using const_iterator = typename EntityArry::const_iteroatr;
using iterator = typename EntityArray::iterator;
EntityManager() = default;
/* toute la partie publique existante, à laquelle on ajoute */
/* les fonctions begin et end en version non const */
iterator begin(){
return entities.begin();
}
iterator end(){
return entities.end();
}
/* les fonctions begin et end en version const */
const_iterator begin() const{ // ce sont des versions
// const, qu'on a dit
return entities.begin();
}
const_iterator end() const{ // ce sont des versions
// const, qu'on a dit
return entities.end();
}
/* et, pour faire bonne mesure, quelques fonctions
* "exportés" supplémentaire
*/
size_t size() const{// parce qu'il est toujours intéressant
// de savoir combien d'entités il y a
return entities.size();
}
bool empty() const{// parce que c'est plus facile
// de savoir s'il est vide que de
// commencer à se demander combien
// d'entités il y a
return entities.empty();
}
void clear(){ // pour le cas où l'on voudrait virer
// toutes les entités
entities.clear();
}
/* il y aura peut-être d'autres fonctions qui te paraitront utiles à l'usage
* mais celles-ci seront déjà "pas mal" et "utiles"
*/
private:
/* et, tant qu'à faire, on utilise les alias créés
* ici aussi
*/
EntityArray entities;
};
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Par contre j'ai toujours l'erreur avec le "TextureManager has not been declared"
Process terminated with status -805306369
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Mon site web de jeux SDL2 entre autres : https://www.ant01.fr
Mon site web de jeux SDL2 entre autres : https://www.ant01.fr
Mon site web de jeux SDL2 entre autres : https://www.ant01.fr
Mon site web de jeux SDL2 entre autres : https://www.ant01.fr
Mon site web de jeux SDL2 entre autres : https://www.ant01.fr