IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C++ Discussion :

membre constexpr et héritage


Sujet :

C++

  1. #21
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    1- Pourquoi transmets tu un float par référence constante à ta fonction cela n'a pas vraiment de sens

    Si tu descend au niveau du code binaire exécutable, tu te rendras compte qu'une référence est considérée par le compilateur exactement de la même manière qu'un pointeur.

    Or, il n'y a absolument aucun intérêt à transmettre l'adresse d'une donnée (qui nécessite l'équivalent d'un registre entier) si c'est pour représenter une donnée qui est plus petite que la taille de ce registre.

    Par principe, on considère donc de ne transmettre par référence que ... les informations dont la taille est plus grande que celle d'un pointeur

    Par facilité, et parce que cela ne pose pas vraiment de problème, on simplifie cette règle de principe en décidant de ne transmettre par référence que ... les informations dont la taille est supérieure à celle des différents types primitifs.

    Ainsi, il y aurait sans doute largement intérêt à transmettre une donnée de type
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    struct Point{
        char x;
        char y;
    };
    car sa taille n'est que de ... deux bytes


    Sous windows 10, avec Gcc-8.1.0 en version 64 bits, j'obtiens les tailles suivantes :
    • sizeof(int*) --> taille d'un pointeur = 8
    • sizeof(float) --> taille d'un float = 4
    • et, au cas où tu douterais de la taille d'un pointeur: sizeof(char *) --> un pointeur vers une donnée de type différent : 8


    Enfin, la meilleure raison que l'on aie d'éviter la copie de certaines informations (outre la quantité de mémoire qu'elles nécessitent pour être représentées) est que leur processus de copie prend "un temps bête", par exemple, parce qu'il implique:
    • d'allouer de la mémoire en suffisance pour contenir l'ensemble des informations
    • de copier l'ensemble des informations internes de l'objet d'origine vers l'objet de destination

    Un tel problème ne se pose pas avec les types primitifs

    2- Pourquoi travailler avec des pointeurs pour pos et vel (que tu ferais bien d'appeler position et velocity, vu que cela ne changera absolument rien au final )

    Pourquoi ne pas travailler avec des références sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    auto & position = component::get<Position>(e);
    auto & velocity =component::get<Velocity>(e);
    Cela te simplifierais la tâche
    * quelle autre solution ais-je pour éviter d'avoir à réaliser cet héritage, tout en me permettant de filtrer uniquement sur les entités concernées par les composants souhaités ?
    Connais-tu le principe d'une clé Voici à quoi cela ressemble:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    __
      \
       \_____________
        _____________\
                 __  /
        /\/\/\/\/  \/
       / 1 2 3 4 5 6
    __/
    Quand tu fais entrer ta clé dans le barillet, chaque pointe va enfoncer une petite pièce métallique dans son encoche de manière à ce qu'elle soit à fleur avec le système rotatif. Si toutes ces pièces sont correctement enfoncées, la clé peut tourner dans le barillet. Si une des pièce n'est pas à fleur, la clé ne peut pas tourner.

    Par exemple, tu remarque qu'il n'y a pas de pointe au niveau du numéro 5 sur le dessin que j'ai fait

    Cela signifie que, s'il y avait une petite pièce métallique au niveau de ce numéro 5, elle ne serait pas enfoncée, et qu'elle empêcherait la clé de tourner, parce que la clé que tu essaye d'utiliser est -- peut-être -- destinée à la maison voisine

    Hé bien ce que je vais te proposer, c'est de créer un système de clés qui te permettra de déterminer rapidement si "tous les composants" nécessaires sont présent pour une entité donnée.

    En effet, tout ce dont on a besoin pour être en mesure de déterminer ce point, c'est ... un bit spécifique par type de composant : si ce bit a une valeur égale à 0, c'est que le composant est absent, si le bit a une valeur égale à 1, c'est que le composant est présent.

    Sur le nombre de bits qui permet de rerpésenter une valeur numérique entière de type unsigned long long (pour lequel on dispose -- sans doute -- d'un alias de type hyper connu, à savoir : size_t) nous devrions donc -- a priori -- être de représenter la présence (ou l'absence) de pas moins de ... 64 composants différents!

    Comment Hé bien faisons simple, et utiliser la classe std::bitset, sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    using KeyType = std::bitset<64>;
    Une fois que le type représentant notre notion de clé est créé, le principe est "relativement simple":

    1- pour chaque entité, on crée une clé

    2 - pour chaque type de composant existant, on défini l'index (dans la clé) du bit qui permet de définir s'il est présent (ou non) pour une entité donnée.

    Par exemple, le composant de type Position pourrait prendre l'indice 0 et le composant de type Velocity pourrait prendre l'indice 1.

    3- Chaque fois que l'on ajoute un composant à une entité, on fait passer le bit correspondant à 1 dans la clé correspondant à l'entité

    4- Chaque fois que l'on retire un composant à une entité, on fait passer le bit correspondant à 0 dans la clé correspondant à l'entité

    5- pour chaque service, on crée une "emprunte" représentant les composants dont on a besoin pour travailler

    6- Avant de faire quoi que ce soit au niveau du service, on compare la clé correspondant à l'entité sur laquelle on a prévu de travailler avec l'emprunte que l'on a créé.

    Cette comparaison se fera sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    static const KeyType pass{keyIndex(Velocity)|keyIndex(position)/*|... */}; // l'emprunte
    auto working = getKey(entity);
    if(working & pass == pass){
       /* on a ce qu'il faut, on peut travailler
    }
    Ce genre de test étant particulièrement rapide, si tu organise correctement la manière de maintenir toutes les clés existantes en mémoire, tu n'a aucun soucis à te faire quant aux performances de ton programme

    Par contre, je vais être "très méchant" avec toi, et te laisser chercher un peu comment mettre une telle logique en oeuvre... sinon, tu n'apprendra rien

    Mais, entre les template et quelques macros bien senties (pour faciliter la réécriture d'un code qui est toujours quasiment le même), tu devrais pouvoir t'en sortir "sans trop de problème"

    a la destruction d'une entité, comment m'assurer que tous les composants seront détruits
    Te souviens tu des cinq principe élémentaires, et du SRP en particulier

    Car, pour représenter "l'ensemble des composant" (d'un type particulier) qui existent, nous avons très certainement intérêt à créer une une permettant de représenter la notion de "container de composants"; quitte à créer des fonctions libres qui cachent l'existence de cette classe.

    Cette classe prendrait sans doute une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    template <typename COMPTYPE>
    class ComponentHolder{
    public:
        template <typename ... ARGS>
        COMPTYPE & create(EntityType entity, ARGS ... param);
        void destroy(EntityType entity);
        /* ... */
    }
    (je vais encore être très mauvais en te laissant le reste, mais je suis persuadé d'en avoir déjà parlé )
    Et nous pourrions alors envisager un système de signaux et de slots (j'en ai développé un qui pourrait être sympa, et que tu retrouveras -->ici<-- ) qui permette à notre "gestionnaire d'entité" d'envoyer un signal à tous les "conteneurs de composant" lorsqu'une entité est détruite.

    Pour ce faire, notre "gestionnaire d'entité" prendrait une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class EntityHolder{
        using destroy_type = Tools::Signal<EntityType>;
        public:
            using slot_type = typename destroy_type::slot_type
            Tools::Connection onEntityDestroy(slot_type slot);
        private:
             destroy_type destroy_;
    }
    et nous pourrions donc corriger notre notion de conteneur de composant pour lui donner la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    template <typename COMPTYPE>
    class ComponentHolder{
    public:
        template <typename ... ARGS>
        COMPTYPE & create(EntityType entity, ARGS ... param);
        void destroy(EntityType entity);
        void entityDestroyed(EntityType entity){
            if(exists(entity)
                destroy(entity);
        }
        /* ... */
    }
    en veillant à connecter ce qui deviendra le slot (entityDestroyed) à notre signal au travers de la fonction onEntityDestroyed de notre "gestionnaire d'entités"
    (reporte toi à la documentation pour savoir comment implémenter ceci )

    Au final, il y a beaucoup à dire sur le sujet... Mais mon intervention est déjà largement assez longue que pour que je décide de te laisser chercher un peu par toi-même

    Si tu n'y arrive vraiment pas, je courrerai de nouveau le risque de faire péter toutes les limites imposées par le forum pour te donner la solution
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  2. #22
    Membre actif Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Points : 219
    Points
    219
    Par défaut
    Merci pour ces retours.

    En ce qui concerne les bit-set, oui, j'avais déjà fait une première implémentation qui utilisait ce principe. j'y pensais effectivement quand on m'a dit, dans un autre post "ne t'embête pas avec des bit-sets" (mais c'était probablement dans un autre contexte). J'ai donc cherché des solutions alternatives, mais je vais donc me permettre de revenir à cette solution (d'autant plus qu'elle est particulièrement simple à mettre en œuvre).

    Pour les float transmis par référence.... En fait c'est une (mauvaise ?) habitude de prise. Par défaut, je transmets tout ce que je peux par référence. De cette manière, je m'assure qu'une données manipulée par une fonction est bien celle que je lui transmets. Le fait de passer en référence constante me permet, de m'assurer, dès la compilation, que la fonction en question ne modifie pas cette donnée. Mais oui... Je peux simplement faire une copie constante donc je vais faire plus attention à la manière dont je transmets les données.

    Car, pour représenter "l'ensemble des composant" (d'un type particulier) qui existent, nous avons très certainement intérêt à créer une une permettant de représenter la notion de "container de composants"; quitte à créer des fonctions libres qui cachent l'existence de cette classe.

    Cette classe prendrait sans doute une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    template <typename COMPTYPE>
    class ComponentHolder{
    public:
        template <typename ... ARGS>
        COMPTYPE & create(EntityType entity, ARGS ... param);
        void destroy(EntityType entity);
        /* ... */
    }
    (je vais encore être très mauvais en te laissant le reste, mais je suis persuadé d'en avoir déjà parlé )
    J'ai déjà cette classe, ça tombe bien .


    Je vais donc m'y remettre et réintégrer ces principes.

    Enfin :
    Au final, il y a beaucoup à dire sur le sujet... Mais mon intervention est déjà largement assez longue que pour que je décide de te laisser chercher un peu par toi-même

    Si tu n'y arrive vraiment pas, je courrerai de nouveau le risque de faire péter toutes les limites imposées par le forum pour te donner la solution
    Et je t'en remercie beaucoup pour ces conseils et autres orientations très pédagogiques ; mais clairement, mon objectif n'est pas d'avoir tout clé en main / prémâché... Si tel était le cas, j'achèterais des programmes tout faits ou copierais tout bêtement ce que je trouve à droite et à gauche sans même essayer de comprendre. J'ai bien conscience que, la manière dont j'aborde les choses peut laisser sous-entendre que je ne souhaite qu'obtenir un résultat. Mais crois moi, mon but avant tout est d'apprendre. Certes, je me suis peut être laissé emporté par un projet qui va, clairement, au-delà de ma compétences initiales en programmation, mais d'un autre côté, je pense aussi que, dans un certain sens, le développement d'un ECS, parmi d'autres types de projets, permet de couvrir un certain nombre de sujets très intéressants et formateurs et qui, pour diverses raisons, me botte bien.
    Mais soit, tu as certainement raison, je dégaine probablement très vite la solution "poser la question sur le forum" là où, une bonne nuit de sommeil + un bon café apporte sans peine une solution. Je tâcherais de remuer mes neurones plus en profondeur avant de poster.
    Nul besoin donc de m'apporter des solutions toutes crues (même si c'est déjà ou presque le cas), ce n'est clairement pas ce que je cherche ici.

    Merci encore pour toute l'aide apportée.

  3. #23
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par BioKore Voir le message
    Merci pour ces retours.

    En ce qui concerne les bit-set, oui, j'avais déjà fait une première implémentation qui utilisait ce principe. j'y pensais effectivement quand on m'a dit, dans un autre post "ne t'embête pas avec des bit-sets" (mais c'était probablement dans un autre contexte). J'ai donc cherché des solutions alternatives, mais je vais donc me permettre de revenir à cette solution (d'autant plus qu'elle est particulièrement simple à mettre en œuvre).
    Sans doute
    Pour les float transmis par référence.... En fait c'est une (mauvaise ?) habitude de prise.
    Entendons nous : Transmettre "par défaut" un paramètre sous la forme d'une référence constante est une excellente habitude.

    En tout état de cause, c'est surement une meilleure habitude que de transmettre (par défaut, toujours) un paramètre sous la forme d'une référence non constante; Et c'est toujours mieux que celle qui consiste à transmettre (par défaut) ce paramètre sous forme de pointeur

    L'idée qui se cache derrière cet ordre (référence constante -> référence non constante -> pointeur) étant de s'imposer de manipuler les paramètres sous la forme imposant le plus de restriction possible (doit exister et ne peut pas être modifié) et de ne lever ces restrictions "au fur et à mesure"(doit exister, peut être modifié / peut ne pas exister) que lorsque l'on n'a vraiment pas d'autre choix.

    De cette manière, on évite au maximum les "effets de bord indésirables"; en plus bien sur de s'éviter toutes les questions tordues que peuvent motiver l'utilisation des pointeurs

    Mais, comme je te l'ai fait remarquer, cela n'a pas vraiment de sens pour tout ce qui est "de taille plus inférieure à celle d'un pointeur" et, de manière générale, pour tous les types primitifs

    Mais, sinon, à choisir, je préféerai toujours voir que tu décide de transmettre un float sous forme de référence constante plutôt que de décider de le transmettre sous forme de pointeur

    Par défaut, je transmets tout ce que je peux par référence. De cette manière, je m'assure qu'une données manipulée par une fonction est bien celle que je lui transmets.
    Cela peut se comprendre

    Mais, là, nous entrons dans un domaine bien particulier qui est la différence entre la sémantique de valeur et la sémantique d'entité. Ce qui nous amène à créer des distinctions supplémentaires:

    Pour rappel, la sémantique de valeur se caractérise par le fait qu'il est tout à fait possible, à un instant T de l'exécution du programme, de se retrouver avec plusieurs données en mémoire présentant exactement les mêmes valeurs.

    L'un dans l'autre, si l'on pouvait garantir que la création d'une copie ne pose pas de problème (de performance ou autre), l'idéal serait de transmettre les données qui présente cette sémantique par valeur (pour éviter les effets de bord non désirés), quitte à ce que la fonction renvoie la "copie modifiée" une fois qu'elle a fini de travailler pour que la fonction appelante puisse profiter des modifications apportées (ex : int modifyTheValue(int value))

    Et, ma foi, si l'on voulait que les modifications apportées soient répercutées sur la variable d'origine, rien n'empêcherait l'utilisation d'un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    int i{15};
    i = modifyTheValue(i);


    D'un autre coté, lorsque l'on manipule des données qui présentent une sémantique d'entité (ex: un compte bancaire), on ne veut surtout pas qu'il existe, à un moment T de l'exécution du programme, deux instances en mémoire de la classes qui présentent exactement les mêmes valeur : il faut impérativement garantir le fait que l'on manipule (au sein d'une fonction appelée) exactement la donnée que l'on décide de transmettre à la fonction

    Autrement dit, les classes qui présentent une sémantique d'entité sont -- par nature -- non copiables et non assignables.

    Il y a une dizaine d'années, je t'aurais dit que tu pouvait garantir cette interdiction de copie et d'assignation en déclarant SANS LES DEFINIR le constructeur de copie et l'opérateur d'affectation au sein de l'accessibilité privée. Cela aurait suffit, mais cela pouvait aussi poser quelques problèmes très spécifiques

    Depuis l'arrivée de C++11, il est possible d'obtenir un résultat similaire, mais assorti de garanties supplémentaires des plus intéressantes en déclarant le constructeur de copie et l'opérateur d'affectation comme étant delete, sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class NonCopyable{
    public:
        NonCopyable(NonCopyable const &) = delete; // constructeur de copie
        NonCopyable &operator= (NonCopyable const &) = delete // opérateur d'affectation
        /* ... */
    }
    Quoi qu'il en soit, si cette interdiction de créer une copie et de provoquer l'assignation est mise en place, le compilateur devrait t'engueuler comme du pu si tu décide de transmettre une donnée présentant la sémantique d'entité par valeur... Et nous sommes donc, bel et bien, dans une situation dans laquelle la transmission sous forme de référence constante devrait être privilégiée par rapport à toutes les autres possibilités permettant d'éviter la copie de la donnée


    Le fait de passer en référence constante me permet, de m'assurer, dès la compilation, que la fonction en question ne modifie pas cette donnée.
    Et tu as tout à fait raison d'imposer cette restriction sur les manipulations que peuvent effectuer les fonctions auxquelles tu fais appel.

    Mais le problème est toujours le même : appliquer "comme un singe" une bonne habitude sans prendre la peine d'en comprendre tous les tenants et les aboutissants n'est jamais une bonne chose

    J'en suis presque arrivé à préférer (du point de vue informatique, s'entend) à ce que l'on me dise "je fais de telle manière, mais je ne sais pas pourquoi" plutôt que de m'entendre répondre "je fait de telle manière par habitude, et je ne sais pas comment je pourrais faire autrement"

    Les habitudes (lorsqu'elles sont justifiées et correctes) sont une très bonne chose, mais pouvoir s'en éloigner en justifiant la raison pour laquelle on le fait, c'est encore mieux


    Et je t'en remercie beaucoup pour ces conseils et autres orientations très pédagogiques ; mais clairement, mon objectif n'est pas d'avoir tout clé en main / prémâché... Si tel était le cas, j'achèterais des programmes tout faits ou copierais tout bêtement ce que je trouve à droite et à gauche sans même essayer de comprendre. J'ai bien conscience que, la manière dont j'aborde les choses peut laisser sous-entendre que je ne souhaite qu'obtenir un résultat.
    Houlà... j'ai l'impression de t'avoir vexé sur ce coup. Si tel est le cas, je te présente mes excuses les plus plates (bien que je n'arrive pas à mettre le doigt sur ce qui aurait pu te vexer ) car ce n'était absolument pas dans mes intentions.

    C'était d'avantage une sorte de private joke pour me rappeler qu'il m'est déjà arrivé deux ou trois fois, depuis que je hante ce forum, de passer un temps considérable à rédiger une réponse pour obtenir, après avoir cliqué sur le bouton "envoyer la réponse", une réponse de la part du forum du genre de
    désolé, votre réponse dépasse de (autant de caractères) la limite des 65 000 et quelque (sans doute 65 565 ) caractères autorisés par ce forum
    Mais crois moi, mon but avant tout est d'apprendre.
    Oui, j'en suis bien conscient, ne t'en fais pas

    Certes, je me suis peut être laissé emporté par un projet qui va, clairement, au-delà de ma compétences initiales en programmation
    Ben, c'est comme cela que l'on peut s'amélorier, non

    mais d'un autre côté, je pense aussi que, dans un certain sens, le développement d'un ECS, parmi d'autres types de projets, permet de couvrir un certain nombre de sujets très intéressants et formateurs et qui, pour diverses raisons, me botte bien.
    Et tu as tout à fait raison!!!

    Le plus difficile dans l'histoire, c'est qu'il faut arriver à "faire le switch" entre une approche clairement orientée objet et l'approche très spécifique requise par un ECS

    Mais soit, tu as certainement raison, je dégaine probablement très vite la solution "poser la question sur le forum" là où, une bonne nuit de sommeil + un bon café apporte sans peine une solution. Je tâcherais de remuer mes neurones plus en profondeur avant de poster.
    Il y a ca, sans doute en partie...

    Mais il y a aussi la nécessité d'arriver à assimiler mes réponses, dont la seule longueur les rend déjà difficiles à assimiler

    Et, avec le temps j'ai bien pris conscience du problème de certaines (pardon : de la plupart ) de mes réponses C'est bien pour cela que j'essaye de me limiter autant que possible aux aspects absolument indispensable, tout en rappelant que la suite pourra arriver (si tant est que tu en aies besoin) "sur simple demande"
    Nul besoin donc de m'apporter des solutions toutes crues (même si c'est déjà ou presque le cas), ce n'est clairement pas ce que je cherche ici.
    J'aime autant me limiter à te donner les orientations... Comme je l'ai dit, c'est le meilleur moyen pour toi d'apprendre de nouvelles choses, et de gagner en expérience

    Mais j'ai aussi douloureusement conscience que de "simples orientations" sont parfois insuffisantes, et qu'une solution "presque toute faite" s'avère parfois nécessaire


    Merci encore pour toute l'aide apportée.
    Mais de rien, avec plaisir
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  4. #24
    Membre actif Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Points : 219
    Points
    219
    Par défaut
    Non, pas vexé du tout, mais je remet un peu en question la facilité avec laquelle je viens crier au secours alors que j'ai déjà une bonne partie des pions en main...
    Et puis justement, comme tu le dis, ça m’embête de te voir te casser en deux pour m'expliquer bien les choses sans que je ne prenne le temps de comprendre plus en profondeur ces explications.

    Mais le problème est toujours le même : appliquer "comme un singe" une bonne habitude sans prendre la peine d'en comprendre tous les tenants et les aboutissants n'est jamais une bonne chose
    Entièrement d'accord sur ce point, et, en temps normal c'est aussi ce que j'ai tendance à me dire, mais je me rends compte que je dois faire plus d'efforts à appliquer ça.

    Mais, comme je te l'ai fait remarquer, cela n'a pas vraiment de sens pour tout ce qui est "de taille plus inférieure à celle d'un pointeur" et, de manière générale, pour tous les types primitifs
    Je commence à appliquer ça sur mes prochaines implémentations. Je ne m'en étais jamais rendu, jusqu'alors, à me poser la question sur "est-ce que la taille d'un pointeur est plus grande ou plus faible que la donnée sur laquelle il pointe"... Ça se tiens.
    Après, je ne me cherche pas d'excuses, mais parfois, lorsque l'on aborde le sujet des "performances" au niveau programmation, j'avais très souvent des retours du genre "ça ne sert à rien d'essayer de gagner des µs avec ce genre de manipulations, c'est plus s'embêter qu'autre chose".
    Justement, ce que j'aime bien ici, c'est qu'au moins, le sujet n'est pas stoppé net, et ces quelques µs de gagnées, justement, sont toujours bonnes à prendre surtout quand cela vient simplement avec la mise en place de bonnes pratiques qui ne mangent pas de pain à prendre (mais qu'il faut néanmoins comprendre pour pouvoir en tirer le bénéfice maximum).

    Merci encore... Malgré mes efforts, je reviendrais, "malheureusement", encore embêter le forum avec mes problèmes Cet ECS n'est pas encore terminé pour moi.

  5. #25
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par BioKore Voir le message
    Je commence à appliquer ça sur mes prochaines implémentations. Je ne m'en étais jamais rendu, jusqu'alors, à me poser la question sur "est-ce que la taille d'un pointeur est plus grande ou plus faible que la donnée sur laquelle il pointe"... Ça se tiens.
    Après, je ne me cherche pas d'excuses, mais parfois, lorsque l'on aborde le sujet des "performances" au niveau programmation, j'avais très souvent des retours du genre "ça ne sert à rien d'essayer de gagner des µs avec ce genre de manipulations, c'est plus s'embêter qu'autre chose".
    Il est vrai qu'il ne sert pas à grand chose de vouloir absolument gagner quelque µs au moment d'écrire le code car, comme le disait si bien l'autre (je crois que c'est Knuth)
    Premature optimization is root of all evil
    (l'optimisation prématurée est la route vers tous les maux)
    Mais le sujet nous prouve (une fois de plus) que rien n'est jamais simple en informatique, car
    Justement, ce que j'aime bien ici, c'est qu'au moins, le sujet n'est pas stoppé net,
    C'est bien là toute l'astuce:
    • primo, tu te lances dans un projet dans lequel tu as une contrainte de temps assez importante : dans l'idéal, tu veux pouvoir garantir les 60 fps (même si 30 fps pourraient suffire).
    • secondo, le passage par référence (de préférence constante) permet de gagner énormément en performances dés que la copie d'une donnée demande du temps (ce qui est le cas de toutes les collections, l'un dans l'autre; une chaine de caractères étant ici considérée comme ... une collection )
    • tertio, le respect de la const-correctness et le fait d'éviter au maximum les effets de bords facilitent énormément la vie du développeur

    ce qui nous amène à
    et ces quelques µs de gagnées, justement, sont toujours bonnes à prendre surtout quand cela vient simplement avec la mise en place de bonnes pratiques qui ne mangent pas de pain à prendre (mais qu'il faut néanmoins comprendre pour pouvoir en tirer le bénéfice maximum).
    Ben oui, et c'est parfois alussinnant

    Au point que le simple fait de remplacer une fonction du genre de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void foo(std::string str){
        /* peu importe la logique */
    }
    ou du genre de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void bar(std::vector<UnType> tab){
        /* peu importe la logique */
    }
    par des fonctions proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void foo(std::string const & str){
        /* la même logique */
    }
    ou du genre de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void bar(std::vector<UnType> const & tab){
        /* la même logique */
    }
    peut régulièrement faire gagner bien plus que "quelques µs", et que la différence en devient facilement palpable pour l'utilisateur

    Après, il est toujours intéressant de s'intéresser un minimum à la manière dont les choses sont traitées au niveau hardware, car, si le fait d'appliquer des règles simples lui permet de traiter plus avantageusement les données de petites tailles, ce n'est pas forcément une optimisation prématurée (surtout si cela ne rend pas le code plus difficile à lire)
    Merci encore... Malgré mes efforts, je reviendrais, "malheureusement", encore embêter le forum avec mes problèmes Cet ECS n'est pas encore terminé pour moi.
    Je n'en doute pas
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #26
    Membre actif Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Points : 219
    Points
    219
    Par défaut
    Bon !

    J'ai pu prendre un peu de temps ce soir pour refactoriser tout ça.

    Globalement, je suis content de la facilité d'utilisation de la chose.

    J'ai donc testé trois (en fait 4) types d'implémentation différentes pour les systèmes :
    * une classe avec héritage un peu comme mes anciennes méthodes mais en plus propre
    * une classe sans héritage, avec "bind" des entités contenant les composants nécessaires (la fonction "execute" ne boucle donc que sur les composants présents, comme avant)
    * une classe sans héritage, sans bind des entités. Le bouclage se fait sur toutes les entités, et si l’entité en cours de lecture comprends tous les éléments requis, on la traite.
    * une simple fonction basique... Le traitement se fait aussi conditionnellement sur l'ensemble des entités, comme pour la solution précédente.

    On pouvait s'y attendre, mais la seule différence que je relève, à ce niveau, entre ces 4 solutions c'est la manière de les utiliser. Mais je suis content, je peux très facilement switcher entre chacune de ces solutions. Petit à petit, au fil des ré-implémentations, j'arrive à dégrossir / simplifier l'ensemble du code.

    Par contre, niveau perfs, je plafonne toujours à 25 FPS pour 128000 particules
    Je me répète, mais je trouve ça ridicule. D'autant plus que je ne traite ni la couleur, ni la durée de vie etc... Uniquement la position.
    Le pire c'est que parmi mes premières implémentations, qui méritaient pas mal de simplification, j'arrivais à monter jusqu'à 360000 particules pour plus de 25 FPS (je ne me souviens plus exactement).
    Même si le calcul des positions était différent, ça fait quand même pas mal de différence je trouve...

    Je vais devoir trouver comment améliorer les performances de tout ça... Et voir si j'arrive à comprendre ce que me recrache le profiler .
    Dans tous les cas, je referais une première lecture pour corriger / modifier ce qui peut l'être trivialement.




    Après une très brève analyse de ce que me ressort le profiler, pour le peu de ce que je comprends, le programme passe beaucoup de temps à récupérer les références vers les composants, ce qui est finalement, ce que l'on souhaite gommer avec l'utilisation d'un ECS...
    Selon moi, ce qui cause ce problème, c'est que l’exécution boucle sur les entités et non directement sur les composants. Or, le stockage des composants évolue "aléatoirement" dans le cycle du programme. Le composant référencé par l'index 36 par exemple, est peut être très loin de celui pointé par l'index 35.
    Partant de ce postulat, plutôt que de boucler sur les entités, j'ai décidé de réaliser un système bouclant sur le composant principal (ici la position), comme sur mes anciennes implémentations. Je récupère l'index lié à ce composant, regarde si la signature de cette entité match avec la signature de la fonction, et si c'est le cas, je vais chercher les autres composants.
    Ce processus me permet de passer de 25 FPS à 33 FPS. Il me reste donc pas mal de boulot à faire.
    Cependant, dans le cas présent où aucune entité n'est détruite/remplacée et où elles sont toutes créées dans l'ordre (et normalement appelées dans l'ordre), je pourrais m'attendre dans ce cas particulier à ne voir aucune différence entre ce mode de fonctionnement et le fait de boucler sur les entités.
    Je dois mieux comprendre le fonctionnement de tout ça si je veux trouver comment optimiser la chose...

  7. #27
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Aurais-tu éventuellement un dépot git (ou autre) à partrir duquel nous pourrions récupérer tes sources Cela nous permettrait d'avoir une vision plus globale du système mis en place

    Mais, effectivement, il est préférable de boucler sur l'un des composants au lieu de boucler sur les entités. Mieux, encore, j'aurais tendance à penser qu'il serait sans doute préférable de boucler sur le composant Velocity, du moins, pour le système qui calcule la position suite au déplacement des particules, en veillant bien sûr à supprimer les composant de type Velociity lorsqu'elle devient nulle, et je m'explique:

    A priori, toutes tes particules disposent forcément d'un composant Position, y compris les particules "inertes" ou "statiques" (comprends : les particules qui ne se déplacent pas).

    Mais, d'un autre coté, les particules qui ne bougent pas ne devraient pas avoir ... de composant Velocity. Pour N particules, tu devrais donc en avoir
    • S qui ne bougent pas, et qui n'ont donc par de composant Velocity et
    • M qui disposent d'un composant Velocity et pour lesquelles il est "logique" de calculer la nouvelle position

    Or, il n'y a rien à faire : tu n'as absolument aucune raison de ne serait-ce qu'envisager de calculer la nouvelle position d'une particule qui ne bouge pas

    Du coup, si tu décides de boucler sur la liste des composants de type Velocity au lieu de boucler sur la liste des composants de type Position, au lieu de boucler sur les 173 000 particules qui existent, tu pourrais te contenter de boucler sur ... beaucoup moins d'éléments

    En outre, cela te permettrait d'éviter d'avoir à vérifier les clés, vu que toutes les particules qui ont un composant de type Velocity ont forcément un composant de type Position: En effet, une particule qui n'a plus de position est considérée comme détruite. Oui, mais, si une particule est détruite, elle n'a -- forcément -- plus aucun besoin d'avoir une vitesse

    En outre, je ne sais pas comment tu as envisagé ton affichage, mais, as tu pensé au "double buffering"

    L'idée est relativement simple : pendant que ta carte graphique affiche les particules de la frame F, tu effectue déjà les calculs nécessaires à l'affichage de la frame F+1.

    Pour ce faire, nous allons littéralement partager l'espace mémoire réservé à l'affichage en deux (ou bien nous allons doubler cet espace mémoire ce n'est qu'une question de point de vue ) pour obtenir deux espaces mémoire que je vais nommer par faciliter "espace 1 "et "espace 2".

    Pendant que ta carte graphique produit l'affichage de la frame F, à partir des données qui se trouvent dans "espace 1", tu pourras déjà effectuer tous les calculs nécessaire à l'affichage de la frame F+1 et placer les données dans "espace 2".

    Une fois que la carte graphique a fini l'affichage de la frame (ou une fois que le délais prescrit est atteint, si tu veux fixer le nombre de fps à une valeur spécifique), tu interverti "espace 1" et "espace 2", de manière à ce que la carte graphique utilise les données de "espace 2" pour l'affichage de la frame F, pendant que tu places le résultat de tes calculs pour l'affichage de la frame F+1 dans "espace 1" .

    Et, bien sur, étant donné que l'affichage s'effectue dans une boucle, l'inversion des espaces mémoire se fera systématiquement, si bien que tu effectuera systématiquement les calculs pour préparer l'affichage de la frame F+1 dans un espace mémoire alors que la carte graphique produira l'affichage de la frame F à partir de... l'autre espace mémoire.

    De cette manière, si tu cherches à garantir les 60fps, tu pourras disposer de l'ensemble des 0.01666 secondes pour faire ton calcul au lieu de ne pouvoir disposer que de ... 0.01666 secondes - (le temps nécessaire pour l'affichage)

    Entre le temps gagné à ne pas essayer d'effectuer des calculs (et des vérifications) inutiles et les quelques milli(micro )secondes grappillées grâce au double buffering, tu devrais pouvoir arriver à gagner pas mal de performances

    Tu remarqueras d'ailleurs que les évolutions proposées n'ont rien à voir avec de la "mico optimisation" : cela reste de la pure logique (et cela n'entre donc pas encore dans le cadre de "l'optimisation prématurée", d'autant plus que l'expérience démontre que l'on en a besoin )
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  8. #28
    Membre actif Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Points : 219
    Points
    219
    Par défaut
    Bonjour,

    Oui, j'ai un dépôt Git, mais cela fait bien longtemps que je ne l'ai pas utilisé + je n'ai jamais essayé depuis windows... Je te tiens au courant quand j'aurais mis les sources dessus.

    En outre, je ne sais pas comment tu as envisagé ton affichage, mais, as tu pensé au "double buffering"
    J'y ai pensé oui. Cependant, j'ai entendu dire que les systèmes de rendu le réalisait déjà plus ou moins automatiquement. Aussi, je pensais qu'il y aurait déjà pas mal d'optimisations à faire avant de m'intéresser à ce sujet. Néanmoins, le profiler me dit que le "vertex append" de SFML est assez gourmand ; donc il y a clairement à gagner avec ça.
    Ça et le threading aussi. J'ai essayé de réaliser un thread par particule au sein d'un même système (méthode un peu bourrin je l'avoue, mais c'est avant tout pour voir si je pouvais jouer avec ça avant de me lancer dans un système de batching). Problème : on souhaite accéder au même vecteur en écriture via chaque threads. J'ai vu ça et là que c'était possible malgré tout donc je dois faire quelques tests autour de ça.

    Sinon, dans un certain sens, si on boucle sur les composants, alors le système de signatures (clés) par entité devient une moindre importance compte tenu que je peux vérifier la présence ou non d'un composant pour une entité particulière sans problèmes. Faut que je test ce que ça donne au niveau performances.

    Pour le moment, dans mon "moteur de particules test" toutes les particules bougent donc, elles ont toujours une vitesse. Mais dans l'absolu, on est d'accord. Si un composant n'a plus d'effet sur l'entité, alors il est avantageux de supprimer ce composant. Concrètement, je pourrais déjà le faire compte tenu du fait que la vitesse est random sur intervalle [0, 60]. Peut-être que le calcul est réalisé inutilement pour une petite poignée de particules.
    Mais je souhaite mettre en place mon système d'event avant ça (ou signal / slot).

    Bref, ça me fait vraiment pas mal de choses à faire. Cependant, maintenant que je pense avoir saisi un peu comment concaténer tout ça, je veux vraiment m'assurer que, pour ce que j'ai déjà fait et/ou que je sais faire, mes choix sont bons (dans le sens convivialité / maintenabilité). Comme on en discute encore ici, je ne me suis pas encore complètement arrêté sur la manière dont je souhaite réaliser / intégrer mes systèmes. Selon moi, la priorité est là.

    Et il en va de même pour les composants. Finalement, je m’aperçoit qu'il est très bon, pour les performances, d'avoir des composants un peu plus imposants que juste "vitesse", "position", "orientation" etc... Je pense m'orienter vers des composants légèrement plus généraux. Par exemple, je pense que les cas où j'aurais besoin d'une vitesse SANS position (est-ce que ça a vraiment un sens ?) seront limités. Idem pour l'orientation.... Donc il est bon de me dire "ok, je vais regrouper ces trois composants en un seul". En agissant comme ça, je suis passé de 30 FPS à 77 FPS ; donc d'un truc pas top, à une application "acceptable". L'objectif est donc de limiter au stricte nécessaire les systèmes nécessitant plus d'un seul composant. En tout cas, au moins à ceux qui, réalisent des calculs déjà "lourds".

    Je te tiens au courant pour le Git. Dans la semaine si je prends le temps de voir ce que ça donne.

  9. #29
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par BioKore Voir le message
    Oui, j'ai un dépôt Git, mais cela fait bien longtemps que je ne l'ai pas utilisé + je n'ai jamais essayé depuis windows... Je te tiens au courant quand j'aurais mis les sources dessus.
    Ce n'est pas bien compliqué, tu verras
    J'y ai pensé oui. Cependant, j'ai entendu dire que les systèmes de rendu le réalisait déjà plus ou moins automatiquement. Aussi, je pensais qu'il y aurait déjà pas mal d'optimisations à faire avant de m'intéresser à ce sujet. Néanmoins, le profiler me dit que le "vertex append" de SFML est assez gourmand ; donc il y a clairement à gagner avec ça.
    Ça et le threading aussi. J'ai essayé de réaliser un thread par particule au sein d'un même système (méthode un peu bourrin je l'avoue, mais c'est avant tout pour voir si je pouvais jouer avec ça avant de me lancer dans un système de batching). Problème : on souhaite accéder au même vecteur en écriture via chaque threads. J'ai vu ça et là que c'était possible malgré tout donc je dois faire quelques tests autour de ça.
    Ben, alors, si tu as une série de chose à tester, qu'attends tu pour le faire Des journées de 48 heures, peut-être
    Sinon, dans un certain sens, si on boucle sur les composants, alors le système de signatures (clés) par entité devient une moindre importance compte tenu que je peux vérifier la présence ou non d'un composant pour une entité particulière sans problèmes. Faut que je test ce que ça donne au niveau performances.
    Effectivement, dans le cadre bien spécifique de ton projet dans lequel chaque entité ne peut avoir au maximum que deux composants, dont la présence de l'un dépend en grande partie de la présence de l'autre, le système de clé / signature est sans doute un peu "overkill"

    Mais, dés que tu voudras gérer des entités un peu plus conséquentes que "de simples particules", que tu voudras pouvoir créer une arme qui peux être sertie de trois joyaux afin de lui donner des caractéristiques magique, par exemple, ce système de clé prendra "tout son sens"

    Pour le moment, dans mon "moteur de particules test" toutes les particules bougent donc, elles ont toujours une vitesse. Mais dans l'absolu, on est d'accord. Si un composant n'a plus d'effet sur l'entité, alors il est avantageux de supprimer ce composant. Concrètement, je pourrais déjà le faire compte tenu du fait que la vitesse est random sur intervalle [0, 60]. Peut-être que le calcul est réalisé inutilement pour une petite poignée de particules.
    Peux-être pourrais tu également envisager d'ajouter une fonction de type
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    <composant> getXxxByIndex(size_t index)
    et de créer une sorte de "table" (on pourrait même parler de vue) qui ressemblerait à quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    id   |idx Velocity | idx Position |
    ------------------------------
     XXX | Vx          | Px
     XYZ | Vy          | Pz
     YAB | Va          | Pb
    (en fait, un tableau de std::tuple<EntityId, size_t, size_t> ferait parfaitement l'affaire) pour, justement, arriver à relier les deux composants d'une même entité, et ne plus avoir à rechercher en permanence quel est le composant Velocity qui va bien avec le composant Position sur lequel tu travailles

    Bien sur, tu bouclerais alors sur ... le contneu (quel que soit l'ordre) de ce tableau de tuples
    Mais je souhaite mettre en place mon système d'event avant ça (ou signal / slot).
    Mon implémentation perso du système de signaux et de slot se trouve ==>ici<== et tu trouveras quelques exemples basiques d'utilisation ==>ICI<== (le reste, ces "poudre aux yeux" )
    Bref, ça me fait vraiment pas mal de choses à faire. Cependant, maintenant que je pense avoir saisi un peu comment concaténer tout ça, je veux vraiment m'assurer que, pour ce que j'ai déjà fait et/ou que je sais faire, mes choix sont bons (dans le sens convivialité / maintenabilité). Comme on en discute encore ici, je ne me suis pas encore complètement arrêté sur la manière dont je souhaite réaliser / intégrer mes systèmes. Selon moi, la priorité est là.
    En effet, un ECS sans systèmes, ca ne le fait pas vraiment

    Et il en va de même pour les composants. Finalement, je m’aperçoit qu'il est très bon, pour les performances, d'avoir des composants un peu plus imposants que juste "vitesse", "position", "orientation" etc... Je pense m'orienter vers des composants légèrement plus généraux. Par exemple, je pense que les cas où j'aurais besoin d'une vitesse SANS position (est-ce que ça a vraiment un sens ?) seront limités. Idem pour l'orientation.... Donc il est bon de me dire "ok, je vais regrouper ces trois composants en un seul". En agissant comme ça, je suis passé de 30 FPS à 77 FPS ; donc d'un truc pas top, à une application "acceptable". L'objectif est donc de limiter au stricte nécessaire les systèmes nécessitant plus d'un seul composant. En tout cas, au moins à ceux qui, réalisent des calculs déjà "lourds".
    Ah, de fait, si une vitesse sans position ne sert à rien, la vitesse sans la direction, ou la direction sans la vitesse, n'a aucun sens

    Et les deux ensembles (vitesse et direction) s'appellent "mouvement"

    Et donc, toute la difficulté est de créer des composants "suffisamment grands" que pour fournir toutes les informations que l'on a besoin, mais "aussi petits que possible" que pour pouvoir en mettre le plus possible dans une page de cache processeur

    Par contre, tu pourrais sans doute envisager de fournir le composant sous deux formes différentes (après avoir envisagé d'y accéder de deux manières différentes ) : sous la forme de "données brutes uniquement" (sans information de l'identifiant d'entité) et sous la forme de "données identifiées" (avec l'information de l'identifiant de l'entité à laquelle le composant est rattaché) pour que tu puisses envisager de récupérer l'identifiant de l'entité à laquelle il est rattaché ... ou non, selon le cas dans lequel tu te trouves
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  10. #30
    Membre actif Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Points : 219
    Points
    219
    Par défaut
    Bonjour à tous,

    Alors, pendant que je télécharge l'appli desktop de git, afin de pouvoir partager la dernière mouture de mon code, j'arrive enfin à afficher 1'500'000 particules pour 75 fps. C'est mieux que ce que j'avais avant ! Cependant, je dois maintenant voire ce que cela donne sur une application un peu plus "complète" que l'affichage de particules. Étant reparti sur un code tout neuf, je dois aussi ré-insérer la gestion des entités (pour le moment, ne traitant que des particules, les entités sont juste issues de la variable incrémentielle de la fonction creerParticules. Je vais voir comment le tout interagit avec ma dernière implémentation du système de gestion des entités (incluant le système de tag/signatures des composants/entités).

    Une fois ceci fait, je m’orienterais vers la gestion des éventements (avec une implémentation signal + slots ?). Ce n'est pas encore une priorité selon moi mais il faut que je rende ma gestion des composants thread-safe. Il me semble que ce dernier critère peut tout de même vite devenir important selon les systèmes mis en place.

    J'envoie le lien du git dès qu'il est prêt.

    EDIT : Ok, le git est normalement prêt : Mon Git

  11. #31
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Merci, je l'ai récupéré

    Je vais prendre quelques temps pour l'analyser avant de t'indiquer comment résoudre les problèmes de performances, mais, avant cela, j'aurais quelques remarque à faire au sujet de l'organisation même de ton code:

    1- Je n'ai absolument rien contre le fait que tu utilises code::blocks, d'autant plus que c'est un EDI portable (dont je dispose sur ma debian ). Et j'ai bien conscience que tu ne prévoyais sans doute pas de fournir ton code à la vue de tous

    Mais, voilà, le fait est que tu as accepté de mettre ton code sur github, et que n'importe qui peut donc décider de le récupérer. Ce qui serait sympa, c'est de laisser la personne qui récupère ton code choisir l'EDI qu'il veut utiliser.

    Et puis, à titre personnel, tu devrais envisager le fait que tes gouts en matière d'EDI pourront changer, car, il faut dire ce qui est : Code::Blocks n'est pas -- et de très loin -- l'EDI le plus intéressant à utiliser (et qui sait, tu pourrais même vouloir travailler "à l'ancienne", en ligne de commande avec un éditeur de fichiers plats )

    De plus, Code::Blocks est particulièrement dépendant du système sur lequel il est utilisé, or, tu n'as réellement la main mise que... sur ton propre système

    As tu déjà envisagé d'utiliser CMake (par exemple) pour configurer ton projet Cela te permettrait de résoudre bon nombre de problèmes de compatibilité entre EDI / système différents du tien

    2- Prend l'habitude de configurer ton EDI pour qu'il prenne en compte les différents sous dossiers qui contiennent des fichiers d'en-tête et qu'il les transmette au compilateur. Cela te simplifiera énormémen la vie par la suite (j'en parle un peu plus loin)

    3- On dirait que tu as semé tes fichiers au petit bonheur la chance : tu as des fichiers dans le dossier racine du projet, mais tu as également deux sous-dossier (ecs et tools) qui contiennent certains fichiers et tu as même un dossier (ecs) qui contient aussi trois sous dossier (components, entities et systems) qui contiennent également des fichiers.

    Or, quand on s'intéresse au fichiers que l'on trouve dans le dossier racine, on trouve Context.hpp et Events.hpp qui font tous les deux ... partie de ton ECS, même si c'est au "plus haut niveau" et... main.cpp qui est le seul fichier qui utilise ton ECS sans en faire intrinsèquement partie.

    Pire encore : les fichiers que l'on retrouve dans le sous dossier tools font eux aussi partie de ton ECS, même si ceux là sont sans doute utilisés ... au niveau le plus bas.

    De plus, tu t'es quelque peu trompé dans le nom de ce dossier, car, ce qu'il contient, ce ne sont pas des outils (car les outils, on peut les récupérer dans d'autres projets ), mais le cœur même de ton ECS; des fonctionnalités que tu vas sans doute réutiliser "jusqu'à plus soif" dans ton ECS.

    Je te conseillerais donc de renommer ce dossier tools en core, et de le faire passer dans ton dossier ecs. Cela te permettrait de placer les fichiers qui se trouvent dans le dossier ecs (Components.hpp, Entities.hpp, Signatures.hpp et Systems.hpp) dans le même sous dossier (car, l'un dans l'autre, tous ces fichiers sont clairement faits pour travailler "main dans la main" )

    Quant aux fichiers Context.hpp et Events.hpp, je les regrouperais donc dans un sous dossier de ecs que j'appellerais sans doute externals, étant donné qu'ils dépendent explicitement de bibliothèques externes.

    Et, pour faire bonne mesure, je placerais le fichier main.cpp dans un dossier séparé (appelons le apps pour applications) que je rajouterais dans le dossier racine. Dans l'idéal, je le placerais même dans un sous dossier de apps que je nommerais vorax, vu que c'est le titre que tu donnes à ta fenêtre
    Au final, j'organiserais sans doute le dossier racine sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    dossier_racine
    |- apps
    |    |- vorax
    |        |- main.cpp
    |- ecs
    |    |- core
    |    |    |- Components.hpp
    |    |    |- Entities.hpp
    |    |    |- ivector.hpp
    |    |    |- Signatures.hpp
    |    |    |- Systems.hpp
    |    |- externals
    |    |    |- Context.hpp
    |    |    |- Events.hpp
    |    |- vorax_business
    |            |- components
    |            |    |- All.hpp
    |            |    |- Physics.hpp
    |            |    |- Renderables.hpp
    |            |- entities
    |            |    |- Particles.hpp
    |            |- systems
    |            |- Physics.hpp
    |            |- Renderable.hpp
    Ce qui te permettra de rajouter les dossier ecs, external et vorax_business aux dossiers utilisés par le compilateur pour la recherche de fichier d'en-tête dans ton EDI (menu build option ->onglet "search directories" ->sous onglet "compiler" dans Code::blocks) , et de simplifier énormément l'inclusion de fichiers en la rendant plus indépendante de la position des différents fichiers d'en-tête.

    Avec une telle organisation, les inclusions de fichiers d'en-tête spécifiques de ton projet pourrait prendre une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    #include <Entities.hpp>
     
    #include <Components.hpp>
    #include <components/Physics.hpp>
    #include <components/Renderables.hpp>
    (exemple issu de Particles.hpp )

    Et surtout, quand tu en auras marre de faire joujou avec vorax, ou, plutôt, quand tu en auras tiré tous les renseignements que tu peux en espérer, tu pourras tout simplement "abandonner" le dossier vorax_business (sans forcément le virer : il contient sans doute des choses intéressantes ) et créer un nouveau projet, qui utilisera son propre sous dossier xxx_business, avec les sous dossiers secondaires components, entities et systems, on change pas une équipe qui gagne ) qui utilisera également le contenu de core et de externals.

    Contenu que ce nouveau projet t'incitera sans doute à étoffer un peu d'ailleurs

    Et c'est là qu'un outil comme CMake te viendra bien à point, car il te permettra de gérer les différents projets que tu pourrais envisage de lancer de manière sympa, par exemple, en te permettant de choisir quelle application doit être compilée (et, pourquo pas, décider de toutes les compiler )

    4- Fais très attention, lorsque tu implémentes une fonction dans un fichier d'en-tête, à bien la déclarer explicitement comme étant inline si elle ne l'est pas de manière implicite (en gros, si ce n'est pas la fonction membre d'une classe que tu implémente directement dans la définition de la classe).

    Car tu as de la chance de n'avoir qu'un seul fichier d'implémentation pour l'instant, autrement tu verrais péter l'édition de liens sous prétexte que l'éditeur de liens croise plusieurs symbole pour toutes les fonctions définies dans tes fichiers d'en-tête

    D'autant plus que, si on y regarde correctement, rien qu'au niveau de Context.hpp
    1. init() n'a aucune raison d'être inline, bien qu'elle ne fasse pas grand chose : ce n'est pas l'unique appel à cette fonction au tout début de l'exécution qui va faire quoi que ce soit
    2. pareil pour create()
    3. update() en fait déjà beaucoup trop (en appelant des fonctions opengl et sfml dont on ne sait rien) pour que l'on puisse croire que cela changera quoi que ce soit

    Il n'y a sans doute que resize qui pourrait effectivement présenter un quelconque intérêt à être inline, car, on peut clairement se poser la question de l'intérêt représentés par fonctions qui se contentent d'effectuer une seule et unique action sur une donnée à laquelle nous avons accès

    Et, de manière plus générale: on peut douter très fort de l'intérêt qu'il y a à rendre inline toutes les fonctions qui doivent boucler sur des collections, surtout si on part sur plus d'un million d'éléments par collection

    5- Je t'ai fait une fleur en dressant la liste des fichiers dans l'organisation du projet que j'utiliserais, en utilisant les noms de fichiers que tu as toi-même utilisés.

    Mais la règle qui consiste à décider une bonne fois pour toute une politique de nommage et de casse cohérente pour les éléments s'applique également aux noms de fichiers. Ce serait cool si tu pouvais adapter les noms de ivector.hpp et de random.hpp au reste du projet

    6- Je ne nie pas la qualité du fichier random.hpp (je n'y ai pas regardé en profondeur), mais, pourquoi aller chercher un fichier tout fait qui fournit bien plus que ce que tu as besoin lorsque tu pourrait utiliser les fonctionnalités fournies par la bibliothèque standard au travers du fichier <random>

    Après tout, ce n'est pas comme si tu faisais de l'aléatoire à grande échelle, vu que tu ne t'en sers que... pour définir la vélocité et la direction de tes particules, et que tu ne le fais qu'au moment du lancement de ton application

    Un code aussi simple que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    	void create(size_t const vNumber) {
    		std::random_device rd;
    		std::mt19937 gen(rd()); 
    		std::uniform_real_distribution<float> randomVelocity{.0f,60.f};
    		std::uniform_real_distribution<float> randomRz{.0f, 6.283f};
     
    		for(size_t i{0}; i!=vNumber; ++i) {
    			index_t e{entities::create()};
     
    			vec2 position{800.0, 450.0};
    			float velocity{randomVelocity(gen)};
    			float rz{random::get(randomRz};
                            /* ... la suite */
    ferait tout aussi bien l'affaire

    Note enfin que je n'ai pas encore commencé à regarder au niveau des performances, que ca, ca viendra plus tard
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  12. #32
    Membre actif Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Points : 219
    Points
    219
    Par défaut
    Bonsoir, et merci pour cette première analyse*!!

    Et puis, à titre personnel, tu devrais envisager le fait que tes gouts en matière d'EDI pourront changer, car, il faut dire ce qui est : Code::Blocks n'est pas -- et de très loin -- l'EDI le plus intéressant à utiliser (et qui sait, tu pourrais même vouloir travailler "à l'ancienne", en ligne de commande avec un éditeur de fichiers plats )
    Oui. Pendant «*longtemps*» je travaillais plutôt avec Vim + Makefiles*; mais maintenant que j’ai une belle machine de guerre sous windows, je me dis, pourquoi pas utiliser un IDE*? Certes, Code::Blocks n’est pas forcément de premier choix mais je dois avouer que j’ai un peu de mal à choisir parmi tous ceux qui existent. J’ai bien pensé à MSVC ou même éclipse, mais l’un est un produit MS (et j’ai peur qu’un code qui compile nickel sous MSVC ne compile pas nécessairement aussi bien sous GCC), et l’autre tourne sur JAVA et franchement…. Les logiciels comme ça qui ont des dépendances explicites pour fonctionner, et en particulier sous JAVA, ça ne m’attire pas vraiment… Alors oui, il en existe certainement pleins d’autres qui répondent à mes critères et qui sont super biens, mais je ne les connais pas encore
    Cmake… A tester. Je connais de nom mais c’est tout. Je vais regarder ce que ça donne.
    Passer une VM sur OpenBSD ou même une Distri sous débian est une bonne idée mais travailler sous VM…. J’aime pas trop non plus. Sujet que je peux/veux tester par contre c’est winBash (ou un truc du genre je retrouverais le nom). En gros c’est Bash (à voir si ça existe pour KSH) mais sur windows. Ce pourrait être une bonne option, et je compte tester ça à l’occasion.
    Enfin, virer mon windows (ou passer en dual boot) et switch sur un distri du genre Mint (que je trouve pas trop mal, j’ai ça sur mon portable). Mais j’ai pas mal d’applis windows que j’utilise et que je ne veux pas utiliser en VM / Wine. Pour le dual-boot ce qui m’embête un peu plus c’est la gestion des boot-loaders. Grub bazarde celui de windows, or, j’aimerais pouvoir coller un linux sur un HDD / SSD tier, et que le simple retrait de ce Disque-Dur soit entièrement transparent pour windows. C’est certainement faisable, mais je dois me renseigner sur ce sujet.
    Sinon j’aime beaucoup Notepad++ C’est pas vraiment un IDE mais je le trouve agréable d’utilisation (plus que Kate et Code::Blocks). Problème, il ne propose pas de compiler / linker. On en reviens donc à d’autres solutions proposées ci-dessus.

    Bref, après m’être bien écarté du sujet initial, j’ai simplement laissé les infos code::blocks dans le git car pas encore complètement configuré mon gitignore. De plus, je me suis dit que ça faisait un truc plug & play pour les quelques gens qui voudraient tester le projet. Pas besoin de reconfigurer tout un projet (enfin je crois)… Mais d’accord avec toi. 1) ça ne marche peut être même pas, 2) l’utilité est particulièrement restreinte 3) les vrais dev n’utilisent pas cet IDE → je vais virer tout ça.

    Maintenant, l’organisation de mon code…. Très vaste sujet. Pour être franc, j’ai cherché pendant un moment (peut être faudrait-il que je m’y remette?) des documents abordant cet aspect de manière assez claire et résultat*: rien.
    Je suis donc content de pouvoir enfin avoir un audit sur ce point. Ce que je cherchais à faire ici c’est justement de séparer la partie «*ECS*» de tout ce qui gravite autour. Les fichiers présents dans le document racine sont ceux qui ne sont là, normalement que pour des raisons de test. Ceux qui sont dans «*Tools*» sont ceux qui sont remplaçables. Par exemple, si un jour je développe un peu plus mon vecteur indexé, j’aurais juste à le remplacer tout en conservant le nom des principales fonctions et tout roule. Idem pour mon fichier ECS, ce dernier est donc «*Dépendant*» des fichiers qui gravitent autour, mais, autant que je sais le faire, indépendant de leurs implémentations. Mon dossier ECS est sensé marcher, que l’on l’utilise avec SFML ou tout autre librairie (à l’exception du fichier Systems.hpp qui est pour le moment quasiment vide et remplis un peu n’importe comment car je voulais faire des tests rapidement). Après, j’aborde ce sujet avec ces affirmations, peut être car je n’ai jamais vraiment réalisé de «*gros*» projets et que du coup, les fois où j’intègre le résultat d’un de mes codes comme outil d’un autre, j’adapte l’implémentation… Il me faut plus d’expérience dans le domaine pour me rendre compte que mon idée n’est pas viable.
    Cependant, comme je sais que j’ai une grande marge de progression à passer dans le cadre de l’organisation de mes fichiers (oui, sans grande surprise c’est autant le bordel dans mes fichiers persos aussi!), je vais passer du temps à analyser un peu la manière dont s’organisent les autres projets sur github. Tous ne sont sûrement pas des références en la matière mais mais je pense que pour une bonne partie, j’ai quand même pas mal à apprendre. Après, si tu as une méthode ou quelques références qui abordent ce sujet de manière efficace et intelligente j’achète de suite.
    D’ailleurs, l’organisation que tu propose est propre à ton expérience où c’est plutôt issu de bonnes pratiques que l’on retrouve régulièrement*?

    Là, tu me corrigeras si je me trompe, mais ça me semble incompatible avec mon «*cahier des charges*» Bien que les systèmes et composants soient nécessairement fortement liés avec le contexte (ici SFML), je veux pouvoir utiliser mon ECS dans d’autres applications qui n’utilisent pas forcément SFML… Et là, en écrivant cette phrase, je remarque qu’en fait cela n’a pas trop de sens. Le DOP est un paradigme. L’ECS n’est qu’une solution basée sur ce paradigme. Si je réalise un autre programme très différent par la suite, alors c’est la méthodo et les outils qui importent et non l’implémentation… Repartant de ce principe, ton organisation est alors bien plus appropriée (qui en aurait douté*!?)…


    Ce qui te permettra de rajouter les dossier ecs, external et vorax_business aux dossiers utilisés par le compilateur pour la recherche de fichier d'en-tête dans ton EDI (menu build option ->onglet "search directories" ->sous onglet "compiler" dans Code::blocks) , et de simplifier énormément l'inclusion de fichiers en la rendant plus indépendante de la position des différents fichiers d'en-tête.
    Là par contre, je suis un peu plus frileux. Autant pour des versions finales d’outils terminés et / ou tiers, ça tombe sous le sens. Autant pour pour les fichiers spécifiques à un projet j’aime moins. Et c’est d’ailleurs très certainement lié à l’IDE par contre. La manipulation proposée est propre à l’IDE et non au projet. La configuration reste sur les projets suivants, pouvant apporter conflits etc.
    Puis si je créé un fichier vector dans la racine (ce qui est idiot, mais plausible si l’on suit le raisonnement de mon organisation actuelle) quel fichier sera inclus*? Celui de la STL ou le mien*?
    Mais mon «*souci*» principal étant plus lié avec le sujet de la configuration de l’IDE, ce ne sera plus un problème si je passe sur au autre IDE ou sous make.

    Je ne nie pas la qualité du fichier random.hpp (je n'y ai pas regardé en profondeur), mais, pourquoi aller chercher un fichier tout fait qui fournit bien plus que ce que tu as besoin lorsque tu pourrait utiliser les fonctionnalités fournies par la bibliothèque standard au travers du fichier <random>
    Là, tu m’e’n donne la raison juste en dessous*:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    std::random_device rd;
    std::mt19937 gen(rd()); 
    std::uniform_real_distribution<float> randomVelocity{.0f,60.f};
    std::uniform_real_distribution<float> randomRz{.0f, 6.283f};
    On a ça vs random::get() ...

    Et puisque j'en suis à me chercher des excuses, ça sûrement un sens, mais quelle idée d’appeler sa classe mt19937 qui plus est dans la stl ? Moi, ça me surprends beaucoup. La STL bouffe tous les noms de classes que l’on pourrait trouver intéressante à utiliser pour NOS propres classes, et pour un truc aussi « pointu » mt19937… Je ne comprends pas. Mais bon, c’est pas très important. Franchement, j’ai utilisé ce fichier simplement par procrastination. Pas envie d’apprendre à utiliser le mt19937, fichier simple a utiliser, en static… Bref. Voilà. Mais finalement on en reviens aux sujets d’organisation, ça fait clairement un fichier que je vais avoir envie de remplacer d’ici quelques temps sans avoir à remodifier tout le code qui l’utilise. La syntaxe random::get() me sied tout à fait !

    En tout cas, merci beaucoup pour ce retour. Comme pour ma manière de coder, la simple « organisation » de mon code nécessite elle aussi une belle montée en puissance. J’en ai conscience, aussi, je suis content que quelqu’un me fasse la remarque. Ça me pousse à devoir réfléchir à ce sujet que j’ai mis de côté trop souvent par manque de sources / conseils, alors que, selon moi, cela fait parti des sujets les plus importants en programmation.

    Merci beaucoup.

  13. #33
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par BioKore Voir le message
    Oui. Pendant «*longtemps*» je travaillais plutôt avec Vim + Makefiles*; mais maintenant que j’ai une belle machine de guerre sous windows, je me dis, pourquoi pas utiliser un IDE*? Certes, Code::Blocks n’est pas forcément de premier choix mais je dois avouer que j’ai un peu de mal à choisir parmi tous ceux qui existent. J’ai bien pensé à MSVC ou même éclipse, mais l’un est un produit MS (et j’ai peur qu’un code qui compile nickel sous MSVC ne compile pas nécessairement aussi bien sous GCC),
    Jusqu'à il y a quelques années, ta crainte aurait été parfaitement justifiée, car, jusqu'à la version 2015 de Visual Studio, les fonctions default et delete n'était par exemple pas supportées

    Mais, depuis, les choses ont bien évolué, et les versions suivantes ont enfin fini par supporter ad minima correctement la dernière norme sortie (C++17 pour Vs 2017 et ultérieur au moment d'écrire ces lignes)

    Ceci dit, cela reste une excellente habitude que de compiler son projet avec plusieurs compilateur pour voir les éventuelles différences entre eux
    Citation Envoyé par BioKore Voir le message
    et l’autre tourne sur JAVA et franchement…. Les logiciels comme ça qui ont des dépendances explicites pour fonctionner, et en particulier sous JAVA, ça ne m’attire pas vraiment… Alors oui, il en existe certainement pleins d’autres qui répondent à mes critères et qui sont super biens, mais je ne les connais pas encore
    Non, ca, je te comprends

    Mais il y a d'autres EDI disponibles, comme QtCreator ou CLion, par exemple.

    Bon, tu me dira que le premier vient avec tout un framework (Qt) que tu ne veux pas forcément utiliser, mais il est parfaitement possible d'avoir des projets qui n'utilisent pas Qt avec lui

    De plus, il est maintenant parfaitement capable de travailler avec les CMakeFiles.txt que l'on utilise pour configurer son projet avec CMake (tout comme CLion il me semble, ainsi que visual studio, d'ailleurs)
    Cmake… A tester. Je connais de nom mais c’est tout. Je vais regarder ce que ça donne.
    Disons que c'est très proche des autotools (autoconf, automake, autoheader et libtool) sous linux de par son objectif, mais sous une forme qui est à la fois
    • plus simple (le langage de script est à mon sens plus simple que le M4 utilisé par autotools)
    • plus automatique dans la gestion (et surtout le lancement) du projet: tu écris ton fichier CMakeLists.txt et "roulez jeunesse"
    • portable : on peut l'utiliser sous windows, linux et Mac sans aucun problème (l'utilisation des autotools sous windows est parfois problématique )

    Passer une VM sur OpenBSD ou même une Distri sous débian est une bonne idée mais travailler sous VM…. J’aime pas trop non plus.
    Et pourtant, dieu sait que cela présente quelques avantages

    Enfin, virer mon windows (ou passer en dual boot) et switch sur un distri du genre Mint (que je trouve pas trop mal, j’ai ça sur mon portable). Mais j’ai pas mal d’applis windows que j’utilise et que je ne veux pas utiliser en VM / Wine. Pour le dual-boot ce qui m’embête un peu plus c’est la gestion des boot-loaders. Grub bazarde celui de windows, or, j’aimerais pouvoir coller un linux sur un HDD / SSD tier, et que le simple retrait de ce Disque-Dur soit entièrement transparent pour windows. C’est certainement faisable, mais je dois me renseigner sur ce sujet.
    En fait, la plupart des bios te permettent de sélectionner le disque dur sur lequel il doit démarrer. Du coup, si tu as deux DD (ou plus), tu peux placer le boot loader de windows sur l'un et grub sur l'autre, et ca fonctionne pas mal

    Il "n'y a qu'à" indiquer le disque dur à utiliser, et on peut même le faire sans vraiment passer par le bios (en appuyant sur <F12> pendant le check mémoire)

    Sinon j’aime beaucoup Notepad++ C’est pas vraiment un IDE mais je le trouve agréable d’utilisation (plus que Kate et Code::Blocks). Problème, il ne propose pas de compiler / linker. On en reviens donc à d’autres solutions proposées ci-dessus.
    Bref, après m’être bien écarté du sujet initial, j’ai simplement laissé les infos code::blocks dans le git car pas encore complètement configuré mon gitignore. De plus, je me suis dit que ça faisait un truc plug & play pour les quelques gens qui voudraient tester le projet. Pas besoin de reconfigurer tout un projet (enfin je crois)… Mais d’accord avec toi. 1) ça ne marche peut être même pas, 2) l’utilité est particulièrement restreinte 3) les vrais dev n’utilisent pas cet IDE → je vais virer tout ça.
    Malheureusement, tu t'es gouré sur ce coup là...

    J'ai personnellement du refaire la configuration d'édition de liens pour pouvoir compiler ton projet
    Maintenant, l’organisation de mon code…. Très vaste sujet. Pour être franc, j’ai cherché pendant un moment (peut être faudrait-il que je m’y remette?) des documents abordant cet aspect de manière assez claire et résultat*: rien.
    Pour être franc, je ne suis pas sur du tout qu'il existe la moindre source sur le sujet, que ce soit en francais ou en anglais
    Je suis donc content de pouvoir enfin avoir un audit sur ce point. Ce que je cherchais à faire ici c’est justement de séparer la partie «*ECS*» de tout ce qui gravite autour. Les fichiers présents dans le document racine sont ceux qui ne sont là, normalement que pour des raisons de test.
    Oui, mais, si tu les laisses dans le dossier racine, cela donne au contraire l'impression que ce sont des fichiers essentiels au projet (plus on s'enfonce dans l'arborescence, plus on se dirige vers des fonctionnalités spécifiques )

    Ceux qui sont dans «*Tools*» sont ceux qui sont remplaçables. Par exemple, si un jour je développe un peu plus mon vecteur indexé, j’aurais juste à le remplacer tout en conservant le nom des principales fonctions et tout roule. Idem pour mon fichier ECS, ce dernier est donc «*Dépendant*» des fichiers qui gravitent autour, mais, autant que je sais le faire, indépendant de leurs implémentations.
    Tout à fait, mais il faut voir où se situe la dépendance.

    Car, d'un coté, tu as la partie "bibliothèques de fonctionnalités", qui représente -- faisons simple -- l'ensemble des fonctionnalités que l'on peut utiliser (ou non) dans plusieurs applications, et la partie purement applicative qui fourni -- faisons simple -- la fonction main() pour générer l'exécutable.

    Le fait est que, dans la partie "bibliothèques de fonctionnalité", on trouve différents "niveaux", à savoir:
    • la (les) vue(s), qui est spécifique à la bibliothèque externe utilisée pour l'affichage
    • la partie "métier", spécifique à une (ou plusieurs) application(s) particulière(s)
    • le "coeur" commun à n'importe quel ECS, quel que soit l'application qu'il permet de mettre en oeuvre
    • les "outils" qui peuvent être utilisés par d'autres projets que l'ECS

    Et, surtout, que ces niveaux dépendent les uns des autres dans un certain ordre:
    • L'application dépend de la vue
    • la vue dépend de la partie métier (car il faut bien qu'elle sache ce qu'elle va montrer )
    • la partie métier dépend du coeur commun à n'importe quel ECS
    • le coeur commun dépend des outils
    • les outils ne dépendent a priori de rien (sauf, éventuellement, d'autres outils )

    Et ce, même s'il se peut que la vue ou la partie métier fasse appel à quelques fonctionnalités spécifiquement fournies par différents outils (mais le coeur ou la partie métier aurait du arriver nous cacher ce fait )

    Et, au delà, tu as une série d'éléments que tu voudras sans doute rajouter à ton projet, tels que
    • des tests unitaires (pour les outils, pour le coeur, pour la partie métier)
    • de la documentation (pour l'ensemble)
    • des exemples d'utilisation (des outils, du coeur, de la partie métier et peut être même pour certaines vues)
    • des applications spécifiques (ce n'est pas le cas ici, mais on pourrait envisager d'avoir une application client, une application serveur et, pourquoi pas, un client en version "smartphone" )

    A partir de là, on se rend compte qu'il est important de placer chaque partie dans un dossier spécifique, ce qui nous emmène sans doute (quand toutes les parties sont présentes) à une arborescence proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    dossier_racine
    |- apps
    |    |-MyApp1
    |    |     |- client
    |    |     |- server
    |    |- OtherApplication
    |- documents (/ doxygen)
    |- examples
    |    |-business
    |    |-core
    |    |-tools
    |    |    |- tool1
    |    |    |- tool2
    |    |    |- ...
    |    |- ... ???
    |- lib
    |    |-business
    |    |-core
    |    |-tools
    |    |- ... ???
    |- tests
    |    |-business
    |    |-core
    |    |-tools
    |    |    |- tool1
    |    |    |- tool2
    |    |    |- ...
    |    |- ... ???
    |- AUTHORS
    |- ChangLog
    |- LICENSE
    |- README
    |- ... ???
    (et, pour être tout à fait franc, je pousse le vice jusqu'à séparer les fichiers d'en-tête -- que je place dans un dossier include -- et les fichiers d'implémentation, que je place dans un fichier src)
    Mon dossier ECS est sensé marcher, que l’on l’utilise avec SFML ou tout autre librairie (à l’exception du fichier Systems.hpp qui est pour le moment quasiment vide et remplis un peu n’importe comment car je voulais faire des tests rapidement). Après, j’aborde ce sujet avec ces affirmations, peut être car je n’ai jamais vraiment réalisé de «*gros*» projets et que du coup, les fois où j’intègre le résultat d’un de mes codes comme outil d’un autre, j’adapte l’implémentation… Il me faut plus d’expérience dans le domaine pour me rendre compte que mon idée n’est pas viable.
    D'après mon expérience, c'est -- à peu de chose près -- l'arborescence qui ressort de la plupart des gros projets que j'ai pu croiser; même si on parle alors plutôt de "solutions" (d'ensemble de projets pouvant évoluer de manière indépendante)

    Une chose est sure: à par -- éventuellement -- un squelette de fichier (ex : config.h.in) qui servira, lors de la configuration du projet à générer un fichier d'en-tête utilisé pour la configuration globale, il n'y a absolument aucun fichier contenant du code dans le dossier racine

    Maintenant, tous les dossiers ne sont pas forcément présents; et d'autres peuvent apparaître.

    Par exemple, on pourrait envisager de rajouter un dossier 3rdParty dans lequel on pourrait rajouter les dépendances externes qui pourraient être absente du système sur lequel la solution sera compilée.

    Cependant, comme je sais que j’ai une grande marge de progression à passer dans le cadre de l’organisation de mes fichiers (oui, sans grande surprise c’est autant le bordel dans mes fichiers persos aussi!), je vais passer du temps à analyser un peu la manière dont s’organisent les autres projets sur github. Tous ne sont sûrement pas des références en la matière mais mais je pense que pour une bonne partie, j’ai quand même pas mal à apprendre. Après, si tu as une méthode ou quelques références qui abordent ce sujet de manière efficace et intelligente j’achète de suite.
    La seule méthode que je connaisse c'est de regrouper les éléments en fonction de leur but ou de leur utilisation, en respectant au mieux la phrase mythique
    Ce qui ce conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément
    D’ailleurs, l’organisation que tu propose est propre à ton expérience où c’est plutôt issu de bonnes pratiques que l’on retrouve régulièrement*?
    Ce n'est sûrement pas issu d'un "guide de bonnes pratiques" (ou alors, je ne l'ai pas encore lu )

    Mais c'est très clairement le genre de structure que l'on retrouve régulièrement
    Là, tu me corrigeras si je me trompe, mais ça me semble incompatible avec mon «*cahier des charges*» Bien que les systèmes et composants soient nécessairement fortement liés avec le contexte (ici SFML), je veux pouvoir utiliser mon ECS dans d’autres applications qui n’utilisent pas forcément SFML… Et là, en écrivant cette phrase, je remarque qu’en fait cela n’a pas trop de sens. Le DOP est un paradigme. L’ECS n’est qu’une solution basée sur ce paradigme. Si je réalise un autre programme très différent par la suite, alors c’est la méthodo et les outils qui importent et non l’implémentation… Repartant de ce principe, ton organisation est alors bien plus appropriée (qui en aurait douté*!?)…
    En fait, l'idée est vraiment de créer un schéma de dépendances à sens unique dans lequel les fonctionnalités de "plus haut niveau" dépendent des fonctionnalités de "plus bas niveau" et dans lequel tu peux décider de retrancher tout ce qui est "de plus haut niveau" pour ne garder "que la base".
    • Tu as créé un système de particules qui utilise SFML pour l'affichage. Mais tu pourrais décider d'utiliser Qt, GLFW ou Glut pour afficher ton système de particules
    • Tu as utilisé des fonctionnalités de base pour créer ton système de particules. Mais avec ces fonctionnalités de base, tu pourrais créer plein d'autres choses, dont l'affichage se ferait de manière totalement différente
    • Tu as utilisé certains outils pour créer tes fonctionnalités de base. Mais ces outils pourraient être utilisé dans un contexte tout à fait différent de celui d'un ECS

    La bonne question à se poser c'est:
    Comment pourrais-je organiser mes fichiers pour que je puisse décider de ne prendre qu'un (ou plusieurs) dossier en étant sûr d'avoir "tout ce qu'il faut" pour pouvoir lancer "un nouveau projet"
    Hé bien la meilleure réponse à donner est sans doute encore de mettre tout ce qui est "forcément" destiné à fonctionner ensemble dans un dossier spécifique
    Là par contre, je suis un peu plus frileux. Autant pour des versions finales d’outils terminés et / ou tiers, ça tombe sous le sens. Autant pour pour les fichiers spécifiques à un projet j’aime moins. Et c’est d’ailleurs très certainement lié à l’IDE par contre. La manipulation proposée est propre à l’IDE et non au projet. La configuration reste sur les projets suivants, pouvant apporter conflits etc.
    Non, il n'y a absolument aucun risque à ce sujet:

    Quel que soit l'IDE que tu utilises, chaque projet va être décrit par un (ou plusieurs) fichier(s) qui sont propre au projet en question, et qui contient l'ensemble des informations qui permettront à l'IDE de faire son travail (qui, rappelons le, consiste essentiellement à lancer les différents outils dans le bon ordre, en leur transmettant les bons paramètres), telles que:
    • le dossier dans lequel placer les fichiers objets générés (pour les différentes cibles : debug / release / autres)
    • le dossier dans lequel placer les exécutables générés (pour les différentes cibles : debug / release / autres), s'il y en a
    • les options de compilation à transmettre au compilateur (pour les différentes cibles : "globale" / rellease /autres)
    • les options à transmettre à l'éditeur de liens (pour les différentes cibles)
    • la liste des fichiers à prendre en compte,
    • j'en passe et de meilleurs


    A titre d'exemple, Code::Blocks va générer un fichier dont l'extension est en *.cbp (Code Blocks Project ) qui est un simple fichier xml dont voici le contenu tel qu'il se présente sur git:
    Code XML : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
    <CodeBlocks_project_file>
    	<FileVersion major="1" minor="6" />
    	<Project>
    		<Option title="ecs" />
    		<Option pch_mode="2" />
    		<Option compiler="gcc" />
    		<Build>
    			<Target title="Debug">
    				<Option output="bin/Debug/ecs" prefix_auto="1" extension_auto="1" />
    				<Option object_output="obj/Debug/" />
    				<Option type="1" />
    				<Option compiler="gcc" />
    				<Compiler>
    					<Add option="-g" />
    				</Compiler>
    			</Target>
    			<Target title="Release">
    				<Option output="bin/Release/ecs" prefix_auto="1" extension_auto="1" />
    				<Option object_output="obj/Release/" />
    				<Option type="1" />
    				<Option compiler="gcc" />
    				<Compiler>
    					<Add option="-O2" />
    				</Compiler>
    				<Linker>
    					<Add option="-s" />
    				</Linker>
    			</Target>
    		</Build>
    		<Compiler>
    			<Add option="-Wall" />
    		</Compiler>
    		<Unit filename="Context.hpp" />
    		<Unit filename="Events.hpp" />
    		<Unit filename="ecs/Components.hpp" />
    		<Unit filename="ecs/Entities.hpp" />
    		<Unit filename="ecs/Signatures.hpp" />
    		<Unit filename="ecs/Systems.hpp" />
    		<Unit filename="ecs/components/All.hpp" />
    		<Unit filename="ecs/components/Physics.hpp" />
    		<Unit filename="ecs/components/Renderables.hpp" />
    		<Unit filename="ecs/entities/Particles.hpp" />
    		<Unit filename="ecs/systems/Physics.hpp" />
    		<Unit filename="ecs/systems/Renderable.hpp" />
    		<Unit filename="main.cpp" />
    		<Unit filename="tools/ivector.hpp" />
    		<Unit filename="tools/random.hpp" />
    		<Extensions>
    			<code_completion />
    			<envvars />
    			<debugger />
    		</Extensions>
    	</Project>
    </CodeBlocks_project_file>
    Quand tu crées un autre projet, même si le fichier *.cbp correspondant à ce projet fini par se trouver dans le même dossier qu'un autre, chaque projet reste très clairement distinct

    Par contre, tu peux envisager de créer ce qu'ils appelle un "espace de travail" (workspace) et que Visual Studio appelle "solution" pour regrouper les deux projets et, pourquoi pas, indiquer qu'un des projets dépend de l'autre (et tu n'est bien sur pas limité à deux projets )

    Puis si je créé un fichier vector dans la racine (ce qui est idiot, mais plausible si l’on suit le raisonnement de mon organisation actuelle) quel fichier sera inclus*? Celui de la STL ou le mien*?
    Appelle le Vector (avec un V majuscule) Cela résoudra le problème.
    Ou bien, place le spécifiquement dans un dossier "tools", en utilisant le dossier parent comme dossier de recherche, et inclus le sous la forme de #include <tools/vector>. Cela évitera tout risque de confusion

    Mais mon «*souci*» principal étant plus lié avec le sujet de la configuration de l’IDE, ce ne sera plus un problème si je passe sur au autre IDE ou sous make.
    Humm... à voir...

    Quel que soit l'IDE utilisé, ou même si tu utilises CMake pour la configuration de ton projet, la configuration générale restera sensiblement pareille et risque donc de poser les même problèmes (s'il y en a)

    Mais, a priori, il y a toujours moyen de s'en sortir, même avec une configuration "fine tuned"

    Là, tu m’e’n donne la raison juste en dessous*:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    std::random_device rd;
    std::mt19937 gen(rd()); 
    std::uniform_real_distribution<float> randomVelocity{.0f,60.f};
    std::uniform_real_distribution<float> randomRz{.0f, 6.283f};
    On a ça vs random::get() ...
    Ca m'a pris 15 secondes et demie d'écrire ce code. Combien de temps cela t'a-t-il pris pour trouver le fichier d'en-tête, le télécharger, trouver où le placer dans ton projet et comprendre comment l'utiliser

    Et puisque j'en suis à me chercher des excuses, ça sûrement un sens, mais quelle idée d’appeler sa classe mt19937 qui plus est dans la stl ?[/QUOTE]
    Bien sur que cela a du sens: cela fait référence à la méthode de génération de valeurs aléatoires dite Mersenne Twister, dans la version utilisant le nombre premier obtenu par la valeur 219937-1
    Moi, ça me surprends beaucoup. La STL bouffe tous les noms de classes que l’on pourrait trouver intéressante à utiliser pour NOS propres classes, et pour un truc aussi « pointu » mt19937… Je ne comprends pas.
    Tu t'imagines, devoir écrire std::mersenne_twister_19937 chaque fois que tu as besoin d'un générateur aléatoire

    Mais bon, c’est pas très important. Franchement, j’ai utilisé ce fichier simplement par procrastination. Pas envie d’apprendre à utiliser le mt19937,
    Au moins, tu le reconnais : c'est de la procrastination

    Mais c'est un tord, car l'utilisation de la STL mérite vraiment que l'on se penche ardemment sur le sujet
    fichier simple a utiliser, en static… Bref. Voilà. Mais finalement on en reviens aux sujets d’organisation, ça fait clairement un fichier que je vais avoir envie de remplacer d’ici quelques temps sans avoir à remodifier tout le code qui l’utilise. La syntaxe random::get() me sied tout à fait !
    Et, si tu ne l'utilisais pas, tu n'aurais pas plus difficile à travailler

    Au pire, tu pourrais envisager (et tu pourrais le faire ici aussi, d'ailleurs) de définir des constantes pour les valeur minimale et maximales des distributions uniformes, histoire d'éviter les valeurs magiques, mais ca, c'est un autre débat
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  14. #34
    Membre actif Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Points : 219
    Points
    219
    Par défaut
    Salut, merci pour le retour.

    Je vais me pencher sur ta manière de voir l'organisation des projets, qui est bien plus réfléchie et aboutie que ce que j'ai en place. Même si je ne suis pas certain encore de "ressentir" tous les avantages que cela apporte (sur la forme proposée, pas dans le fond), je vais essayer de mettre en place quelque chose de similaire. J'ai jeté un œil à CMake ; ça a l'air très pratique effectivement quoi que peut être un peu compliqué à suivre quand les projets s'entassent en nombre mais ça je m'y ferais une idée en l'utilisant.

    Mais il y a d'autres EDI disponibles, comme QtCreator ou CLion, par exemple.
    Ah, je vais regarder ce qu'est CLion. C'est la première fois que j'en entends parler. Par contre pour QTCréator, là c'est un peu le même combat. Ça vient avec une panoplie de trucs inutiles quand on veux juste faire du C++ pur. Si je télécharge un IDE pour faire du C++ c'est pour faire du C++ (voire du C et de l'ASM). Si c'est pour que le bouzin nous ramène toutes ses librairies qui vont avec et que l'on ne compte en aucun cas utiliser, ça ne m'attire pas trop.





    Car, d'un coté, tu as la partie "bibliothèques de fonctionnalités", qui représente -- faisons simple -- l'ensemble des fonctionnalités que l'on peut utiliser (ou non) dans plusieurs applications, et la partie purement applicative qui fourni -- faisons simple -- la fonction main() pour générer l'exécutable.

    Le fait est que, dans la partie "bibliothèques de fonctionnalité", on trouve différents "niveaux", à savoir:

    la (les) vue(s), qui est spécifique à la bibliothèque externe utilisée pour l'affichage
    la partie "métier", spécifique à une (ou plusieurs) application(s) particulière(s)
    le "cœur" commun à n'importe quel ECS, quel que soit l'application qu'il permet de mettre en œuvre
    les "outils" qui peuvent être utilisés par d'autres projets que l'ECS


    Et, surtout, que ces niveaux dépendent les uns des autres dans un certain ordre:

    L'application dépend de la vue
    la vue dépend de la partie métier (car il faut bien qu'elle sache ce qu'elle va montrer )
    la partie métier dépend du cœur commun à n'importe quel ECS
    le cœur commun dépend des outils
    les outils ne dépendent a priori de rien (sauf, éventuellement, d'autres outils )
    Oui. Je crois bien avoir compris ceci ; En fait il faut que je vois un niveau plus haut qu'actuellement.
    Mais si on a plusieurs "libs" qui doivent partager les mêmes outils ? on est finalement obligés de créer des dépendances entre les bibliothèques non ? Exemple, si je me fait un gestionnaire de mémoire (ou un "pool") ; je vais en avoir besoin pour mon ECS, mais je vais en avoir besoin aussi pour d'autres éléments (comme l'IA juste par exemple ou d'autres choses....). Ce gestionnaire n'a donc pas sa place dans le dossier "lib/tools"...

    Je serais donc tenté de faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    dossier_racine
    |- apps
    |    |-MyApp1
    |    |     |- client
    |    |     |- server
    |    |- OtherApplication
    |- ecs
    |    |-business
    |    |-core
    |    |-tools
    |    |- ... ???
    |- mem
    |    |-business
    |    |-core
    |    |-tools
    |    |- ... ???
    Et du coup, tools ne reprendrait alors les outils utiles UNIQUEMENT pour la bibliothèque en question... Me trompe-je ?

    Par exemple, on pourrait envisager de rajouter un dossier 3rdParty dans lequel on pourrait rajouter les dépendances externes qui pourraient être absente du système sur lequel la solution sera compilée.
    Comme SFML, OpenGL ou encore GLM par exemple ? Ou plutôt, comme je l'utilise aujourd'hui, le "random" ?


    Tu t'imagines, devoir écrire std::mersenne_twister_19937 chaque fois que tu as besoin d'un générateur aléatoire
    En fait oui. Car même si cela nous oblige un travail supplémentaire (using std::mersenne_twister_19937 = std::mt19937;) cela nous responsabilise dans la gestion des noms des fonctions que l'on apporte et/ou nous incite à créer des wrappers pour ces classes. Mais ces remarques ne sont qu'un clin d’œil de mon point de vue sur la chose. Globalement, les noms ne me dérangent pas non plus outre mesure et malgré tout le mal que je puisse dire des noms de la STL, même avec une organisation foireuse, je m'y retrouve donc bon...


    Par contre, pour une organisation comme ça, si je sépare mes déclarations de mes définitions, je vais absolument devoir me mettre à CMake ou le faire à coups de makefiles... Je me lance donc de suite sur CMake ! Je mettrais à jour mon GIT avec ça si les résultats me semblent concluants.

    Merci !

  15. #35
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par BioKore Voir le message
    ça a l'air très pratique effectivement quoi que peut être un peu compliqué à suivre quand les projets s'entassent en nombre mais ça je m'y ferais une idée en l'utilisant.
    Non, justement, c'est là l'avantage du système:
    Mettons que tu aies trois projets différents "d'utilitaires" qui ont été développés "chacun de son coté", et dont le dossier racine serait respectivement dossier1, dossier2 et dossier3. Ils ont tous les trois au minimum un CMakeLists.txt au niveau de leur dossier racine respectif (*).

    Un jour, tu décide de lancer un quatrième projet qui tirerait justement parti de l'utilisation de ces trois premier projets. Hé bien, tu crées -- forcément -- un nouveau dossier pour ce nouveau projet, et tu y copie dossier1, dossier2 et dossier3 dans leur intégralité.

    Au niveau du CMaLists.txt de ton nouveau projet, il suffira d'ajouter les trois dossiers que tu viens de copier sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    add_subdirectory(dossier1)
    add_subdirectory(dossier2)
    add_subdirectory(dossier3)
    pour que tes trois projets "d'utilitaires" soient automatiquement ajoutés à ton nouveau projet. N'est-ce pas magique

    (*) En réalité, il y a de fortes chances pour que chaque dossier de tes trois projets dispose de son propre CMakeLists.txt, qui sera pris en compte par celui qui se trouve dans le dossier parent grâce à cette fameuse instruction add_subdiectory(nom_du_dossier).
    Ah, je vais regarder ce qu'est CLion. C'est la première fois que j'en entends parler. Par contre pour QTCréator, là c'est un peu le même combat. Ça vient avec une panoplie de trucs inutiles quand on veux juste faire du C++ pur. Si je télécharge un IDE pour faire du C++ c'est pour faire du C++ (voire du C et de l'ASM). Si c'est pour que le bouzin nous ramène toutes ses librairies qui vont avec et que l'on ne compte en aucun cas utiliser, ça ne m'attire pas trop.
    Autant je suis tout à fait d'accord avec toi quand tu dis refuser de dépendre de java pour faire du C++ en utilisant éclipse, autant les choses sont beaucoup moins claires lorsqu'il s'agit de QtCreator qui vient avec Qt

    Car Qt est un framework C++. Un framework qui souffre du poids de son histoire, certes, mais un framework qui a été développé dés l'origine en C++ quand même.

    En plus, c'est un framework qui vient avec tant de possibilités que tu trouveras très facilement une raison d'être content d'en disposer. Sans vouloir être exhaustif, nous pouvons citer la possibilité:
    • de faire de l'IHM (des interfaces graphiques)
    • d'attaquer les différentes bases de données qui existent
    • de mettre en place une architecture serveur + client
    • de faire du traitement d'images
    • j'en passe, et sans doute de meilleures (il y a plus de 40 modules différents et indépendants, si mes souvenirs sont bons )

    L'un dans l'autre, Qt est à un niveau un peu plus élevé ce que boost est au bas niveau du C++ (savais tu que bon nombre des fonctionnalités ajoutées à la bibliothèque standard en C++11 et ultérieures ont été purement et simplement copiées de boost )
    Oui. Je crois bien avoir compris ceci ; En fait il faut que je vois un niveau plus haut qu'actuellement.
    Oui, c'est bien là l'idée
    Mais si on a plusieurs "libs" qui doivent partager les mêmes outils ? on est finalement obligés de créer des dépendances entre les bibliothèques non ?
    Non, pas du tout...

    La relation de dépendance est une relation N .. N : une bibliothèque peut avoir plusieurs (un nombre inconnu) de dépendances -- même si, en pratique, nous essayerons malgré tout de le limiter au maximum -- et une bibliothèque peut servir de dépendance à un nombre inconnu d'autres bibliothèques. Et dans ce sens là, le développeur de la bibliothèque qui sert de dépendance n'a de toutes manières pas à s'en inquiéter

    Mieux encore, la relation de dépendances reste malgré tout "récursive": si A dépend de B, que B dépend de C et que C dépend de D, alors, A dépend également (mais de manière indirecte) de C et de D; même si, dans l'idéal, le développeur de C aura fait tout son possible pour éviter de faire "transparaitre" l'utilisation de D auprès de l'utilisateur, que le développeur de B aura, dans l'idéal, fait tout son possible pour éviter de laisser transparaitre l'utilisation de C auprès de l'utilisateur de B et que le développeur de A aura fait tout son possible pour éviter de laisser transparaitre l'utilisation de B auprès de l'utilisateur de A (le principe de la "boite noire", en quelque sorte )

    La seule chose qu'il faille éviter, ce sont les dépendances circulaires : que A dépende de B et que B dépende de A.

    Mais, pour le reste : depuis que tu t'es mis au développement, tu as surement déjà développé trois projets ou plus, même si certains d'entre eux étaient des "projets jetables". Et ces trois projets, qui étaient tout à fait indépendants les uns des autres utilisaient surement la bibliothèque standard, non

    Mais si ces trois projets (pas tout à fait jetables, du coup ) fournissaient des fonctionnalités (différentes, cela va de soi) que tu juge "bien développées" et qui pourraient être utiles au développement d'un quatrième projet, qu'est ce qui t'empêcherait de les utiliser tous les trois, en faisant, du coup, dépendre ton quatrième projet des trois premiers

    nous sommes loin de la relation d'héritage pour laquelle un héritage "en diamant" (ou en "losange") pourrait poser problème
    Exemple, si je me fait un gestionnaire de mémoire (ou un "pool") ; je vais en avoir besoin pour mon ECS, mais je vais en avoir besoin aussi pour d'autres éléments (comme l'IA juste par exemple ou d'autres choses....). Ce gestionnaire n'a donc pas sa place dans le dossier "lib/tools"...
    Hé bien, ton pool memoire sera dans libs/tools, ton ECS sera dan libs/ecs, avec dépendance vers libs/tools et on troisième projet dans libs/machin, avec dépendance vers libs/tools... Où serait le problème

    Et si tu as une quatrième partie, qui se trouverait dans libs/bidule, elle pourrait très bien avoir une dépendance avec libs/ecs et avec libs/machin
    Je serais donc tenté de faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    dossier_racine
    |- apps
    |    |-MyApp1
    |    |     |- client
    |    |     |- server
    |    |- OtherApplication
    |- ecs
    |    |-business
    |    |-core
    |    |-tools
    |    |- ... ???
    |- mem
    |    |-business
    |    |-core
    |    |-tools
    |    |- ... ???
    Et du coup, tools ne reprendrait alors les outils utiles UNIQUEMENT pour la bibliothèque en question... Me trompe-je ?
    Non, effectivement, tu ne te trompes pas.

    Mais, dans les faits, ce serait sans doute moins pratique et moins évident

    Car, imaginons que ecs et que mem utilisent tous les deux le même outil (ton fichier ivector.hpp, pour disposer d'un exemple concret ). Après tout, il n'y a rien qui l'empêcherait, vu que les fonctionnalités mises en oeuvre sont ... des outils, qui sont -- par nature -- adaptés à "n'importe quelle situation similaire".

    Un jour, peut-être dans un an, tu vas te rendre compte -- pendant le développement de ecs -- "qu'il y a un bug" dans ce fichier, et tu vas bien sur le corriger dans ... ecs/tools. Oui, mais, le bug, il est aussi présent dans mem/tools! Et il mérite aussi d'être corrigé !

    Sauf que tu seras tellement pris "le nez dans la m..de" que tu oublieras de répercuter la correction dans mem/tools. Si bien que ce bug -- que tu croiras avoir corrigé -- sera encore bel et bien patent mais... dans d'autres circonstances

    Le conseil du DRY (Don't Repeat Yourself) s'applique à l'ensemble du code, sur l'ensemble du projet ou de la "solution" envisagé(e). Tu dois éviter d'avoir deux fichiers identiques à des endroits différents

    Comme SFML, OpenGL ou encore GLM par exemple ? Ou plutôt, comme je l'utilise aujourd'hui, le "random" ?
    Oui, exactement: j'y placerais toutes les bibliothèques externes dont j'ai besoin et dont la présence (en version "de développement") n'est pas forcément garantie

    En fait oui. Car même si cela nous oblige un travail supplémentaire (using std::mersenne_twister_19937 = std::mt19937;) cela nous responsabilise dans la gestion des noms des fonctions que l'on apporte et/ou nous incite à créer des wrappers pour ces classes. Mais ces remarques ne sont qu'un clin d’œil de mon point de vue sur la chose. Globalement, les noms ne me dérangent pas non plus outre mesure et malgré tout le mal que je puisse dire des noms de la STL, même avec une organisation foireuse, je m'y retrouve donc bon...
    Pour la petite histoire, t'ai je déjà dit que bon nombre des fonctionnalités (les threads, random, file_system, et bien d'autres) de la bibliothèque standard ajoutée en C++11 et depuis sont tout droit tirées de boost

    Il semblerait que le comité se soit contenté de récupérer le nom (qui est un alias de type du genre de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    typedef mersenne_twister_engine< uint32_t, 32, 624, 397, 31, 0x9908b0df, 11, 0xffffffff, 7, 0x9d2c5680, 15, 0xefc60000, 18, 1812433253 > mt19937;
    dans boost.random) sans y changer quoi que ce soit
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  16. #36
    Membre actif Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Points : 219
    Points
    219
    Par défaut
    Ok, je pense avoir bien compris la logique.

    Je met donc actuellement en place une solution de ce genre, grâce à CMake.... Et aussi MSVC que je me suis résigné à prendre (la version gratuite uniquement). Faut avouer, il est bien plus complet / paramétrable que CB. Mais ça reste quand même une usine à gaz... Quand je vois des vidéos sur le net, pas une seule ne propose un affichage similaire C'est pas trop dérangeant mais pas mal de fonctionnalités que je vois ça et là (uniquement sur la partie affichage / mise en forme) m'intéressent beaucoup, sans que je vois comment les mettre en place. Bref. Ceci reste superflu. Je trouverais bien ce que je cherche si je continue à utiliser cet outil.
    Ce qui m'ennuie par contre, c'est le comportement des solutions.
    J'en ait une qui marche bien, from scratch et qui me fourni ce que je veux quand je le lance via CMake directement (pour un projet CB). Mais voilà.... Cette même solution ne fonctionne pas sous MSVC !! raison :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Impossible d'ouvrir le fichier include*: 'lib/bidule.hpp'*: No such file or directory
    bidule.hpp est simplement le header du code de ma bibliothèque test pour CMake / MSVC et qu'il est intégré dans le main.

    EDIT : En fait un des CMakeFiles comprenait une erreur : ${CMAKE_INCLUDE_SOURCE_DIR} au lieu de ${CMAKE_CURRENT_SOURCE_DIR}... Je vais donc pouvoir tester sur quelque chose d'un peu plus important !

    Pourtant, les solutions sont bien les mêmes (les CMakeLists aussi du coup) ! Faut que je trouve comment MSVC gère ses CMake... C'est fou quand même quand on y repense, les outils de fou que l'on fait pour aligner 3 lignes de code et qui te plombent sur un truc pareil alors que ça marche avec un pauvre fichier txt et une ligne de code J'en resterais dubitatif...
    Deux jours que je bataille sur des outils plutôt que sur mon code... Ça me rends un peu triste quand même. Mais bon, j'ose me laisser croire que c'est du temps de gagner sur le moyen terme.

  17. #37
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Ben, ca, c'est toujours le même problème : un outil -- quel qu'il soit -- ne te fera réellement gagner du temps qu'à partir du moment où tu auras appris à l'utiliser correctement

    Et, bien sur, cet apprentissage prend toujours "un peu de temps", mais ce n'est généralement pas du temps perdu, quand on le compare au temps que l'on gagne, au final, grâce à l'utilisation correcte de l'outil
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  18. #38
    Membre actif Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Points : 219
    Points
    219
    Par défaut
    Oui.

    Par contre je galère vraiment avec les bibliothèques externes et les problèmes de redéfinition... Malgré les include_directories() et les link_directories() j'ai l'impression qu'il ne veut pas aller chercher ce qu'il faut où je le veux (pourtant l'ordre est respecté). MSVC me sort toujours fatal error LNK1120: 2 externes non rÚsolus liées aux fonctions propres à SFML. Puis je ne peux pas (mais là c'est peut être normale par contre) définir de variables dans mes *.hpp sous peine de me retrouver avec des redéfinitions de partout.

    Finalement, je comprends pourquoi j'ai pris l'habitude de tout faire dans des *.hpp.

    Je vais continuer à me casser les dents sur ce sujet un moment.

  19. #39
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Pour ce qui est des bibliothèques externes, tu dois commencer par utiliser l'instruction find_package(nom_de_la_bibliothèque) qui devrait, si tout va bien, générer des variables spécifique pour au moins:
    • le dossier dans lequel se trouve les fichiers d'en-tête
    • le dossier dans lequel la bibliothèque compilée (libXXX.a sous Gcc XXX.lib sous visual studio)
    • le nom de la bibliothèque à utiliser pour l'édition de liens

    Fais attention au fait que tu ne peux -- a priori -- pas utiliser une bibliothèque externe compilée avec Gcc avec une application compilée avec visual studio (et inversement, bien sur); et que, dans l'idéal, il faut que la version du compilateur utilisée pour compiler la bibliothèque soit la même que celle utilisée pour compiler l'application

    L'utilisation de la commande [c]target_link_libraries(nom_de_la_cible liste_des_bibliothèques_externes ) devrait faire en sorte que les bonnes options, avec les bonnes informations soient mises au bon endroit

    Il y a un livre paru assez récemment sur l'utilisation de cmake qui s'appelle Cmake cookbook (en anglais). Ils ont eu la bonne idée de placer tous leurs exemples sur git et de les laisser en accès strictement libre ==>ICI<==.

    N'hésites pas à t'inspirer des exemples qu'iils proposent dans ce livre, car tu y trouveras plein d'informations utiles
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  20. #40
    Membre actif Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Points : 219
    Points
    219
    Par défaut
    merci pour le lien ; j'y jette un œil !

    je me suis renseigné sur le mode d'ajout des bibliothèques externes et... Malheureusement, je ne tiens pas à devoir me refaire les fichiers FindSFML.cmake nécessaire au bon fonctionnement de find_package() (je ne les ai pas trouvés dans le master de SFML, même après compilation comme ça peut être indiqué ça et là). Par contre, j'ai réussi à en créer pour mes propres bibliothèques (sur des versions de test uniquement encore donc sans bibliothèque externe). C'est utile quand on les fait une par une, mais pour faire tout SFML je risque de passer plus de temps à faire du "fichier de conf cmake" que du c++
    Je vais commencer par essayer de trouver un moyen de récupérer ou faire ces fichiers....

    Après, histoire de remettre les choses dans leur contexte, une fois que l'on a mis les mains dedans, est-ce vraiment indispensable de passer par CMake ? Enfin, je veux dire, à l'utilisation, y gagne t-on vraiment en terme de convivialité de gestion des projets par rapport au projet standard configuré depuis l'IDE ? Les dev en mode projet passent vraiment 2 semaines à faire leurs CMake à chaque fois comme moi (et encore, il me reste pas mal de boulot à ce niveau là ^^) ?

    Au passage, j'essayais bien, effectivement, d'inclure des bibliothèques compilées sous gcc avec MSVC. L'erreur venait donc probablement de là ! Je vais devoir re-créer un projet pour valider l'erreur (j'ai tout supprimé depuis), mais ceci expliquerait cela

    Aussi j'ai arrêté mon choix, je pense, sur CLion. Pas trop le choix que de passer du temps sur les CMake mais j'aime bien son interface. Seul hic, je dois encore trouver comment le faire compiler sous Cygwin.

    Je continue à faire mes *.txt au lieu du c++. Je dois arriver à mettre en place mon architecture projet.

    Merci.

+ Répondre à la discussion
Cette discussion est résolue.
Page 2 sur 3 PremièrePremière 123 DernièreDernière

Discussions similaires

  1. Héritage initialisation membre const
    Par themadmax dans le forum C++
    Réponses: 7
    Dernier message: 26/05/2011, 15h31
  2. Pointeur de fonction membre et héritage
    Par Caduchon dans le forum Langage
    Réponses: 6
    Dernier message: 25/03/2011, 12h02
  3. [POO] Héritage : Surcharge d'un membre statique parent
    Par Nullos Oracle dans le forum Langage
    Réponses: 7
    Dernier message: 11/09/2007, 18h39
  4. Réponses: 16
    Dernier message: 17/03/2007, 17h31
  5. [POO] Pointeur sur fonction membre et héritage
    Par MrDuChnok dans le forum C++
    Réponses: 9
    Dernier message: 20/07/2006, 17h19

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo