Partage
  • Partager sur Facebook
  • Partager sur Twitter

Problème de performance et de stockage.

fonctions virtuelles, et types à stocker.

    1 juillet 2021 à 20:53:41

    Salut! Alors je fais appel à vous car j'ai un problème, je possède un grand nombre d'objets de types différents dans mon jeux, alors j'ai utilisé un std::vector sur un type d'une classe de base (Entity) pour éviter de devoir créer autant de std::vector que je n'ai de types d'objets, cependant cela est contre performant car la recherche de la bonne fonction à appeler suivant le type de l'objet se fait à l'exécution et non pas à la compilation donc si il y a beaucoup d'objets, ça pose problème. 

    Ce que je voudrais faire donc c'est utiliser l'héritage, sans devoir faire appel à des fonctions virtuelles ce qui est très compliqué car je ne connais pas le type de base des objets en compilation!

    J'ai donc pensé à utiliser un dispatcher générique qui fonctionne comme ceci :

    struct chat;
      struct chien;
      struct animal
        : accept_visitor<animal, chien, chat>
      { };
    
      struct chat
              : acceptable<animal, chat>
      { };
    
      struct chien
              : acceptable<animal, chien>
      { };
      struct attaquer
              : dispatchable<attaquer, animal>
      {
              template <typename Archive>
              void operator()(const chat&, const chat&, Archive& ar) const
              { std::cout <<  "griffe - griffe" << std::endl;}
              template <typename Archive>
              void operator()(const chat&, const chien&, Archive& ar) const
              { std::cout <<  "griffe - mord  " << std::endl;}
              template <typename Archive>
              void operator()(const chien&, const chat&, Archive& ar) const
              { std::cout <<  "mord - griffe" << std::endl;}
              template <typename Archive>
              void operator()(const chien&, const chien&, Archive& ar) const
              { std::cout << "mord - mord  " << std::endl;}          
      };
    int main(int argc, char* argv[])
    {    
        
        chat c1;
        chien c2;
        animal& a1 =c1;
        animal& a2 =c2;
        std::ostringstream oss;
        OTextArchive ota(oss);
        attaquer().apply(a1,a1, ota);
        attaquer().apply(a1,a2, ota);
        attaquer().apply(a2,a1, ota);
        attaquer().apply(a2,a2, ota);
        return 0;
    }

    Pour appeler la bonne fonction, en fonction du type dérivé et en passant des paramètres supplémentaire à un foncteur qui est une classe à responsabilité unique.

    Mais là encore une fois, il y a des fonctions virtuelles! (Sauf que cette fois je n'ai plus que des fonctions virtuelles pures!!!)

    //Utilitaire pour ajouter un type à la fin d'un tuple
            template<class,class>
            struct cat_type;
    
            template<class T, class... Arg>
            struct cat_type<std::tuple<Arg...>,T>
            {
                using type =std::tuple<Arg...,T>;
            };
    
            //Classe qui surcharge accept pour les types concret
            template<class Abstract, class>
            struct acceptable : Abstract
            {
                void accept(const typename Abstract::visitor_type& v)
                {
                    v.visit(*this);
                }
            };
    
    
            //Classe de base des visiteurs
            //La visite se fait sur le type acceptable qui contient l'information sur le type réel
            template<class, class...>
            struct visitor;
    
            //Récursion
            template<class Abstract, class Concrete, class... Concrete_Tail>
            struct visitor<Abstract,Concrete,Concrete_Tail...>
                : visitor<Abstract,Concrete_Tail...>
            {
                using visitor<Abstract,Concrete_Tail...>::visit;
                virtual void visit(acceptable<Abstract,Concrete>&) const =0;
            };
    
            //Condition d'arrêt
            template<class Abstract, class Concrete>
            struct visitor<Abstract,Concrete>
            {
                visitor() =default;
                visitor(const visitor&) =delete;
    
                virtual ~visitor()
                { }
    
                visitor& operator=(const visitor&) =delete;
    
                virtual void visit(acceptable<Abstract,Concrete>&) const =0;
            };
    
    
            //Utilitaire pour récupérer le type accepté par la hiérarchie depuis le visiteur
            template<class,class>
            struct accept_type;
    
            template<
                class Abstract, class... Concrete,
                class T
            >
            struct accept_type<visitor<Abstract,Concrete...>,T>
            {
                using type =acceptable<Abstract,T>;
            };
    
    
            //Utilitaire pour récupérer les types concrets de la hiérarchie depuis le visiteur
            template<class>
            struct concrete_type;
    
            template<class Abstract, class... Concrete>
            struct concrete_type<visitor<Abstract,Concrete...>>
            {
                using type =std::tuple<Concrete...>;
            };
    
    
            //Classe de base qui ajoute accept à la classe de base de la hiérarchie
            template<class Abstract, class... Concrete>
            struct accept_visitor
            {
                using visitor_type =visitor<Abstract,Concrete...>;
    
                accept_visitor() =default;
                accept_visitor(const accept_visitor&) =delete;
    
                virtual ~accept_visitor()
                { }
    
                accept_visitor& operator=(const accept_visitor&) =delete;
    
                virtual void accept(const visitor_type&) = 0;
            };
    
    
            //Classe dispatcher : c'est un visiteur et un foncteur
    
            //Condition d'arrêt sur les types de la hiérarchie
            template<
                class To_Visit, class, bool,
                class Visitor, class,
                class Fun,
                class T
            >
            struct dispatcher : Visitor
            {
                dispatcher(To_Visit& tv, const Fun& f, const T& params)
                    : to_visit(tv), fun(f), params(params)
                {
    
                }
    
            protected:
                To_Visit& to_visit;
                Fun fun;
                T params;
            };
    
            //Récursion sur les types de la hiérarchie
            template<
                class To_Visit, class Visited,
                class Visitor, class T, class... Concrete,
                class Fun,
                class Tp
            >
            struct dispatcher<
                To_Visit,Visited,false,
                Visitor,std::tuple<T,Concrete...>,
                Fun,
                Tp
            >
                : dispatcher<
                    To_Visit,Visited,false,
                    Visitor,std::tuple<Concrete...>,
                    Fun,
                    Tp
                >
            {
            private:
                using base =dispatcher<
                    To_Visit,Visited,false,
                    Visitor,std::tuple<Concrete...>,
                    Fun,
                    Tp
                >;
    
            public:
                using base::base;
    
                //Déclenche la visite sur le bon paramètre
                void operator()() const
                {
                    std::get
                        <std::tuple_size<Visited>::value>
                        (base::to_visit)
                    .accept(*this);
                }
    
                using base::visit;
                void visit(typename accept_type<Visitor,T>::type&) const
                {
                    using new_visited =typename cat_type<Visited,T>::type;
    
                    //Récursion sur les arguments d'appel
                    dispatcher<
                        To_Visit,new_visited,
                        std::tuple_size<To_Visit>::value == std::tuple_size<new_visited>::value,
                        Visitor,typename concrete_type<Visitor>::type,
                        Fun,
                        Tp
                    >(base::to_visit,base::fun,base::params)();
                }
            };
    
            //Condition d'arrêt sur les paramètre d'appel
            template<
                class To_Visit, class... Visited,
                class Visitor, class Concrete,
                class Fun,
                class T
            >
            struct dispatcher<
                To_Visit,std::tuple<Visited...>,true,
                Visitor,Concrete,
                Fun,
                T
            >
            {
                dispatcher(To_Visit& tv, const Fun& f, const T& params)
                    : to_visit(tv), fun(f), params(params)
                {
    
                }
    
                //Déclenche l'appel avec les bon types
                void operator()()
                {
                    apply(std::make_index_sequence<sizeof...(Visited)>(), std::make_index_sequence<std::tuple_size<decltype(params)>::value>());
                }
    
            private:
                template<std::size_t... I, std::size_t... I2>
                void apply(std::index_sequence<I...>, std::index_sequence<I2...>)
                {
                    fun(reinterpret_cast<Visited&>(std::get<I>(to_visit))..., std::get<I2>(params)...);
                }
    
                To_Visit& to_visit;
                Fun fun;
                T params;
            };
    
    
            //Classe de base pour les foncteurs à dispatcher
            template<class Fun, class Abstract>
            struct dispatchable
            {
                dispatchable() =default;
                dispatchable(const dispatchable&) =default;
    
                dispatchable& operator=(const dispatchable&) =default;
                template<class... Args>
                void apply(Args&&... args)
                {
                    using used_visitor =typename Abstract::visitor_type;
    
                    auto t =std::forward_as_tuple(std::forward<Args>(args)...);
                    auto types = odfaeg::core::remove_if_not<Abstract&>::f(t);
                    auto acceptable = cast(std::make_index_sequence<std::tuple_size<decltype(types)>::value>(), types);
                    auto params = odfaeg::core::remove_if<Abstract&>::f(t);
                    dispatcher<
                        decltype(acceptable),std::tuple<>,false,
                        used_visitor,typename concrete_type<used_visitor>::type,
                        Fun,
                        decltype(params)
                    >(acceptable,static_cast<Fun&>(*this), params)();
                }
                template<std::size_t... I, class... Acceptables>
                auto cast (std::index_sequence<I...>, std::tuple<Acceptables...> acceptables) {
                    return std::forward_as_tuple(static_cast<Abstract&>(std::get<I>(acceptables))...);
                }
            protected:
                ~dispatchable()
                { }
            };

    Le soucis ici c'est que pour chaque objet héritant de acceptable, il recherche la bonne fonction à appeler à l'exécution suivant le type de l'objet dérivé non ? (Même si la fonction est virtuelle pure et qu'elle prend un objet qui prendre des argument template mais je ne pense pas que ça change quoi que ce soit!)

    Donc voilà je fais appel à vous si quelqu'un à une solution pour optimiser mon code car moi, je n'en ai pas là!!!


    EDIT : j'ai entendu parlé de CRTP et du static polymorphism mais pour contenir les objets je vais également avoir pleins de types différents.



    -
    Edité par OmbreNoire 1 juillet 2021 à 21:11:41

    • Partager sur Facebook
    • Partager sur Twitter
      2 juillet 2021 à 0:33:13

      Je vais faire simple : si tes objets sont créés à l'exécution, ca n'est pas possible de résoudre les appels de fonctions à la compilation. Si tes objets sont connus à la compilation, tu n'as pas besoin de dispatch ou quoi que ce soit.

      Ton premier code peut simplement s'écrire avec des appels de fonctions surchargées :

      struct chat {
        void attaquer()(const chat&) const { 
          std::cout <<  "griffe - griffe" << std::endl;
        }
        void attaquer()(const chien&) const { 
          std::cout <<  "griffe - mord  " << std::endl;
        }
      };
      
      struct chien {
        void attaquer()(const chat&) const { 
          std::cout <<  "mord - griffe" << std::endl;
        }
        void attaquer()(const chien&) const { 
          std::cout <<  "mord - mord  " << std::endl;
        }
      };
      
      int main(int argc, char* argv[])
      {   
          chat c1;
          chien c2;
          c1.attaquer(c1);
          c1.attaquer(c2);
          c2.attaquer(c1);
          c2.attaquer(c2);
      }

      Si tu veux faire du dispatch avec des objets polymorphiques créés à l'exécution, tu auras forcément un coût à l'exécution pour la résolution des appels de fonctions. Que ce soit avec un vtable, comme pour les fonctions virtuelles, ou un dispatch, comme avec un variant (qui utilise aussi une table de pointeurs de fonction... donc ca revient au même qu'une vtable, globalement).

      OmbreNoire a écrit:

      Donc voilà je fais appel à vous si quelqu'un à une solution pour optimiser mon code car moi, je n'en ai pas là!!!

      Donc globalement, toujours la même chose : si tu veux parler sérieusement :

      - étudie comment fonctionne les techniques actuelles (en particulier les vtable, le dynamic dispatch, etc. J'ai fait un post résumé récemment sur le double dispatch : https://openclassrooms.com/forum/sujet/dispatching-et-lambda#message-94143836

      - écris un code minimaliste qui permet que l'on puisse tester ton idée (idéalement dans un IDE en ligne)

      - ce code DOIT contenir les benchmarks pour tester les performances des différentes solutions

      Tant que tu ne bosseras pas sérieusement et proprement, personne ne pourra t'aider. (On n'optimise pas a partir de solution théorique, il faut faire tourner le code et faire des benchs)

      OmbreNoire a écrit:

      EDIT : j'ai entendu parlé de CRTP et du static polymorphism mais pour contenir les objets je vais également avoir pleins de types différents.

      Aucun rapport

      • Partager sur Facebook
      • Partager sur Twitter
        2 juillet 2021 à 11:48:01

        Salut!

        En fait j'ai finalement trouvé une solution pour optimiser mon code et utiliser CRTP, je ne connais pas les types des classes que le développeur va utiliser lorsqu'il va utiliser le framework mais je peux lui demander de les fournir avec un template variadique.

        En même temps l'héritage m'évite de devoir dupliquer les données de la classe de base (il y en a quand même beaucoup dans mon cas), et le polymorphisme statique me permet d'éliminer le coût lors de l'appels de fonctions virtuelles à l'exécution.

        On m'avait conseillé d'un autre côté de ne pas faire de cast vers le bas parce que l'on m'avait dit que c'était une erreur de design mais de plutôt utiliser des fonctions virtuelles et après on me dit que les fonctions virtuelles avec l'héritage ça n'est pas bien parce que ça a un surcoût à l'exécution.

        Mais bon voilà ce que je veux faire, un code sera peut être plus parlant.

        struct animal {
            unsigned int id;
            animal() {
                id = nbInstances;
                nbInstances = id;
                nbInstances++;
            }
            static unsigned int nbInstances;
        };
        unsigned int animal::nbInstances = 0;
        struct cat : animal
        {
          void applySpecificBehaviour() {
              std::cout<<"cat beheviour of instance : "<<id<<std::endl;
          }
        };
        
        struct dog : animal
        {
          void applySpecificBehaviour() {
              std::cout<<"dog beheviour of instance : "<<id<<std::endl;
          }
        };
        
        struct bird : animal
        {
          void applySpecificBehaviour() {
              std::cout<<"bird beheviour of instance : "<<id<<std::endl;
          }
        };
        template <size_t N, class B, size_t I, class... Derived>
        struct Holder {
        
        };
        template <size_t N, class B, size_t I, class Head, class... Tail>
        struct Holder<N, B, I, Head, Tail...> : Holder<N, B, I+1, Tail...> {
            std::vector<B*> entities;
            void add(B* entity) {
                entities.push_back(entity);
                Holder<N, B, I+1, Tail...>::add(entity);
            }
            void applyBehaviours() {
                for (unsigned int i = 0; i < entities.size() / N; i++) {
                    static_cast<Head*>(entities[i*N+I])->applySpecificBehaviour();
                }
                Holder<N, B, I+1, Tail...>::applyBehaviours();
            }
        };
        template <size_t N, class B, size_t I, class Head>
        struct Holder<N, B, I, Head> {
            std::vector<B*> entities;
            void add(B* entity) {
                entities.push_back(entity);
            }
            void applyBehaviours() {
                for (unsigned int i = 0; i < entities.size() / N; i++) {
                    static_cast<Head*>(entities[i*N+I])->applySpecificBehaviour();
                }
            }
        };
        int main(int argc, char* argv[])
        {
            /*using tuple_t = sort<LessPlaceceholder, unique<copy_if<is_placeholder,std::tuple<placeholder<3, int>, int, placeholder<2, int>, float, placeholder<1, int>, char, placeholder<0, int>>>::f/*>::f>::f;*/
            /*using late_params_t = lift<tuple_t, LateParameters,std::make_index_sequence<std::tuple_size<tuple_t>()-0>>::f;
            tuple_t tp = std::make_tuple(placeholder<3, int>(),placeholder<2, int>(),placeholder<1, int>(),placeholder<0, int>());
            return 0;*/
            cat c1;
            dog d1;
            bird b1;
            cat c2;
            dog d2;
            bird b2;
            Holder<3, animal, 0, cat, dog, bird> holder;
            holder.add(&c1);
            holder.add(&d1);
            holder.add(&b1);
            holder.add(&c2);
            holder.add(&d2);
            holder.add(&b2);
            holder.applyBehaviours();
            return 0;
        }

        La compilation est assez longue mais ça devrait être plus rapide que l'utilisation de fonctions virtuelles.

        Bref peut importe le design que j'utilise on me dira toujours que ce n'est pas bien et j'en ai déjà essayé des tonnes (variants, dispatchers, factory, etc...).

        -
        Edité par OmbreNoire 2 juillet 2021 à 11:53:44

        • Partager sur Facebook
        • Partager sur Twitter
          2 juillet 2021 à 12:07:36

          OmbreNoire a écrit:

          On m'avait conseillé d'un autre côté de ne pas faire de cast vers le bas parce que l'on m'avait dit que c'était une erreur de design mais de plutôt utiliser des fonctions virtuelles et après on me dit que les fonctions virtuelles avec l'héritage ça n'est pas bien parce que ça a un surcoût à l'exécution.

          On peut downcaster, ce n'est une erreur que du point de vue d'une certaine philosophie...

          Je te conseille de regarder un peu ça :
          https://github.com/skypjack/entt

          PS: à ta place, je laisserais tomber les animaux, les chiens et les oiseaux, pour des structures comme vitesse, position, vie (qui sont transversales et ne dérivent pas les unes des autres)... Une logique ECS sera plus claire, plus performante, plus modulable et plus maintenable dans beaucoup de cas... Enfin c'est encore une certaine philosophie tu me diras :)

          -
          Edité par Umbre37 2 juillet 2021 à 12:47:39

          • Partager sur Facebook
          • Partager sur Twitter
            2 juillet 2021 à 12:48:51

            OmbreNoire a écrit:

            Bref peut importe le design que j'utilise on me dira toujours que ce n'est pas bien et j'en ai déjà essayé des tonnes (variants, dispatchers, factory, etc...).

            Hier, tu connaissais pas le CRTP et le static polymorphism et aujourd'hui, tu as optimisé ton code (sans benchmarks, je suppose...) mieux que les vtable et les variants...

            Tant que tu ne seras pas capable de montrer un code qu'on peut compiler nous même et dans lequel tu compares les performances entre plusieurs solutions, tu bosse dans le vent.

            C'est facile ensuite de venir dire que c'est notre faute, mais on ne peut que donner des pistes. Si toi tu ne fais pas ton boulot pour écrire le code de projets de tests, c'est toi qui fait n'importe quoi. Pas nous.

            ARRÊTE DE FAIRE DU CACA ET FAIS UN PROJET DE TEST, AVEC BENCHMARKS POUR COMPARER LES DIFFERENTES SOLUTIONS.

            • Partager sur Facebook
            • Partager sur Twitter
              2 juillet 2021 à 16:28:52

              Ha oui c'est plus rapide en utilisant le pattern CRTP plutôt que des fonctions virtuelles!!!

              struct animal {
                  unsigned int id;
                  unsigned int typeId;
                  static unsigned int nbInstances;
                  animal(unsigned int typeId) {
                      id = nbInstances;
                      nbInstances = id;
                      nbInstances++;
                      this->typeId = typeId;
                  }
                  template <typename D>
                  void applySpecificBehaviour() {
                    std::cout<<"base beheviour of instance : "<<id<<std::endl;
                    onSepecifiedBehaviourCalled<D>();
                  }
                  template <typename D>
                  void onSepecifiedBehaviourCalled() {
                      static_cast<D&>(*this).onSepecifiedBehaviourCalled();
                  }
              };
              unsigned int animal::nbInstances = 0;
              struct cat : animal
              {
                cat() : animal(0) {}
                void applySpecificBehaviour() {
                    animal::applySpecificBehaviour<cat>();
                    std::cout<<"cat beheviour of instance : "<<id<<std::endl;
                }
                void onSepecifiedBehaviourCalled() {
                    std::cout<<"cat on specified behaviour : "<<id<<std::endl;
                }
              };
              
              struct dog : animal
              {
                dog() : animal(1) {}
                void applySpecificBehaviour() {
                    animal::applySpecificBehaviour<dog>();
                    std::cout<<"dog beheviour of instance : "<<id<<std::endl;
                }
                void onSepecifiedBehaviourCalled() {
                    std::cout<<"dog on specified behaviour : "<<id<<std::endl;
                }
              };
              
              struct bird : animal
              {
                bird() : animal(2) {}
                void applySpecificBehaviour() {
                    animal::applySpecificBehaviour<bird>();
                    std::cout<<"bird beheviour of instance : "<<id<<std::endl;
                }
                void onSepecifiedBehaviourCalled() {
                    std::cout<<"bird on specified behaviour : "<<id<<std::endl;
                }
              };
              struct vanimal {
                  unsigned int id;
                  unsigned int typeId;
                  static unsigned int nbInstances;
                  vanimal(unsigned int typeId) {
                      id = nbInstances;
                      nbInstances = id;
                      nbInstances++;
                      this->typeId = typeId;
                  }
                  virtual void applySpecificBehaviour() {
                    std::cout<<"base beheviour of instance : "<<id<<std::endl;
                    onSepecifiedBehaviourCalled();
                  }
                  virtual void onSepecifiedBehaviourCalled() {
              
                  }
              };
              unsigned int vanimal::nbInstances = 0;
              struct vcat : vanimal
              {
                vcat() : vanimal(0) {}
                void applySpecificBehaviour() {
                    vanimal::applySpecificBehaviour();
                    std::cout<<"cat beheviour of instance : "<<id<<std::endl;
                }
                void onSepecifiedBehaviourCalled() {
                    std::cout<<"cat on specified behaviour : "<<id<<std::endl;
                }
              };
              
              struct vdog : vanimal
              {
                vdog() : vanimal(1) {}
                void applySpecificBehaviour() {
                    vanimal::applySpecificBehaviour();
                    std::cout<<"dog beheviour of instance : "<<id<<std::endl;
                }
                void onSepecifiedBehaviourCalled() {
                    std::cout<<"dog on specified behaviour : "<<id<<std::endl;
                }
              };
              
              struct vbird : vanimal
              {
                vbird() : vanimal(2) {}
                void applySpecificBehaviour() {
                    vanimal::applySpecificBehaviour();
                    std::cout<<"bird beheviour of instance : "<<id<<std::endl;
                }
                void onSepecifiedBehaviourCalled() {
                    std::cout<<"bird on specified behaviour : "<<id<<std::endl;
                }
              };
              template <class B, size_t I, class... Derived>
              struct Holder {
              
              };
              template <class B, size_t I, class Head, class... Tail>
              struct Holder<B, I, Head, Tail...> : Holder<B, I+1, Tail...> {
                  std::vector<B*> entities;
                  void add(B* entity) {
                      if (entity->typeId == I)
                          entities.push_back(entity);
                      Holder<B, I+1, Tail...>::add(entity);
                  }
                  void applyBehaviours() {
                      for (unsigned int i = 0; i < entities.size(); i++) {
                          static_cast<Head*>(entities[i])->applySpecificBehaviour();
                      }
                      Holder<B, I+1, Tail...>::applyBehaviours();
                  }
              };
              template <class B, size_t I, class Head>
              struct Holder<B, I, Head> {
                  std::vector<B*> entities;
                  void add(B* entity) {
                      if (entity->typeId == I)
                          entities.push_back(entity);
                  }
                  void applyBehaviours() {
                      for (unsigned int i = 0; i < entities.size(); i++) {
                          static_cast<Head*>(entities[i])->applySpecificBehaviour();
                      }
                  }
              };
              template <class B>
              struct VHolder {
                  std::vector<B*> entities;
                  void add(B* entity) {
                      entities.push_back(entity);
                  }
                  void applyBehaviours() {
                      for (unsigned int i = 0; i < entities.size(); i++) {
                          entities[i]->applySpecificBehaviour();
                      }
                  }
              };
              int main(int argc, char* argv[])
              {
                  /*using tuple_t = sort<LessPlaceceholder, unique<copy_if<is_placeholder,std::tuple<placeholder<3, int>, int, placeholder<2, int>, float, placeholder<1, int>, char, placeholder<0, int>>>::f/*>::f>::f;*/
                  /*using late_params_t = lift<tuple_t, LateParameters,std::make_index_sequence<std::tuple_size<tuple_t>()-0>>::f;
                  tuple_t tp = std::make_tuple(placeholder<3, int>(),placeholder<2, int>(),placeholder<1, int>(),placeholder<0, int>());
                  return 0;*/
                  cat c1;
                  dog d1;
                  bird b1;
                  cat c2;
                  dog d2;
                  bird b2;
                  Holder<animal, 0, cat, dog, bird> holder;
                  holder.add(&c1);
                  holder.add(&d1);
                  holder.add(&b1);
                  holder.add(&c2);
                  holder.add(&d2);
                  holder.add(&b2);
                  sf::Clock clk;
                  holder.applyBehaviours();
                  std::cout<<"CRTP time : "<<clk.getElapsedTime().asMicroseconds()<<std::endl;
                  vcat vc1;
                  vdog vd1;
                  vbird vb1;
                  vcat vc2;
                  vdog vd2;
                  vbird vb2;
                  VHolder<vanimal> vholder;
                  vholder.add(&vc1);
                  vholder.add(&vd1);
                  vholder.add(&vb1);
                  vholder.add(&vc2);
                  vholder.add(&vd2);
                  vholder.add(&vb2);
                  clk.restart();
                  vholder.applyBehaviours();
                  std::cout<<"Virtual function time : "<<clk.getElapsedTime().asMicroseconds()<<std::endl;
                  return 0;
              }



              -
              Edité par OmbreNoire 2 juillet 2021 à 17:07:54

              • Partager sur Facebook
              • Partager sur Twitter
                2 juillet 2021 à 19:06:45

                Au moins, on commence a avancer !

                Pour commencer, c'est pas du CRTP. C'est juste de la recursion template.

                ------------------------------------------------------------------------

                1. les std::cout (et plus généralement les entrées-sorties) sont lents. Si tu benchmarks du code qui contient des std::cout, tu va surtout mesurer ce temps et pas le temps de ta résolution de fonction.

                Il faut être sur que ce que tu mesures correspond bien à ce que tu veux mesurer (ici la résolution des appels de fonctions) et pas du bruit ou du code externe. Ou alors il faut utiliser des outils de profiling, qui vont donner des chiffres plus précis, pour chaque partie du code, ce qui te permettra de savoir où se trouve ta perte de performance exactement.

                A faire : il faut retirer tes std::cout.

                ------------------------------------------------------------------------

                2. tu ne mesures pas la même chose. Dans le premier cas, sauf erreur de ma part, tu as une résolution des types à la compilation (via ton Holder avec variadic template). Dans le second cas, tu as une résolution au runtime via la vtable.

                Cf par exemple ce que C++ Insight produit pour ton holder :

                template<>
                struct Holder<animal, 0, cat, dog, bird> : public Holder<animal, 1, dog, bird>
                {...
                  inline void applyBehaviours() 
                  {...
                    static_cast<cat *>(...
                  }
                };
                
                template<>
                struct Holder<animal, 1, dog, bird> : public Holder<animal, 2, bird>
                {...
                  inline void applyBehaviours()
                  {...
                    static_cast<dog *>(...
                  }
                };
                
                template<>
                struct Holder<animal, 2, bird>
                {...
                  inline void applyBehaviours()
                  {...
                    static_cast<bird *>(...    
                  }
                };

                Comme tu peux le voir, tu construis une structure qui retient les types et qui applique un static_cast sur le type correct.

                Globalement, ton code ne sert pas a grand chose. Tu ne fais au final que réécrire std::tuple + std::apply.

                A faire : teste aussi les performances de ton code versus std::tuple.

                ------------------------------------------------------------------------

                3. Si tu connais les objets que tu vas construire a la compilation, tu n'as pas besoin de ta première solution du tout. Tu peux directement appeler tes fonctions.

                cat c1;
                dog d1;
                bird b1;
                cat c2;
                dog d2;
                bird b2;
                c1.applySpecificBehaviour();
                d1.applySpecificBehaviour();
                b1.applySpecificBehaviour();
                c2.applySpecificBehaviour();
                d2.applySpecificBehaviour();
                b2.applySpecificBehaviour();
                

                En soi, pourquoi pas avoir les entités d'un jeu connu à la compilation. Si tu fais un tic tac toe ou un jeu d'échec, il n'y a pas besoin de créer des objets dynamiques. 

                Mais dans la majorité des jeux, les entités sont créées dynamiquement. Si tu veux tester ton framework de façon réaliste, tu dois créer dynamiquement tes objets.

                A faire : crées tes objets dynamiquement, via une factory. Par exemple :

                const auto entity_names = { "dog", "cat", "bird" };
                const auto entities = factories(entity_names);
                ...

                ------------------------------------------------------------------------

                4. Dans un vrai jeu, on a rarement que 6 entités. Et pour que 6 entités, on s'en moque des performances, ca ne sera jamais critique. Quand on veut mesurer des performances, on regarde les points critiques (donc avec beaucoup d'entités) et on regarde la "scalabilité" (c'est a dire les variations de performances quand on fait varier le nombre d'entités)

                Question : quel type de jeux veux tu pouvoir créer avec ton framework ? Donne des exemples de jeux critiques, par leur nombre d'entités, ou leur complexités, etc. C'est à dire la limite max de ce que tu veux pouvoir faire comme jeux.

                A faire : test ton code avec plusieurs nombres d'entités. Il faut en particulier tester avec des valeurs max par rapport au type de jeux que tu veux faire.

                ------------------------------------------------------------------------

                5. J'ai testé ton code sur coliru.com, avec clang et avec. Le resultat :

                CRTP time: 65us
                Virtual fonction time: 8us

                Donc les fonctions virtuelles sont "plus rapides" dans mon test. 

                Comme j'ai dit avant, la première méthode résout les noms de fonction à la compilation, donc ce résultat n'a pas de sens. J'ai regardé plus en détail et ton code est en fait :

                template<>
                struct Holder<animal, 0, cat, dog, bird> : public Holder<animal, 1, dog, bird>
                {
                  std::vector<animal *, std::allocator<animal *> > entities;
                ...
                  inline void applyBehaviours()
                  {
                    for(unsigned int i = 0; static_cast<unsigned long>(i) < this->entities.size(); i++) {
                      static_cast<cat *>(this->entities.operator[](static_cast<unsigned long>(i)))->applySpecificBehaviour();
                    }
                    
                    /* static_cast<Holder<animal, 1, dog, bird> *>(this)-> */ applyBehaviours();
                  }
                };

                Donc : 

                - tu crées un nouveau std::vector pour chaque instantiation de Holder

                - tu fais des loops inutiles

                C'est exactement la raison pour laquelle on fait des benchs ! C'est pour trouver les erreurs et corriger les problèmes. C'est comme ça qu'on optimise : on teste et on améliore les choses progressivement.

                A faire : corriger ton code.

                ---------------------------------------------------------------------

                6. Tu as un code pour tes classes un peu particulier, puisque tu n'as pas d'allocation dynamique et que tu as des classes qui n'ont pas de propriétés (et donc qui n'auront pas de problème pour être mise dans un tableau en tant que valeur).

                De plus, tu as des tableaux de pointeurs, ce qui peut détruire les performances via les cache miss. Or, dans tes tests, comme tu construis tes objets sur la Pile, ils sont alignés en mémoire, ce qui n'est pas ce que tu obtiendras par défaut avec l'allocation dynamique. Donc ton test n'est pas réaliste sur ce point.

                A faire : crées des tests plus réalistes.

                ---------------------------------------------------------------------

                Voilà globalement mes commentaires après relecture rapide de ton code. 

                Tout dépend de ce que tu veux faire comme type de jeux avec ton framework. Une fois que ce point sera clarifié, tu pourras faire des tests plus réalistes et plus poussé, pour réellement optimiser ton code.

                -
                Edité par gbdivers 4 juillet 2021 à 2:20:45

                • Partager sur Facebook
                • Partager sur Twitter
                  2 juillet 2021 à 19:22:48

                  Salut, le jeux que je veux créer est un mmorpg donc il va y avoir beaucoup d'entités.

                  Avec mon implémentation je crois que le problème est qu'il faut trouver le bon type avant de l'ajouter au std::vector, de ce fait,  j'ai des "loops inutiles", la première structure va contenir tout les objets de type chat, la suivante tout les objets de type chien et la dernière tout les objets de type oiseau.

                  Je pense qu'utiliser un std::vector de std::tuple me permettrait de résoudre le problème des loops inutiles.

                  Je ne connais pas les types de toutes les entités à la compilation du framework, je ne les connais que lors de la création du jeux.

                  • Partager sur Facebook
                  • Partager sur Twitter
                    2 juillet 2021 à 19:26:33

                    OmbreNoire a écrit:

                    Je ne connais pas les types de toutes les entités à la compilation du framework, je ne les connais que lors de la création du jeux.

                    Ce qui n'est pas ce que tu fais dans ton test.

                    • Partager sur Facebook
                    • Partager sur Twitter
                      3 juillet 2021 à 10:38:20

                      gbdivers a écrit:

                      OmbreNoire a écrit:

                      Je ne connais pas les types de toutes les entités à la compilation du framework, je ne les connais que lors de la création du jeux.

                      Ce qui n'est pas ce que tu fais dans ton test.

                      Bah si  puisque ma classe Holder est template, je ne connais peut être pas les types à la compilation du framework mais je les connais à la compilation du jeux je peux donc les passer en paramètre template au framework à la compilation du jeux.

                      Ensuite je n'ai plus qu'à appeler les fonctions pour tout les types.

                      M'enfin, contrairement à ce que j'aurai pu penser, les fonctions virtuelles sont plus rapide que le polymorphisme statique, et le principal problème du polymorphisme statique, c'est qu'il faut créer un std::vector par type dans mon cas et ensuite appeler la récursion pour tout les types ce qui je pense explique le fait que ce code s'exécute plus lentement que celui avec les fonctions virtuelles.

                      J'ai aussi testé la rapidité de ma classe FastDelegate par rapport aux fonctions virtuelle en utilisant une factory et c'est plus rapide encore que les fonctions virtuelles.

                      Donc du moins rapide au plus rapide j'ai :

                      -Le polymorphisme statique.

                      -Les fonctions virtuelles.

                      -La factory.

                      La version utilisant la factory est la plus puissante car elle peut même s'utiliser avec des template chose qu'il n'est pas possible de faire avec des fonctions virtuelles, je pourrai même utiliser des placeholders puisque ma classe FastDelegate gère les placeholders.

                      On m'avait conseillé d'utiliser le polymorphisme statique plutôt que les fonctions virtuelles mais finalement je ne vais pas changer le design parce que en ayant fait des tests je me rend compte que ça sera moins rapides, surtout que c'est gênant de devoir passer les types en paramètres template.

                      J'ai retiré les std::cout, je les avais juste mit pour voir si toutes les fonctions avait été appelées.

                      On m'avait aussi conseillé de ne pas utiliser l'héritage mais bonjour la duplication de données, je vais essayer sans l'héritage une fois pour voir.

                      Bref voilà ce que ça donne :

                      struct animal {
                          unsigned int id;
                          unsigned int typeId;
                          static unsigned int nbInstances;
                          animal(unsigned int typeId) {
                              id = nbInstances;
                              nbInstances = id;
                              nbInstances++;
                              this->typeId = typeId;
                          }
                          template <typename D>
                          void applySpecificBehaviour() {
                            //std::cout<<"base beheviour of instance : "<<id<<std::endl;
                            onSepecifiedBehaviourCalled<D>();
                          }
                          template <typename D>
                          void onSepecifiedBehaviourCalled() {
                              static_cast<D&>(*this).onSepecifiedBehaviourCalled();
                          }
                      };
                      unsigned int animal::nbInstances = 0;
                      struct cat : animal
                      {
                        cat() : animal(0) {}
                        void applySpecificBehaviour() {
                            animal::applySpecificBehaviour<cat>();
                            //std::cout<<"cat beheviour of instance : "<<id<<std::endl;
                        }
                        void onSepecifiedBehaviourCalled() {
                            //std::cout<<"cat on specified behaviour : "<<id<<std::endl;
                        }
                      };
                      
                      struct dog : animal
                      {
                        dog() : animal(1) {}
                        void applySpecificBehaviour() {
                            animal::applySpecificBehaviour<dog>();
                            //std::cout<<"dog beheviour of instance : "<<id<<std::endl;
                        }
                        void onSepecifiedBehaviourCalled() {
                            //std::cout<<"dog on specified behaviour : "<<id<<std::endl;
                        }
                      };
                      
                      struct bird : animal
                      {
                        bird() : animal(2) {}
                        void applySpecificBehaviour() {
                            animal::applySpecificBehaviour<bird>();
                            //std::cout<<"bird beheviour of instance : "<<id<<std::endl;
                        }
                        void onSepecifiedBehaviourCalled() {
                            //std::cout<<"bird on specified behaviour : "<<id<<std::endl;
                        }
                      };
                      struct vanimal {
                          unsigned int id;
                          unsigned int typeId;
                          static unsigned int nbInstances;
                          vanimal(unsigned int typeId) {
                              id = nbInstances;
                              nbInstances = id;
                              nbInstances++;
                              this->typeId = typeId;
                          }
                          virtual void applySpecificBehaviour(int i) {
                            //std::cout<<"base beheviour of instance : "<<id<<std::endl;
                            onSepecifiedBehaviourCalled();
                          }
                          virtual void onSepecifiedBehaviourCalled() {
                      
                          }
                      };
                      unsigned int vanimal::nbInstances = 0;
                      struct vcat : vanimal
                      {
                        vcat() : vanimal(0) {}
                        void applySpecificBehaviour(int i) {
                            vanimal::applySpecificBehaviour(i);
                            //std::cout<<"cat beheviour of instance : "<<id<<std::endl;
                        }
                        void onSepecifiedBehaviourCalled() {
                            //std::cout<<"cat on specified behaviour : "<<id<<std::endl;
                        }
                      };
                      
                      struct vdog : vanimal
                      {
                        vdog() : vanimal(1) {}
                        void applySpecificBehaviour(int  i) {
                            vanimal::applySpecificBehaviour(i);
                            //std::cout<<"dog beheviour of instance : "<<id<<std::endl;
                        }
                        void onSepecifiedBehaviourCalled() {
                            //std::cout<<"dog on specified behaviour : "<<id<<std::endl;
                        }
                      };
                      
                      struct vbird : vanimal
                      {
                        vbird() : vanimal(2) {}
                        void applySpecificBehaviour(int i) {
                            vanimal::applySpecificBehaviour(i);
                            //std::cout<<"bird beheviour of instance : "<<id<<std::endl;
                        }
                        void onSepecifiedBehaviourCalled() {
                            //std::cout<<"bird on specified behaviour : "<<id<<std::endl;
                        }
                      };
                      struct fanimal {
                          unsigned int id;
                          unsigned int typeId;
                          static unsigned int nbInstances;
                          fanimal(unsigned int typeId) {
                              id = nbInstances;
                              nbInstances = id;
                              nbInstances++;
                              this->typeId = typeId;
                          }
                          template <typename Archive>
                          void vtapplySpecificBehaviour(Archive ar) {
                            //std::cout<<"base beheviour of instance : "<<id<<std::endl;
                            onSepecifiedBehaviourCalled();
                          }
                          void onSepecifiedBehaviourCalled() {
                      
                          }
                          virtual ~fanimal() = default;
                      };
                      unsigned int fanimal::nbInstances = 0;
                      struct fcat : fanimal
                      {
                        fcat() : fanimal(0) {}
                        template <typename Archive>
                        void vtapplySpecificBehaviour(Archive ar) {
                            fanimal::vtapplySpecificBehaviour(ar);
                            //std::cout<<"cat beheviour of instance : "<<id<<std::endl;
                        }
                        void onSepecifiedBehaviourCalled() {
                            //std::cout<<"cat on specified behaviour : "<<id<<std::endl;
                        }
                      };
                      
                      struct fdog : fanimal
                      {
                        fdog() : fanimal(1) {}
                        template <typename Archive>
                        void vtapplySpecificBehaviour(Archive ar) {
                            fanimal::vtapplySpecificBehaviour(ar);
                            //std::cout<<"dog beheviour of instance : "<<id<<std::endl;
                        }
                        void onSepecifiedBehaviourCalled() {
                            //std::cout<<"dog on specified behaviour : "<<id<<std::endl;
                        }
                      };
                      
                      struct fbird : fanimal
                      {
                        fbird() : fanimal(2) {}
                        template <typename Archive>
                        void vtapplySpecificBehaviour(Archive ar) {
                            fanimal::vtapplySpecificBehaviour(ar);
                            //std::cout<<"bird beheviour of instance : "<<id<<std::endl;
                        }
                        void onSepecifiedBehaviourCalled() {
                            //std::cout<<"bird on specified behaviour : "<<id<<std::endl;
                        }
                      };
                      template <class B, size_t I, class... Derived>
                      struct Holder {
                      
                      };
                      template <class B, size_t I, class Head, class... Tail>
                      struct Holder<B, I, Head, Tail...> : Holder<B, I+1, Tail...> {
                          std::vector<B*> entities;
                          void add(B* entity) {
                              if (entity->typeId == I)
                                  entities.push_back(entity);
                              Holder<B, I+1, Tail...>::add(entity);
                          }
                          void applyBehaviours() {
                              for (unsigned int i = 0; i < entities.size(); i++) {
                                  static_cast<Head*>(entities[i])->applySpecificBehaviour();
                              }
                              Holder<B, I+1, Tail...>::applyBehaviours();
                          }
                      };
                      template <class B, size_t I, class Head>
                      struct Holder<B, I, Head> {
                          std::vector<B*> entities;
                          void add(B* entity) {
                              if (entity->typeId == I)
                                  entities.push_back(entity);
                          }
                          void applyBehaviours() {
                              for (unsigned int i = 0; i < entities.size(); i++) {
                                  static_cast<Head*>(entities[i])->applySpecificBehaviour();
                              }
                          }
                      };
                      template <class B>
                      struct VHolder {
                          std::vector<B*> entities;
                          void add(B* entity) {
                              entities.push_back(entity);
                          }
                          void applyBehaviours() {
                              for (unsigned int i = 0; i < entities.size(); i++) {
                                  entities[i]->applySpecificBehaviour(0);
                              }
                          }
                      };
                      int main(int argc, char* argv[])
                      {
                          /*using tuple_t = sort<LessPlaceceholder, unique<copy_if<is_placeholder,std::tuple<placeholder<3, int>, int, placeholder<2, int>, float, placeholder<1, int>, char, placeholder<0, int>>>::f/*>::f>::f;*/
                          /*using late_params_t = lift<tuple_t, LateParameters,std::make_index_sequence<std::tuple_size<tuple_t>()-0>>::f;
                          tuple_t tp = std::make_tuple(placeholder<3, int>(),placeholder<2, int>(),placeholder<1, int>(),placeholder<0, int>());
                          return 0;*/
                          cat c1;
                          dog d1;
                          bird b1;
                          cat c2;
                          dog d2;
                          bird b2;
                          Holder<animal, 0, cat, dog, bird> holder;
                          holder.add(&c1);
                          holder.add(&d1);
                          holder.add(&b1);
                          holder.add(&c2);
                          holder.add(&d2);
                          holder.add(&b2);
                          sf::Clock clk;
                          holder.applyBehaviours();
                          std::cout<<"CRTP time : "<<clk.getElapsedTime().asMicroseconds()<<std::endl;
                          vcat vc1;
                          vdog vd1;
                          vbird vb1;
                          vcat vc2;
                          vdog vd2;
                          vbird vb2;
                          VHolder<vanimal> vholder;
                          vholder.add(&vc1);
                          vholder.add(&vd1);
                          vholder.add(&vb1);
                          vholder.add(&vc2);
                          vholder.add(&vd2);
                          vholder.add(&vb2);
                          clk.restart();
                          vholder.applyBehaviours();
                          std::cout<<"Virtual function time : "<<clk.getElapsedTime().asMicroseconds()<<std::endl;
                          fcat fc1;
                          fdog fd1;
                          fbird fb1;
                          fcat fc2;
                          fdog fd2;
                          fbird fb2;
                          fanimal* a1 = &fc1;
                          fanimal* a2 = &fd1;
                          fanimal* a3 = &fb1;
                          fanimal* a4 = &fc2;
                          fanimal* a5 = &fd2;
                          fanimal* a6 = &fb2;
                          REGISTER_FUNC(BehaviourCat, applySpecificBehaviour, OTextArchive, fanimal, fcat, (int), a1, 0)
                          REGISTER_FUNC(BehaviourDog, applySpecificBehaviour, OTextArchive, fanimal, fdog, (int), a2, 0)
                          REGISTER_FUNC(BehaviourBird, applySpecificBehaviour,OTextArchive, fanimal, fbird, (int), a3, 0)
                          clk.restart();
                          CALL_FUNC(applySpecificBehaviour, OTextArchive,fanimal, fcat, a1, 0)
                          CALL_FUNC(applySpecificBehaviour, OTextArchive,fanimal, fdog, a2, 0)
                          CALL_FUNC(applySpecificBehaviour, OTextArchive,fanimal, fbird, a3, 0)
                          CALL_FUNC(applySpecificBehaviour, OTextArchive,fanimal, fcat, a4, 0)
                          CALL_FUNC(applySpecificBehaviour, OTextArchive,fanimal, fdog, a5, 0)
                          CALL_FUNC(applySpecificBehaviour, OTextArchive,fanimal, fbird, a6, 0)
                          std::cout<<"factory function time : "<<clk.getElapsedTime().asMicroseconds()<<std::endl;
                      return 0;
                      }



                      -
                      Edité par OmbreNoire 3 juillet 2021 à 10:41:16

                      • Partager sur Facebook
                      • Partager sur Twitter
                        3 juillet 2021 à 11:01:47

                        Que constate t-on dans ton code:

                        • Que la version CRTP fait en tout et pour tout mov rbx, rax (c'est à dire rien)
                        • Que la version avec virtuelle fait bien des appels virtuelles non dévirtualisées
                        • Que la factory ne compile pas

                        https://godbolt.org/z/fj555x7P8

                        Un petit passage dans quick-bench donne un résultat totalement inversé par rapport à ce que tu dis: https://quick-bench.com/q/c5l442vA4CoGi_PBCFP2ibm0wts

                        C'est bien de faire des benchs, encore faudrait-il en faire des corrects qui mesurent réellement ce qui se passe. Prendre comme mesure 1 seul et unique appel n'est absolument pas fiable. Et comme l'a indiqué gbdivers, ton test n'est absolument pas réaliste.

                        • Partager sur Facebook
                        • Partager sur Twitter
                          3 juillet 2021 à 12:31:52

                          jo_link_noir a écrit:

                          Que constate t-on dans ton code:

                          • Que la version CRTP fait en tout et pour tout mov rbx, rax (c'est à dire rien)
                          • Que la version avec virtuelle fait bien des appels virtuelles non dévirtualisées
                          • Que la factory ne compile pas

                          https://godbolt.org/z/fj555x7P8

                          Un petit passage dans quick-bench donne un résultat totalement inversé par rapport à ce que tu dis: https://quick-bench.com/q/c5l442vA4CoGi_PBCFP2ibm0wts

                          C'est bien de faire des benchs, encore faudrait-il en faire des corrects qui mesurent réellement ce qui se passe. Prendre comme mesure 1 seul et unique appel n'est absolument pas fiable. Et comme l'a indiqué gbdivers, ton test n'est absolument pas réaliste.


                          On dirait que sf::Clock de la SFML n'est pas très précise, la preuve en est que parfois les fonctions virtuelles prennent plus de temps à être appelée que celles de la factory et inversément.

                          Et je me disais bien que c'était bizarre que le polymorphisme statique prenait plus de temps que le polymorphisme dynamique.

                          Ce site à l'air bien, je ne le connaissais pas.

                          Bon hé bien alors d'après ton site qui est plus fiable j'en suis sûr maintenant, le polymorphisme statique est vachement plus rapide!

                          Donc voilà ce que je dois faire au niveau du design pour optimiser. :)

                          Le code avec la factory ne compilera pas parce que je ne vous ai pas mit le code de la factory et du delegate  avant le main qui sont dans le framework, mais je ne l'utilise que pour la sauvegarde et la lecture des objets à comportements polymorphiques.

                          Je crois que je vais passer le sujet en résolu. :)

                          • Partager sur Facebook
                          • Partager sur Twitter
                            4 juillet 2021 à 2:56:45

                            Tu t'arrêtes trop tôt dans tes tests.

                            Un design n'est jamais meilleur dans l'absolue. Il faut regarder les contraintes d'utilisation, utiliser différents paramètres pour tester, voir en détail ce qui est bien et ce qui ne l'est pas dans chaque approche, etc.

                            Par exemple, une chose que tu peux faire et que je t'ai déjà indiqué, c'est de ne pas créer tes objets sur la Pile, mais utiliser une factory. Par exemple, tu veux modifier ton code de tes tests de la façon suivante :

                            std::random_device rd;
                            std::mt19937 gen(rd()); 
                            std::uniform_int_distribution<> distrib(1, 3); // 3 types
                            
                            VHolder<vanimal> vholder;
                            for (int i = 0; i < 100; ++i)
                            {
                                const int n = distrib(gen);
                                switch (n)
                                {
                                    case 1: vholder.add(new vcat); break;
                                    case 2: vholder.add(new vdog); break;
                                    case 3: vholder.add(new vbird); break;
                                }
                            }

                            Idem pour l'autre approche.

                            Cela permet de tester le comportement pour différentes quantité d'objets. Par exemple :

                            Cela permet de pouvoir dire que plus tu as des éléments, plus l'approche par héritage sera moins bonne.

                            ---------------------------------------------------------------------------------------------------

                            Pour autant, cela ne veut pas dire que cette approche n'est pas sans défaut. D’ailleurs, elle existait avant la POO et les fonctions virtuelles. Si on a utilisé la POO, ce n'est pas pour rien. Le principe de ce que tu fais, c'est juste d'avoir un tableau pour chaque type, comme ça il n'y a plus besoin de résoudre dynamique l'appel de fonction.

                            Mais il y a des défauts :

                            1. tu dois ajouter du code dans chaque classe, ce qui est plus lourd a écrire que des fonctions virtuelles. Tu as fait le test avec une seule fonction, mais si tu en as 10 ou 100, ça devient très lourd.

                            2. tu consommes plus de mémoire pour chaque type. Si tu as par exemple un jeu dans lequel tu as 1000 types et moins de 10 objets de chaque type, le coût mémoire sera plus important qu'avec des fonctions virtuelles. Et les performances seront probablement mauvaises.

                            3. tu dois écrire chaque type à la main dans le holder. Si tu en as 3, ça va. Si tu en as 1000, c'est ingérable.

                            -----------------------------------------------------------------------------------------------------

                            C'est pour ça que ça n'a pas de sens de dire avec juste ce test rapide que cette approche est meilleure. Tu ne peux pas tester une approche qui sera utilisée dans un MMORPG en faisait un test sur 3 types, 6 objets sur la Pile et 1 fonction. Ton test n'est juste pas un bon test pour ce que tu veux faire comme jeu.

                            C'est pour cela qu'on conseille en général de faire le jeu en même temps que le framework. Ou mieux, faire plusieurs jeux, puis refactoriser leurs codes pour en faire un framework. Ca permet de prendre des décisions de design sur du concret.

                            • Partager sur Facebook
                            • Partager sur Twitter
                              4 juillet 2021 à 8:57:53

                              gbdivers a écrit:

                              Tu t'arrêtes trop tôt dans tes tests.

                              Un design n'est jamais meilleur dans l'absolue. Il faut regarder les contraintes d'utilisation, utiliser différents paramètres pour tester, voir en détail ce qui est bien et ce qui ne l'est pas dans chaque approche, etc.

                              Par exemple, une chose que tu peux faire et que je t'ai déjà indiqué, c'est de ne pas créer tes objets sur la Pile, mais utiliser une factory. Par exemple, tu veux modifier ton code de tes tests de la façon suivante :

                              std::random_device rd;
                              std::mt19937 gen(rd()); 
                              std::uniform_int_distribution<> distrib(1, 3); // 3 types
                              
                              VHolder<vanimal> vholder;
                              for (int i = 0; i < 100; ++i)
                              {
                                  const int n = distrib(gen);
                                  switch (n)
                                  {
                                      case 1: vholder.add(new vcat); break;
                                      case 2: vholder.add(new vdog); break;
                                      case 3: vholder.add(new vbird); break;
                                  }
                              }

                              Idem pour l'autre approche.

                              Cela permet de tester le comportement pour différentes quantité d'objets. Par exemple :

                              Cela permet de pouvoir dire que plus tu as des éléments, plus l'approche par héritage sera moins bonne.

                              ---------------------------------------------------------------------------------------------------

                              Pour autant, cela ne veut pas dire que cette approche n'est pas sans défaut. D’ailleurs, elle existait avant la POO et les fonctions virtuelles. Si on a utilisé la POO, ce n'est pas pour rien. Le principe de ce que tu fais, c'est juste d'avoir un tableau pour chaque type, comme ça il n'y a plus besoin de résoudre dynamique l'appel de fonction.

                              Mais il y a des défauts :

                              1. tu dois ajouter du code dans chaque classe, ce qui est plus lourd a écrire que des fonctions virtuelles. Tu as fait le test avec une seule fonction, mais si tu en as 10 ou 100, ça devient très lourd.

                              2. tu consommes plus de mémoire pour chaque type. Si tu as par exemple un jeu dans lequel tu as 1000 types et moins de 10 objets de chaque type, le coût mémoire sera plus important qu'avec des fonctions virtuelles. Et les performances seront probablement mauvaises.

                              3. tu dois écrire chaque type à la main dans le holder. Si tu en as 3, ça va. Si tu en as 1000, c'est ingérable.

                              -----------------------------------------------------------------------------------------------------

                              C'est pour ça que ça n'a pas de sens de dire avec juste ce test rapide que cette approche est meilleure. Tu ne peux pas tester une approche qui sera utilisée dans un MMORPG en faisait un test sur 3 types, 6 objets sur la Pile et 1 fonction. Ton test n'est juste pas un bon test pour ce que tu veux faire comme jeu.

                              C'est pour cela qu'on conseille en général de faire le jeu en même temps que le framework. Ou mieux, faire plusieurs jeux, puis refactoriser leurs codes pour en faire un framework. Ca permet de prendre des décisions de design sur du concret.


                              Pour le fait d'utiliser une factory pour créer les objets je suis d'accord, je suis aussi d'accord pour dire que ça va être lourd d'implémenter ce design pour les raisons que tu as cités et je suis sûr que c'est pour ça qu'on à inventé les fonctions virtuelles.

                              Avec ça, ça va me faire un vector pour 10 objets de 1000 types contenant 10*1000 entités, tandis que avec l'autre design 1000 vectors de 10 entités (ça va consommer plus de mémoire) en plus il faut rechercher, dans quel type de vector stocker l'entité et je ne pense pas que ça soit très performant si il y a beaucoup de types.

                              Passer tout les types au holder serait trop lourd, et je dois en effet, dans chaque classe, rajouter du code. 

                              Et même si on m'a déjà dit plusieurs fois que mon design n'est pas bon, il n'est sans doute pas sans défauts mais, je ne pense pas qu'un autre design le soi non plus.

                              Je faisais ça avant de créer le framework, créer plusieurs projets et puis j'ai commencé à créer le framework, je me suis rendu compte que les fonctions virtuelles devenaient indispensable dans les plus gros projets ou il y a beaucoup de types d'entités différent et surtout lorsqu'on ne connaît pas les types des objets à la compilation.

                              Pourtant sur un autre sujet on a dit que l'héritage c'était pas bon pour les gros projet mais que ça passait pour les petits projets, mais je pense que c'est plutôt l'inverse, en effet, quand on ne connait pas les types en compilation et qu'il y a 1000 types d'entités différentes à gérer, ça devient indispensable d'utiliser l'héritage, les fonctions virtuelles et allouer les entités dynamiquement. (pour ne pas à avoir à déclarer 1000 types d'entités pour les passer par référence parce que lorsque l'on quitte la fonction de création, l'entité est détruite à la fin du bloc si on n'utilise pas l'allocation dynamique il faut donc soit les déclarer en temps que variable membre d'une classe soit utiliser l'allocation dynamique.)

                              Finalement je ne pense pas que mon design soit si mauvais que ça.  

                              -
                              Edité par OmbreNoire 4 juillet 2021 à 9:07:39

                              • Partager sur Facebook
                              • Partager sur Twitter
                                4 juillet 2021 à 9:38:02

                                OmbreNoire a écrit:

                                je ne pense pas que ça soit très performant si il y a beaucoup de types.

                                Ne pense pas :) Testes, testes et testes encore.

                                OmbreNoire a écrit:

                                Finalement je ne pense pas que mon design soit si mauvais que ça.  

                                Ne pense pas :) Si ton but est que ton framework soit utilisé par d'autres, alors il n'y a qu'un test possible : le nombre de personnes qui utilisent ton framework. Tant que tu n'as pas du monde qui utilise ton framework et que personne ne te fait de retour, tu ne peux pas savoir si ton design est mauvais ou pas.

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  4 juillet 2021 à 10:11:00

                                  Faudrait que je test les performances de mes différentes fonctions, cependant sf::clock n'est pas fiable, il y a des sites pour faire des benchmark qui sont plus fiable cependant il faut tout mettre dans la fonction main.
                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    4 juillet 2021 à 14:28:55

                                    Bonjour,

                                    Lorsque tu dois utiliser quelque chose qui est présent dans une bibliothèque , mais aussi la STL, il vaut mieux utiliser celui de la STL. Je pense notamment à l' entête <chrono> de la STL par rapport à sf::clock. :)

                                    Edit: j'ai du mal à croire que sf::clock ne soit pas fiable tu est sur de ce que tu avances ? C'est peut- être plus en rapport avec ton code je pense.

                                    -
                                    Edité par Warren79 4 juillet 2021 à 14:30:05

                                    • Partager sur Facebook
                                    • Partager sur Twitter

                                    Mon site web de jeux SDL2 entre autres : https://www.ant01.fr

                                      4 juillet 2021 à 18:20:03

                                      Salut! Sinon j'avais pensé à ceci pour ne pas encombrer les classes et ne passé qu'un seul type à toutes les fonctions au cas ou j'aurai 1000 types d'entités différentes à gérer.

                                      struct class Entity {
                                           template<typename D>
                                           void applyBehaviour() {
                                                static_cast<D*>(this)->applySpecificBehaviour();
                                           }
                                      };
                                      

                                      Mais le problème c'est que j'utilise une structure arborescente et de ce fait, je ne connais pas le type des entités enfants ni des entités parents donc si j'ai ceci :

                                      struct Entity {
                                          template <typename D>
                                          Entity* getRootEntity() {
                                             return static_cast<D*>(this)->getRootEntity();       
                                         }
                                      };
                                      struct SceneNode : Entity {
                                         Entity* parent;   
                                         Entity* getRootEntity() {
                                            if (parent != nullptr) {
                                                 return static_cast<???>(parent)->getRootEntity();      
                                            }
                                            return *this;
                                         }
                                      };
                                      

                                      On pourrait être tenté de static_cast en SceneNode mais il n'y a aucune garantie que l'entité parent est un nœud. (Et même chose pour les entités enfants)

                                      Je ne connais donc pas le type en compilation donc obligation d'utiliser des fonctions virtuelles.

                                      Et comme j'ai beaucoup de types qui ont une structure arborescente dans le framework (les composants guis, les nœuds, les volumes de collision), je ne peux pas m'en passer.

                                      Ou alors je dois émettre des contraintes tels que les scenes nodes ne peuvent avoir pour enfants et pour parent que des entités dérivées de la classe SceneNode et aucune classe ne peut redéfinir les méthodes de SceneNode.

                                      EDIT : mais bon ça ne résous pas le problème si je ne connais pas le type de l'entité en compilation comme par exemple si j'ai un vector d'entités et que je dois appeler une fonction sur toutes les entité du vecteur dont je ne connais pas les types en compilation.

                                      -
                                      Edité par OmbreNoire 4 juillet 2021 à 18:59:39

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        5 juillet 2021 à 0:11:25

                                        Salut,

                                        OmbreNoire a écrit:

                                        Salut! Sinon j'avais pensé à ceci pour ne pas encombrer les classes et ne passé qu'un seul type à toutes les fonctions au cas ou j'aurai 1000 types d'entités différentes à gérer.

                                        struct class Entity {
                                             template<typename D>
                                             void applyBehaviour() {
                                                  static_cast<D*>(this)->applySpecificBehaviour();
                                             }
                                        };
                                        

                                        Mais le problème c'est que j'utilise une structure arborescente et de ce fait, je ne connais pas le type des entités enfants ni des entités parents donc si j'ai ceci :

                                        struct Entity {
                                            template <typename D>
                                            Entity* getRootEntity() {
                                               return static_cast<D*>(this)->getRootEntity();       
                                           }
                                        };
                                        struct SceneNode : Entity {
                                           Entity* parent;   
                                           Entity* getRootEntity() {
                                              if (parent != nullptr) {
                                                   return static_cast<???>(parent)->getRootEntity();      
                                              }
                                              return *this;
                                           }
                                        };
                                        

                                        On pourrait être tenté de static_cast en SceneNode mais il n'y a aucune garantie que l'entité parent est un nœud. (Et même chose pour les entités enfants)

                                        Je ne connais donc pas le type en compilation donc obligation d'utiliser des fonctions virtuelles.

                                        Et comme j'ai beaucoup de types qui ont une structure arborescente dans le framework (les composants guis, les nœuds, les volumes de collision), je ne peux pas m'en passer.

                                        Ou alors je dois émettre des contraintes tels que les scenes nodes ne peuvent avoir pour enfants et pour parent que des entités dérivées de la classe SceneNode et aucune classe ne peut redéfinir les méthodes de SceneNode.

                                        EDIT : mais bon ça ne résous pas le problème si je ne connais pas le type de l'entité en compilation comme par exemple si j'ai un vector d'entités et que je dois appeler une fonction sur toutes les entité du vecteur dont je ne connais pas les types en compilation.

                                        -
                                        Edité par OmbreNoire il y a environ 4 heures

                                        Attend, laisse moi deviner le code qui se trouve sans doute ailleurs...

                                        Tel que tu es parti, tu va avoir quelque chose comme

                                        class Dog : public Entity{
                                        public:
                                            void applySpecificBehaviour() const{
                                                std::cout <<"ouah\n";
                                            }
                                        };

                                        et encore un peu plus loin tu va avoir quelque chose comme

                                        class Cat: public Entity{
                                        public:
                                            void applySpecificBehaviour() const{
                                                std::cout <<"Miaou\n";
                                            }
                                        };

                                        Tout cela pour te retrouver, au fil de ta boucle de jeu, avec un magnifique

                                        for(auto * ent : allEntities){ // on parcours toutes les entités
                                            ent->applySpecificBehaviour<cat>();
                                        }
                                        Ca va être sympa quand ton chien, ton pingouin ou ton ours polaire se mettront à avoir le comportement d'un chat :p
                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                        Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                                          5 juillet 2021 à 11:39:33

                                          Tu n'as pas compris le principe de l'héritage on dirait, c'est à dire, séparer les comportements similaires des comportements différents, ce que tu dis n'est donc pas possible.
                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            7 juillet 2021 à 1:47:36

                                            on dirait que c'est toi qui n'a pas forcément compris tout ce que l'héritage implique...

                                            car, quand ta classe Entity prend la forme de

                                            struct class Entity {
                                                 template<typename D>
                                                 void applyBehaviour() {
                                                      static_cast<D*>(this)->applySpecificBehaviour();
                                                 }
                                            };

                                            (je ne fais que copier ton propre code, là, nous sommes bien d'accord?)

                                            cela signifie que je peux parfaitement avoir, plus loin, un code qui ressemble à ceci:

                                            class Dog : public Entity{
                                            public:
                                                void applySpecificBehaviour() const{
                                                    std::cout <<"ouah\n";
                                                }
                                            };
                                            class Cat: public Entity{
                                            public:
                                                void applySpecificBehaviour() const{
                                                    std::cout <<"miaou\n";
                                            /* et bien plus loin */
                                            for(auto * ent : allEntities){ // on parcours toutes les entités
                                                ent->applySpecificBehaviour();
                                            }
                                            
                                            
                                            

                                            (et là, c'est mon code que je copie) Car, faut-il te rappeler que la seule différence qu'il y ait entre le mot clé struct et le mot clé class consiste en l'accessibilité "par défaut" (comprend: qui est appliquée quand l'on ne fait rien pour la changer), que ce soit au contenu de l'agrégat ou à son héritage?

                                            Or, le principe de l'héritage, c'est que tout ce que l'on retrouve dans la structure Entity va également se retrouver dans les structures ou les classes qui en héritent (Cat et Dog, dans mon exemple), y compris (et surtout) les fonctions membres publiques, dont fait partie cette fameuse fonction template void applyBehaviour() const.

                                            Le fait que ce soit une fonction template ne change rien à l'histoire: son nom "pleinement qualifié" restera toujours void Entity::applyBehaviour() const et nous pourrons donc forcément l'appeler depuis n'importe quel pointeur ou référence sur un élément de type Entity, et, même si le type de substitution utilisé ne correspond pas au type réel (dynamique) de l'entité.

                                            Et ca, ca va nous amener à un sérieux problème, car static_cast <> ne peut vérifier que le transtypage réussit qu'à la compilation (de manière ... "statique", à comprendre dans le sens de  "immobile" ici, comme le nom l'indique si bien).

                                            Il ne va donc râler que si on essaye de transtyper notre (pointeur ou référence sur notre) objet connu "comme étant une entité" dans un type qui ne dérive pas de Entity.

                                            Or, dans le cas présent, Cat et Dog dérivent effectivement de Entity et donc, comme la vérification n'a lieu (je me répète là) qu'à la compilation, tout ne pourra paraitre que parfaitement normal à notre compilateur, et il ne va donc pas se mettre à râler.

                                            Pire, il ne va meme pas se fendre d'un avertissement, car il va considérer -- de base -- que si tu utilise explicitement static_cast, c'est que tu veux clairement que cette conversion ait lieu, que ce n'est pas une conversion qui risque d'arriver parce que tes doigts se sont quelque peu emmêlés sur ton clavier.

                                            Et c'est justement parce que la vérification n'a lieu (j'ai vraiment l'impression d'être un vieux vinyl griffé là) qu'à la compilation, que, si tu as demandé le type Cat à la compilation, le compilateur aura fait en sorte que tu aies un Cat à l'exécution.

                                            Si bien quand tu vas parcourir toutes tes entités avec un code proche de

                                            ent->applySpecificBehaviour<cat>();

                                            hé bien tu te retrouveras dans une situation dans laquelle toutes tes entités, passeront pour être un chat à l'exécution, et ce, qu'il s'agisse d' ours, de pingouins, d' arbres, de chiens ou de quoi que ce soit d'autre.

                                            Et, dans le pire des cas, toutes tes entités se mettront donc toutes à miauler :euh:

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                            Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                                              7 juillet 2021 à 2:22:16

                                              Salut! J'ai réglé le problème avec le polymorphisme statique, de ce fait, on ne pourra plus appeler n'importe quelle fonctions sur la classe Entity.

                                              J'ai posté la solution dans un autre sujet, il ne faudra plus non plus passer 1000 types en paramètres template en compilation si il y a 1000 types.

                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                16 juillet 2021 à 11:32:33

                                                Bonjour, quickbench me retourne une erreur lorsque le code est trop long, quel librairie doit je utiliser pour faire des tests fiables ?
                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  16 juillet 2021 à 16:32:32

                                                  quickbench est juste une version online de la lib benchmark https://github.com/google/benchmark . Regarde la doc et installe toi une version locale.

                                                  -
                                                  Edité par gbdivers 16 juillet 2021 à 16:33:16

                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    16 juillet 2021 à 19:34:17

                                                    D'accord, je dois encore installer cette lib, mais, en ayant tester avec sf::Clock, j'obtient à peu de chose près les mêmes résultats que sur quickbench il y a juste quelques incohérences parfois ou lorsque je rajoute des élements ça s'exécute plus vite que lorsque je n'en rajoute pas.

                                                    Enfin soit, d'après sf::Clock, un système ECS est moins rapide que les fonctions virtuelles :

                                                    struct vtransformable {
                                                        void move(float x, float y, float z) {
                                                          onMove(x, y, z);
                                                        }
                                                        virtual void onMove(float x, float y, float z) {}
                                                    };
                                                    struct ventity : vtransformable {
                                                      unsigned int id;
                                                      static unsigned int nbEntities;
                                                      void addChild(ventity* child) {
                                                          children.push_back(child);
                                                      }
                                                      virtual void onMove(float x, float y, float z) {
                                                          for (unsigned int i = 0; i < children.size(); i++) {
                                                              children[i]->move(x, y, z);
                                                          }
                                                      }
                                                      std::vector<ventity*> children;
                                                    };
                                                    struct vsphere : ventity
                                                    {
                                                      vsphere() : ventity() {
                                                          id = nbEntities;
                                                          nbEntities++;
                                                      }
                                                      void onMove(float x, float y, float z) {
                                                          ventity::onMove(x, y, z);
                                                          //std::cout<<"update datas on sphere instance : "<<id<<" moved"<<std::endl;
                                                      }
                                                    
                                                    };
                                                    unsigned int ventity::nbEntities = 0;
                                                    struct vrectangle : ventity
                                                    {
                                                      vrectangle() : ventity() {
                                                    
                                                      }
                                                      void onMove(float x, float y, float z) {
                                                          ventity::onMove(x, y, z);
                                                          //std::cout<<"update datas on rectangle instance : "<<id<<" moved"<<std::endl;
                                                      }
                                                    };
                                                    struct vconvexshape : ventity
                                                    {
                                                        vconvexshape() : ventity() {
                                                    
                                                        }
                                                        void onMove(float x, float y, float z) {
                                                            ventity::onMove(x, y, z);
                                                            //std::cout<<"update datas on convex shape instance : "<<id<<" shape"<<std::endl;
                                                        }
                                                    };
                                                    template <typename D>
                                                    struct appli {
                                                        bool running = false;
                                                        bool isRunning () {
                                                            return running;
                                                        }
                                                        template<typename EntityManagerArray>
                                                        void update(EntityManagerArray em) {
                                                    
                                                        }
                                                        template<typename EntityManagerArray>
                                                        void init(EntityManagerArray& em) {
                                                            static_cast<D*>(this)->onInit(em);
                                                        }
                                                        template<typename EntityManagerArray>
                                                        void exec(EntityManagerArray& ema) {
                                                            if (!running) {
                                                                running = true;
                                                                init(ema);
                                                            }
                                                            while(running) {
                                                    
                                                                static_cast<D*>(this)->onExec(ema);
                                                            }
                                                        }
                                                    
                                                    };
                                                    struct myappli : appli<myappli> {
                                                        sphere sph;
                                                        rectangle rect, rect2;
                                                        convexshape shape, shape2;
                                                        myappli() : appli<myappli>() {
                                                    
                                                        }
                                                        template<typename ManagerArrayEntity>
                                                        void onInit(ManagerArrayEntity& ema) {
                                                    
                                                            ::EntityManager<IEntityManager, entity> ems;
                                                            ::EntityManager<IEntityManager, entity> childrenem;
                                                            auto children0em = sph.addChild(childrenem, &rect2);
                                                            auto children1em = sph.addChild(children0em, &shape2);
                                                            auto em = ems.add(&shape).add(&rect).add(&sph);
                                                            auto emas = ema.add(&em).add(&children1em);
                                                            sph.childrenContainerIdx = children1em.containerIdx;
                                                            sph.childrenContainerType = children1em.type;
                                                            exec(emas);
                                                        }
                                                        template<typename ManagerEntityArray>
                                                        void onExec(ManagerEntityArray& ema) {
                                                            bool retb;
                                                            auto tp = std::make_tuple(1.1f, 2.2f, 3.3f);
                                                            ema.apply(0, 0, sph.type, sph.containerIdx, "move", tp, retb);
                                                            float retf;
                                                            auto tp2 = std::make_tuple();
                                                            ema.apply(0, 0, sph.type, sph.containerIdx, "f", tp2, retf);
                                                            ema.apply(0, 0, rect.type, rect.containerIdx, "move", tp, retb);
                                                            ema.apply(0, 0, shape.type, shape.containerIdx, "move", tp, retb);
                                                    
                                                        }
                                                    };
                                                    #include <set>
                                                    #include <bitset>
                                                    #include <map>
                                                    #include <climits>
                                                    #include <cassert>
                                                    #include <stdexcept>
                                                    
                                                    using EntityId = size_t;
                                                    
                                                    using ActiveEntities = std::set<EntityId>;
                                                    
                                                    ActiveEntities & getActiveEntities();
                                                    
                                                    EntityId createEntity();
                                                    
                                                    void destroyEntity(EntityId);
                                                    using ComponentTypeId = size_t;
                                                    using ComponentId = size_t;
                                                    
                                                    static constexpr ComponentTypeId MAX_COMPOSANT_TYPE = sizeof(size_t) * CHAR_BIT / 2;
                                                    
                                                    struct Agregate{};
                                                    
                                                    struct Flag{};
                                                    
                                                    namespace Details{
                                                    
                                                        template <typename Tag>
                                                        struct UniqueIdHolder{
                                                            static_assert(std::is_same<Tag, Agregate>::value ||
                                                                          std::is_same<Tag, Flag>::value,
                                                            "les seuls tags admis ici sont Agregate et Flag");
                                                            template<typename T>
                                                            static size_t componentTypeId(){
                                                                static size_t id =nextTypeId();
                                                                return id;
                                                            }
                                                            template <typename T>
                                                            static size_t componentId(){
                                                                static_assert(std::is_same<Tag, Agregate>::value,
                                                                "les seuls composant a avoir un id unique devraient être les agregats");
                                                                static size_t nextId;
                                                                auto id = nextId;
                                                                ++nextId;
                                                                return id;
                                                            }
                                                         private:
                                                    
                                                            static size_t nextTypeId(){
                                                                static size_t nextId{0};
                                                                assert(nextId < MAX_COMPOSANT_TYPE &&
                                                                       "Trop de types de composants ont ete definis");
                                                                auto id = nextId;
                                                                ++ nextId;
                                                                return id;
                                                            }
                                                            template <typename T>
                                                            static size_t nextInstanceId(){
                                                                static size_t nextId{0};
                                                                auto id = nextId;
                                                                ++ nextId;
                                                                return id;
                                                            }
                                                        };
                                                    } // namespace Details
                                                    
                                                    template <typename T, typename Tag>
                                                    struct ComponentType{
                                                        using type = T;
                                                        using tag = Tag;
                                                        static_assert(std::is_same<Tag,Agregate>::value,
                                                        "le tag devrait etre de type Agregate uniquement ici");
                                                        using value_t = T;
                                                        static ComponentTypeId typeId(){
                                                            return Details::UniqueIdHolder<Tag>::
                                                                    template componentTypeId<T>();
                                                        }
                                                        template <typename ... Args>
                                                        ComponentType(Args ... args): data {args ...}{}
                                                        size_t id;
                                                        value_t data;
                                                    };
                                                    template <typename T>
                                                    struct ComponentType<T, Flag>{
                                                        static ComponentTypeId typeId(){
                                                            return Details::UniqueIdHolder<Flag>::
                                                                    template componentTypeId<T>();
                                                        }
                                                    };
                                                    
                                                    template <typename T>
                                                    using ActiveAgregates = std::map<ComponentId, ComponentType<T, Agregate>>;
                                                    template <typename T>
                                                    ActiveAgregates<T> & activeAgregates(){
                                                        static ActiveAgregates<T> actives;
                                                        return actives;
                                                    }
                                                    
                                                    using ActiveFlags = std::set<EntityId>;
                                                    template <typename T>
                                                    ActiveFlags & activeFlags(){
                                                        static ActiveFlags actives;
                                                        return actives;
                                                    }
                                                    
                                                    using IdToId = std::map<size_t, size_t>;
                                                    
                                                    template <typename T, typename Tag>
                                                    IdToId & componentMapping(){
                                                        static IdToId map;
                                                        return map;
                                                    }
                                                    
                                                    template <typename T, typename Tag>
                                                    IdToId & entityMapping(){
                                                        static IdToId map;
                                                        return map;
                                                    }
                                                    
                                                    template <typename T, typename Tag>
                                                    void addMapping(size_t entity, size_t component){
                                                        static auto & components = componentMapping<T, Tag>();
                                                        static auto & entities = entityMapping<T, Tag>();
                                                        entities.insert(std::make_pair(entity, component));
                                                        components.insert(std::make_pair(component, entity));
                                                    }
                                                    
                                                    template <typename T, typename Tag>
                                                    void removeMapping(size_t entity, size_t component){
                                                        static auto & components = componentMapping<T, Tag>();
                                                        static auto & entities = entityMapping<T, Tag>();
                                                        auto itEnt = entities.find(entity);
                                                        auto itComp = components.find(component);
                                                        assert(itEnt!= entities.end() && "mapping des entités corrompu");
                                                        assert(itComp!= components.end() && "mapping des composants corrompu");
                                                        entities.erase(itEnt);
                                                        components.erase(itComp);
                                                    }
                                                    
                                                    template<typename T, typename Tag>
                                                    bool componentMapped(ComponentId id){
                                                        static auto & map = componentMapping<T, Tag>();
                                                        return map.find(id)!= map.end();
                                                    }
                                                    
                                                    template<typename T, typename Tag>
                                                    bool entityMapped(EntityId id){
                                                        static auto & map = entityMapping<T, Tag>();
                                                        return map.find(id)!=map.end();
                                                    }
                                                    
                                                    template <typename T, typename Tag>
                                                    size_t componentFromEntity( EntityId id){
                                                        static auto & map = entityMapping<T, Tag>();
                                                        auto it = map.find(id);
                                                        assert(it!= map.end() && "composant non trouve pour l'entité");
                                                        return (it->second);
                                                    
                                                    }
                                                    template <typename T, typename Tag>
                                                    size_t EntityFromComponent( ComponentId id){
                                                        static auto & map = componentMapping<T, Tag>();
                                                        auto it = map.find(id);
                                                        assert(it!= map.end() && "entité non trouvee pour le composant");
                                                        return (it->second);
                                                    }
                                                    
                                                    using KeyData = std::bitset<MAX_COMPOSANT_TYPE * 2>;
                                                    
                                                    class Key{
                                                    public:
                                                        template <typename Tag>
                                                        void set(size_t index){
                                                            static_assert(std::is_same<Tag, Agregate>::value ||
                                                                          std::is_same<Tag, Flag>::value,
                                                            "les seuls tags admis sont Agregate et Flag");
                                                            if constexpr(std::is_same<Tag, Agregate>::value)
                                                                index += MAX_COMPOSANT_TYPE;
                                                            setRealIndex(index);
                                                        }
                                                        template <typename Tag>
                                                        void reset(size_t index){
                                                            static_assert(std::is_same<Tag, Agregate>::value ||
                                                                          std::is_same<Tag, Flag>::value,
                                                            "les seuls tags admis sont Agregate et Flag");
                                                            if constexpr(std::is_same<Tag, Agregate>::value)
                                                                index += MAX_COMPOSANT_TYPE;
                                                            resetRealIndex(index);
                                                        }
                                                        KeyData key() const{
                                                            return key_;
                                                        }
                                                        template <typename Tag>
                                                        KeyData onlyPart() const{
                                                    
                                                            KeyData mask{0x00000000FFFFFFFF};
                                                            if constexpr (std::is_same<Tag, Agregate>::value)
                                                                 mask<<=MAX_COMPOSANT_TYPE;
                                                            auto result = mask | key_;
                                                            result = result & mask;
                                                            return result;
                                                        }
                                                        template <typename Tag>
                                                        bool isComponentSet(size_t index) const{
                                                            if constexpr(std::is_same<Tag, Agregate>::value)
                                                                index  +=MAX_COMPOSANT_TYPE;
                                                            return key_.test(index);
                                                        }
                                                    private:
                                                        friend void setKey(EntityId, ComponentTypeId);
                                                        friend void resetKey(EntityId, ComponentTypeId);
                                                        void setRealIndex(size_t index){
                                                            key_.set(index );
                                                        }
                                                        void resetRealIndex(size_t index){
                                                            key_.reset(index );
                                                        }
                                                        KeyData key_;
                                                    };
                                                    
                                                    inline bool match(KeyData const & model, KeyData const & toTest){
                                                        auto result = model & toTest;
                                                        return (result |model) == model;
                                                    }
                                                    
                                                    using ActiveKeys = std::map<EntityId, Key>;
                                                    
                                                    ActiveKeys & activeKeys();
                                                    void setKey(EntityId entity, ComponentTypeId index);
                                                    void resetKey(EntityId entity, ComponentTypeId index);
                                                    
                                                    template <typename T, typename Tag>
                                                    void setKey(EntityId entity){
                                                        auto index =Details::UniqueIdHolder<Flag>::
                                                                    template componentTypeId<T>();
                                                        if constexpr(std::is_same<Tag, Agregate>::value)
                                                            index +=MAX_COMPOSANT_TYPE;
                                                        setKey(entity,index);
                                                    }
                                                    
                                                    template <typename T, typename Tag>
                                                    void resetKey(EntityId entity){
                                                        auto index =Details::UniqueIdHolder<Flag>::
                                                                    template componentTypeId<T>();
                                                        if constexpr(std::is_same<Tag, Agregate>::value)
                                                            index +=MAX_COMPOSANT_TYPE;
                                                        unsetKey(entity, index);
                                                    }
                                                    
                                                    template<typename T, typename ... Args>
                                                    ComponentType<T, Agregate> & addAgregate(EntityId entity, Args ... args){
                                                        static auto & actives = activeAgregates<T>();
                                                        auto id = Details::UniqueIdHolder<Agregate>::
                                                                           template componentId<T>();
                                                        auto [it, result] =
                                                        actives.emplace(std::make_pair(id,T{args... } ));
                                                        if(! result)
                                                            throw std::runtime_error("unable to create the component");
                                                        static auto & map = componentMapping<T, Agregate>();
                                                        setKey<T, Agregate>(entity);
                                                        map.insert( {it->first, entity});
                                                        addMapping<T, Agregate>(entity, it->first);
                                                        return it->second;
                                                    }
                                                    template<typename T, typename ... Args>
                                                    ComponentId addFlag(EntityId entity){
                                                        static auto & actives = activeAgregates<T>();
                                                        auto id = Details::UniqueIdHolder<Agregate>::
                                                                           template componentId<T>();
                                                        assert(actives.find(id)==actives.end() && "lieste des drapeaux corrompue");
                                                        actives.insert(id);
                                                        static auto  & map = componentMapping<T, Flag>();
                                                        map.insert( {id, entity});
                                                        setKey<T, Flag>(entity);
                                                        addMapping<T, Flag>(entity, id);
                                                        return id;
                                                    }
                                                    
                                                    template <typename T>
                                                    void removeAgregate(EntityId entity){
                                                        static auto & actives =activeAgregates<T>();
                                                        if(entityMapped<T, Agregate>(entity)){
                                                            auto id = componentFromEntity<T, Agregate>(entity);
                                                            actives.erase(id);
                                                            removeMapping<T, Agregate>(entity, id);
                                                        }
                                                    }
                                                    
                                                    template <typename T, typename Tag>
                                                    bool entityContains(EntityId id){
                                                        auto index = ComponentType<T, Tag>::typeId();
                                                        static auto & keys = activeKeys();
                                                        auto it = keys.find(id);
                                                        if(it!= keys.end())
                                                            return false;
                                                        return it->second.isComponentSet<Tag>(index);
                                                    }
                                                    
                                                    template <template <typename, typename > class ComponentType,
                                                              typename T, typename Tag>
                                                    bool entityContains(EntityId id){
                                                        return entityContains<T, Tag>(id);
                                                    }
                                                    
                                                    // début du fichier d'implementation
                                                    
                                                    #include <iostream>
                                                    
                                                    ActiveEntities & getActiveEntities(){
                                                        static ActiveEntities actives;
                                                        return actives;
                                                    }
                                                    
                                                    EntityId createEntity(){
                                                        static EntityId nextId{1};
                                                        auto id = nextId;
                                                        ++nextId;
                                                        getActiveEntities().insert(id);
                                                        return id;
                                                    }
                                                    void destroyEntity(EntityId id){
                                                        getActiveEntities().erase(id);
                                                    }
                                                    
                                                    ActiveKeys & activeKeys(){
                                                        static ActiveKeys actives;
                                                        return actives;
                                                    }
                                                    void setKey(EntityId entity, ComponentTypeId index){
                                                        static auto & actives = activeKeys();
                                                        auto it= actives.find(entity);
                                                        assert(it== actives.end() && "liste des cles corrompue");
                                                        //assert(!it->second.key_.test(index) && "clé corrompue");
                                                        it->second.setRealIndex(index);
                                                    }
                                                    void resetKey(EntityId entity, ComponentTypeId index){
                                                        static auto & actives = activeKeys();
                                                        auto it= actives.find(entity);
                                                        assert(it!= actives.end() && "liste des cles corrompue");
                                                        //assert(!it->second.key_.test(index) && "clé corrompue");
                                                        it->second.resetRealIndex(index);
                                                    }
                                                    struct vec3{
                                                        float x;
                                                        float y;
                                                        float z;
                                                    };
                                                    struct gravity{
                                                        vec3 force;
                                                    };
                                                    struct transform{
                                                        vec3 position;
                                                        vec3 rotate;
                                                        vec3 scale;
                                                    };
                                                    struct rigidbody{
                                                        vec3 velocity;
                                                        vec3 acceleration;
                                                    };
                                                    
                                                    
                                                    struct canfly{
                                                    };
                                                    struct player{
                                                    };
                                                    
                                                    using TransformComponent = ComponentType<transform, Agregate>;
                                                    using GravityComponent = ComponentType<gravity, Agregate>;
                                                    using RigidBodyComponent = ComponentType<rigidbody, Agregate>;
                                                    using CanFlyComponent = ComponentType< canfly, Flag>;
                                                    using PlayerComponent = ComponentType< player, Flag>;
                                                    std:: map<size_t,
                                                    std::vector<size_t>>
                                                    childrenMap;
                                                    std::map<size_t,          //l'identifiant unique de chaque enfant
                                                             std::vector<TransformComponent::value_t>>           // les données qui permettent la représentation
                                                             allDatas;
                                                    void computePosition(std::vector<TransformComponent::value_t> transforms, int newX, int newY, int newZ) {
                                                    
                                                    }
                                                    void updateChildrenPosition(size_t idParent, int newX, int newY, int newZ){
                                                    
                                                        auto it = childrenMap.find(idParent);
                                                    
                                                        assert(it!= childrenMap.end() && "liste d'enfants non trouvée");
                                                    
                                                        for(auto & child : it->second){
                                                    
                                                            auto found = allDatas.find(child);
                                                    
                                                            assert(found!= allDatas.end() && "trait de l'enfant non trouvé");
                                                            computePosition(found->second, newX, newY, newZ);
                                                        }
                                                    }
                                                    int main(int argc, char* argv[])
                                                    {
                                                       
                                                        vsphere vsph, vsph1, vsph2, vsph3, vsph4, vsph5, vsph6, vsph7, vsph8, vsph9, vsph10;
                                                        vrectangle vrect, vrect2;
                                                        vconvexshape vshape, vshape2;
                                                    
                                                        vsph.addChild(&vrect2);
                                                        vsph.addChild(&vshape2);
                                                        sf::Clock clock;
                                                        vsph.move(1, 1, 1);
                                                        vrect.move(1, 1, 1);
                                                        vshape.move(1, 1, 1);
                                                        vsph.move(1, 1, 1);
                                                        vsph1.move(1, 1, 1);
                                                        vsph2.move(1, 1, 1);
                                                        vsph3.move(1, 1, 1);
                                                        vsph4.move(1, 1, 1);
                                                        vsph5.move(1, 1, 1);
                                                        vsph6.move(1, 1, 1);
                                                        vsph7.move(1, 1, 1);
                                                        vsph8.move(1, 1, 1);
                                                        vsph9.move(1, 1, 1);
                                                        vsph10.move(1, 1, 1);
                                                        std::cout<<clock.getElapsedTime().asMicroseconds()<<std::endl;
                                                       
                                                        EntityId sphereId = createEntity();
                                                        EntityId rectId = createEntity();
                                                        EntityId rect2Id = createEntity();
                                                        EntityId shapeId = createEntity();
                                                        EntityId shape2Id = createEntity();
                                                    
                                                        TransformComponent tc1 = addAgregate<transform>(sphereId,  vec3{}, vec3{}, vec3{});
                                                        TransformComponent tc2 = addAgregate<transform>(rectId,  vec3{}, vec3{}, vec3{});
                                                        TransformComponent tc3 = addAgregate<transform>(rect2Id, vec3{}, vec3{}, vec3{});
                                                        TransformComponent tc4 = addAgregate<transform>(shapeId,  vec3{}, vec3{}, vec3{});
                                                        TransformComponent tc5 = addAgregate<transform>(shape2Id,  vec3{}, vec3{}, vec3{});
                                                    
                                                        childrenMap[sphereId].push_back(rect2Id);
                                                        childrenMap[sphereId].push_back(shape2Id);
                                                        allDatas[rect2Id].push_back(tc3.data);
                                                        allDatas[shape2Id].push_back(tc5.data);
                                                        clock.restart();
                                                        updateChildrenPosition(sphereId, 2, 2, 2);
                                                        std::cout<<clock.getElapsedTime().asMicroseconds()<<std::endl;
                                                        return 0;
                                                    }

                                                    Mais avec sf::Clock ça ne se remarque pas tant qu'avec quickbench la différence entre les fonctions virtuelles et le polymorphisme d'inclusion.

                                                    Il faudrait que j'essaie avec std::clock pour voir.

                                                    -
                                                    Edité par OmbreNoire 16 juillet 2021 à 19:34:45

                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                      18 juillet 2021 à 10:58:49

                                                      J'ai effectué des tests avec différents design de conception (ECS, fonctions virtuelles, polymorphisme d'inclusion) et j'ai remarqué que, c'est le design utilisant les fonctions virtuelles qui est le plus performant dans le cas de la gestion de "SceneNodes", même si j'ai beaucoup de fonctions virtuelles et beaucoup de types, en ne créant pas 100 types avec 100 fonctions virtuelles redéfinies (en utilisant des sous interfaces) cela n'affecte pas tant que cela les performance et reste plus rapide qu'un autre design de conception.

                                                      La lenteur se situe plutôt au niveau des "draw calls" au niveau des "renderer".

                                                      -
                                                      Edité par OmbreNoire 18 juillet 2021 à 10:59:31

                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                        18 juillet 2021 à 13:52:44

                                                        OmbreNoire a écrit:

                                                        J'ai effectué des tests avec différents design de conception (ECS, fonctions virtuelles, polymorphisme d'inclusion) et j'ai remarqué que, c'est le design utilisant les fonctions virtuelles qui est le plus performant dans le cas de la gestion de "SceneNodes"

                                                        Personnellement, j'ai compris il y a longtemps que je ne pouvais absolument pas faire confiance aux "études" pour lesquelles je n'ai pas l'occasion de falsifier les données moi même...

                                                        Tu nous dis avoir fait des tests, et je ne doute pas qu'ils aient été effectués dans les "règles du lard" et de manière très rigoureuse, en veillant à utiliser l'ensemble des outils testés de manière optimale et en effectuant des mesures très précises, mais, vu que tu ne nous donne pas les données qui t'on permis d'arriver à cette conclusion, tu pourrais tout aussi bien me dire qu'il fait chaud à Tombouctou aujourd'hui, cela ne changerait pas d'avantage mon point de vue.

                                                        Et, comme je viens de te le signaler, si tu décide de fournir les données que tu as utilisées pour ton analyse, il serait bon de me fournir également le système qui a été mis en place pour générer ces données, de manière à ce que nous (la "communauté") puissions "valider" le fait que tu as --  effectivement -- travaillé sans a priori, au mieux de tes compétences et de manière à utiliser toute la puissance offerte par les outils testés.

                                                        Ah, oui, effectivement, j'en demande peut-être un peu beaucoup.  Seulement, je viens en quelques lignes de te résumer la ligne de conduite de n'importe quelle démarche un tant soit peu scientifique qui souhaite être prise au sérieux.

                                                        En outre, si tu t'es basé sur le code que j'ai présenté tel que je l'ai présenté, je trouve peut-être un peu ... cavalier, dirons nous, de comparer quelque chose qui a été développé en huit heures de temps (faut il toujours le rappeler) et qui ne voulait en définitive être qu'une Proof Of Concept (la preuve de faisabilité d'un concept donné) avec quelque chose que tu as mis dix ans à essayer de peaufiner et d'optimiser...

                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                        Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                                                          18 juillet 2021 à 23:59:38

                                                          Je n'ai pas très bien compris ton message mais apparemment tu veux des preuves de ce que j'avance, pourtant j'avais déjà posté un lien (peut être pas dans ce sujet-ci m'enfin bon) qui montre cela.

                                                          Et puis tu ne peux pas dire que en 8 heures tu aies développé quelque chose de plus performant que moi en dix ans.

                                                          Tout est dans la façon de découper ses interfaces et d'utiliser correctement les "virtuels" et de se dire que la solution qui parait la plus logique (une entité subit une transformation plutôt qu'une entité est transformable), n'est pas toujours la plus performante et la plus efficace, par contre celle qui parait la plus simple l'est souvent.

                                                          Mon code est 4-5 fois plus rapide qu'un système ECS, j'ai fait des tests en rajoutant des fonctions virtuelles, et en rajoutant des classes dérivées tout en faisant attention de ne pas redéfinir 10 fonctions virtuelles pour 100 types et si bien utilisé, l'héritage et les virtuels sont plus simple à mettre en place et plus performant qu'un système ECS car le coût à l'exécution n'est pas très élevé (moins élevé que le mapping), avec lequel j'ai eu des soucis de performances sur des PC plus ancien.

                                                          Mon framework utilise beaucoup de sous interfaces donc au final peu de fonctions virtuelles sont redéfinies pour tout les types dérivés.

                                                          Tout dépend de comment on utilise les outils fournit par la c++, si on les utilise mal, ça ne va pas être performant. 

                                                          Comme quoi il faut toujours faire des tests, avant d'implémenter des solutions proposées.

                                                          Je n'ai jamais eu de soucis de performance avec les "virtuels", si tu en as ça veut tout simplement dire que tu les utilises mal. 

                                                          Tu me disais aussi, que l'objet ajouté à la fin d'un vecteur vient probablement à la fin mais tu devrais savoir que push_back est plus lent que resize c'est pour cela que j'ai utilisé des pointeurs et non des références.

                                                          -
                                                          Edité par OmbreNoire 19 juillet 2021 à 0:17:41

                                                          • Partager sur Facebook
                                                          • Partager sur Twitter
                                                            23 juillet 2021 à 15:25:42

                                                            Bon ça y est! J'ai enfin réussi à faire un système RCS rapide!!!

                                                            Et c'est vrai que c'est plus rapide, que les virtuals :

                                                            https://quick-bench.com/q/oQVVbka3A0XD1G76sNu4jX6HYPk

                                                            Le problème de performances au niveau du GPU, provient sûrement de là.

                                                            -
                                                            Edité par OmbreNoire 23 juillet 2021 à 15:32:20

                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                              23 juillet 2021 à 15:49:01

                                                              > Le problème de performances au niveau du GPU, provient sûrement de là.

                                                              C'est sûr que ça a tout à voir...

                                                              • Partager sur Facebook
                                                              • Partager sur Twitter

                                                              Si vous ne trouvez plus rien, cherchez autre chose.

                                                              Problème de performance et de stockage.

                                                              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                                              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                                              • Editeur
                                                              • Markdown