Outils d'utilisateurs

Outils du Site


javaquarium

Différences

Cette page vous donne les différences entre la révision choisie et la version actuelle de la page.

Lien vers cette vue

javaquarium [2016/05/12 17:04]
gbdivers
javaquarium [2016/05/30 20:08] (Version actuelle)
gbdivers [Exercice 3.3 : Mais… la sexualité des poissons est horriblement compliquée !]
Ligne 1846: Ligne 1846:
                         (std::get<2>(*poisson_sex) != std::get<2>(*poisson_2_sex)) && (race != std::get<1>(*poisson_2_race)))                         (std::get<2>(*poisson_sex) != std::get<2>(*poisson_2_sex)) && (race != std::get<1>(*poisson_2_race)))
                     {                     {
-                        ecs::add_poisson(race, "Bebe", ecs::random_sexe(), std::min(std::get<2>(*type), std::get<2>(*poisson_2_race)));+                        ecs::add_poisson(race, "Bebe", ecs::random_sexe(), 5);
                     }                     }
                 }                 }
Ligne 1878: Ligne 1878:
 <code> <code>
 Tour Entities Algues Herbivores Carnivores Tour Entities Algues Herbivores Carnivores
-0 20 3 10 7 +0 20 1 8 11 
-1 27 6 11 10 +1 24 2 10 12 
-2 35 6 16 13 +2 33 2 13 18 
-3 44 6 21 17 +3 45 1 19 25 
-4 53 6 26 21 +4 57 0 25 32 
-5 67 6 33 28 +5 63 0 28 35 
-6 60 2 31 27 +6 62 0 28 34 
-7 68 0 36 32 +7 69 0 31 38 
-8 78 0 44 34 +8 72 0 31 41 
-9 81 0 45 36 +9 81 0 34 47 
-10 94 0 56 38 +10 84 0 33 51 
-11 101 0 63 38 +11 97 0 40 57 
-12 114 0 70 44 +12 106 0 44 62 
-13 133 0 80 53 +13 110 0 44 66 
-14 146 0 86 60 +14 120 0 45 75 
-15 161 0 94 67 +15 130 0 52 78 
-16 172 0 103 69 +16 137 0 58 79 
-17 173 0 102 71 +17 159 0 61 98 
-18 188 0 115 73 +18 179 0 69 110 
-19 199 0 122 77 +19 190 0 72 118 
-20 210 0 129 81 +20 206 0 77 129 
-21 234 0 140 94 +21 228 0 82 146 
-22 254 0 152 102 +22 242 0 85 157 
-23 280 0 166 114 +23 273 0 92 181 
-24 288 0 175 113 +24 288 0 97 191 
-25 305 0 183 122 +25 310 0 97 213 
-26 318 0 198 120 +26 332 0 100 232 
-27 342 0 215 127 +27 365 0 107 258 
-28 359 0 225 134 +28 390 0 114 276 
-29 383 0 244 139 +29 427 0 119 308 
-30 407 0 260 147 +30 449 0 119 330 
-31 447 0 285 162 +31 499 0 132 367 
-32 503 0 321 182 +32 533 0 140 393 
-33 544 0 347 197 +33 580 0 144 436 
-34 576 0 366 210 +34 641 0 150 491 
-35 591 0 374 217 +35 668 0 156 512 
-36 636 0 413 223 +36 713 0 159 554 
-37 682 0 438 244 +37 741 0 156 585 
-38 724 0 468 256 +38 806 0 176 630 
-39 766 0 500 266 +39 875 0 189 686 
-40 845 0 550 295 +40 931 0 187 744 
-41 894 0 583 311 +41 982 0 183 799 
-42 944 0 618 326 +42 1019 0 177 842 
-43 1014 0 672 342 +43 1053 0 179 874 
-44 1090 0 724 366 +44 1093 0 175 918 
-45 1152 0 782 370 +45 1138 0 175 963 
-46 1230 0 840 390 +46 1189 0 163 1026 
-47 1320 0 908 412 +47 1239 0 158 1081 
-48 1404 0 979 425 +48 1282 0 143 1139 
-49 1519 0 1058 461 +49 1329 0 132 1197 
-50 1600 0 1119 481 +50 1319 0 109 1210 
-51 1688 0 1180 508 +51 1341 0 92 1249 
-52 1799 0 1274 525 +52 1321 0 69 1252 
-53 1905 0 1344 561 +53 1302 0 47 1255 
-54 2042 0 1448 594 +54 1244 0 24 1220 
-55 2160 0 1529 631 +55 1163 0 3 1160 
-56 2304 0 1637 667 +56 1137 0 1 1136 
-57 2431 0 1732 699 +57 1113 0 1 1112 
-58 2551 0 1828 723 +58 1090 0 1 1089 
-59 2656 0 1935 721 +59 1045 0 0 1045 
-60 2757 0 2023 734 +60 997 0 0 997 
-61 2890 0 2153 737 +61 948 0 0 948 
-62 3049 0 2307 742 +62 896 0 0 896 
-63 3208 0 2467 741 +63 843 0 0 843 
-64 3328 0 2585 743 +64 626 0 0 626 
-65 3420 0 2681 739 +65 442 0 0 442 
-66 3548 0 2818 730 +66 258 0 0 258 
-67 3668 0 2944 724 +67 128 0 0 128 
-68 3785 0 3067 718 +68 0 0 0 0
-69 3876 0 3186 690 +
-70 3938 0 3296 642 +
-71 4019 0 3412 607 +
-72 4025 0 3484 541 +
-73 4061 0 3565 496 +
-74 4016 0 3588 428 +
-75 3906 0 3562 344 +
-76 3779 0 3522 257 +
-77 3567 0 3406 161 +
-78 3320 0 3244 76 +
-79 3103 0 3099 4 +
-80 3010 0 3006 4 +
-81 2909 0 2909 0 +
-82 2779 0 2779 0 +
-83 2633 0 2633 0 +
-84 2469 0 2469 0 +
-85 2312 0 2312 0 +
-86 2168 0 2168 0 +
-87 2002 0 2002 0 +
-88 1442 0 1442 0 +
-89 970 0 970 0 +
-90 577 0 577 0 +
-91 247 0 247 0 +
-92 0 0 0 0 +
-93 0 0 0 0 +
-94 0 0 0 0 +
-95 0 0 0 0 +
-96 0 0 0 0 +
-97 0 0 0 0 +
-98 0 0 0 0 +
-99 0 0 0 0+
 ===== Fini ====== ===== Fini ======
 </code> </code>
Ligne 1984: Ligne 1953:
 ==== Exercice 3.3 : Mais… la sexualité des poissons est horriblement compliquée ! ==== ==== Exercice 3.3 : Mais… la sexualité des poissons est horriblement compliquée ! ====
  
-===== Partie 4 : Rendons notre simulateur d'aquarium pratique =====+Dans cette partie, on ajoute differents types de comportement sexuels.
  
-==== Exercice 4.1 : Sauvez Willy ! ====+<code cpp main.cpp> 
 +#include <iostream> 
 +#include <vector> 
 +#include <string> 
 +#include <algorithm> 
 +#include <numeric> 
 +#include <iterator> 
 +#include <cassert> 
 +#include <random> 
 +#include <tuple> 
 +  
 +namespace ecs { 
 +    namespace entity { 
 +        using id size_t; 
 +        using entities std::vector<id>; 
 +    } 
 +  
 +    namespace component { 
 +        using name std::string; 
 +        using is_male = bool; 
 +        using detail_component = std::tuple<entity::id, name, is_male>; 
 +  
 +        enum class race { algue, Mérou, Thon, PoissonClown, Sole, Bar, Carpe }; 
 +        using point_vie = int8_t; 
 +        using age = uint8_t; 
 +        using type_component = std::tuple<entity::id, race, point_vie, age>; 
 +    } 
 +  
 +    namespace system { 
 +        using details = std::vector<component::detail_component>; 
 +        using types = std::vector<component::type_component>; 
 +    } 
 +  
 +    namespace internal { 
 +        entity::entities _entities; 
 +        system::details _details; 
 +        system::types _types; 
 +  
 +        std::random_device _random_device; 
 +        std::default_random_engine _random_engine { _random_device() }; 
 +    } 
 +  
 +    bool is_algue(component::race r) { return (r == component::race::algue); } 
 +    bool is_poisson(component::race r) { return !is_algue(r); } 
 +    bool is_carnivore(component::race r) { return (r == component::race::Mérou || r == component::race::Thon || r == component::race::PoissonClown); } 
 +    bool is_herbivore(component::race r) { return (r == component::race::Sole || r == component::race::Bar || r == component::race::Carpe); } 
 +  
 +    component::race& get_race(component::type_component & t) { return std::get<1>(t); } 
 +    component::race get_race(component::type_component const& t) { return std::get<1>(t); } 
 +    component::point_vie& get_point_vie(component::type_component & t) { return std::get<2>(t); } 
 +    component::age& get_age(component::type_component & t) { return std::get<3>(t); } 
 +     
 +    component::is_male is_male(component::detail_component const& d) { return std::get<2>(d); } 
 +    component::is_male & is_male(component::detail_component & d) { return std::get<2>(d); } 
 +     
 +    bool is_algue_t(component::type_component const& t) { return is_algue(get_race(t)); } 
 +    bool is_poisson_t(component::type_component const& t) { return is_poisson(get_race(t)); } 
 +    bool is_carnivore_t(component::type_component const& t) { return is_carnivore(get_race(t)); } 
 +    bool is_herbivore_t(component::type_component const& t) { return is_herbivore(get_race(t)); } 
 +   
 +    bool can_reproduce(component::type_component const& type_papa, component::type_component const& type_maman, 
 +                       component::detail_component const& detail_papa, component::detail_component const& detail_maman)  
 +    { 
 +        if (get_race(type_papa) != get_race(type_maman)) 
 +            return false; 
 +         
 +        return  (is_male(detail_papa) != is_male(detail_maman)); 
 +    } 
 +     
 +    bool random_sexe(component::race r) { 
 +        if (r == component::race::Carpe || r == component::race::Thon) { 
 +            static std::uniform_int_distribution<size_t> algue_distribution(0,1); 
 +            return (algue_distribution(ecs::internal::_random_engine) == 0); 
 +        } else { 
 +            return true; 
 +        } 
 +    } 
 +     
 +    void update_sexe(component::type_component type_poisson, component::is_male & is_male_poisson) { 
 +        if ((get_race(type_poisson) == component::race::Bar || get_race(type_poisson) == component::race::Mérou) &&  
 +            (get_age(type_poisson) >= 10))  
 +        { 
 +            is_male_poisson = false; 
 +        } 
 +    } 
 +  
 +    component::race random_race() { 
 +        static std::uniform_int_distribution<size_t> algue_distribution(0,6); 
 +        return static_cast<component::race>(algue_distribution(ecs::internal::_random_engine)); 
 +    } 
 +  
 +    std::string to_string(component::race r) { 
 +        static const std::string races[] = { "Algue", "Mérou", "Thon", "Poisson-clown", "Sole", "Bar", "Carpe" }; 
 +        const auto i = static_cast<size_t>(r); 
 +        assert(i < (sizeof(races) / sizeof(*races))); 
 +        return races[i]; 
 +    } 
 +  
 +    template<typename Predicat> 
 +    std::vector<entity::id> get_entities(Predicat&& predicat) { 
 +        std::vector<entity::id> entities; 
 +        std::for_each(begin(internal::_types), end(internal::_types), 
 +            [&entities, predicat](const auto type){ if (predicat(type)) { entities.push_back(std::get<0>(type)); } } 
 +        ); 
 +        return entities; 
 +    } 
 +  
 +    template<typename Collection> 
 +    typename Collection::iterator get_component(Collection & components, entity::id id) { 
 +        auto it = find_if(begin(components), end(components), [id](auto t){ return (std::get<0>(t) == id); }); 
 +        return it; 
 +    } 
 +  
 +    entity::id create_entity() { 
 +        const auto id = internal::_entities.empty() ? 0 : internal::_entities.back() + 1; 
 +        internal::_entities.push_back(id); 
 +        return id; 
 +    } 
 +  
 +    entity::id add_algue(ecs::component::point_vie pv = 10) { 
 +        const auto id = create_entity(); 
 +        internal::_types.push_back(std::make_tuple(id, component::race::algue, pv, 0)); 
 +        return id; 
 +    } 
 +  
 +    entity::id add_poisson(component::race t, component::name n, component::is_male m, ecs::component::point_vie pv = 10) { 
 +        assert(is_poisson(t)); 
 +        const auto id = create_entity(); 
 +        internal::_types.push_back(std::make_tuple(id, t, pv, 0)); 
 +        internal::_details.push_back(std::make_tuple(id, n, m)); 
 +        return id; 
 +    } 
 +  
 +    void remove_entity(entity::id id) { 
 +        const auto entities_it = std::remove(internal::_entities.begin(), internal::_entities.end(), id); 
 +        internal::_entities.erase(entities_it, internal::_entities.end()); 
 +  
 +        const auto details_it = std::remove_if(internal::_details.begin(), internal::_details.end(),  
 +            [id](auto t){ return (std::get<0>(t) == id); }); 
 +        internal::_details.erase(details_it, internal::_details.end()); 
 +  
 +        const auto types_it = std::remove_if(internal::_types.begin(), internal::_types.end(), 
 +            [id](auto t){ return (std::get<0>(t) == id); }); 
 +        internal::_types.erase(types_it, internal::_types.end()); 
 +    } 
 +  
 +    void remove_entities(std::vector<ecs::entity::id> const& ids) { 
 +        for (auto id: ids)  
 +            remove_entity(id); 
 +    } 
 +  
 +    void print(size_t tour, size_t entites, size_t algues, size_t herbivores, size_t carnivores) { 
 +        std::cout << tour << "\t\t" << entites << "\t\t" << algues << "\t\t" << herbivores << "\t\t" << carnivores << std::endl; 
 +    } 
 +
 +  
 +int main() { 
 +    std::cout << "Tour\t\tEntities\tAlgues\t\tHerbivores\tCarnivores" << std::endl; 
 +  
 +    for (size_t i {}; i < 20; ++i) { 
 +        const auto race = ecs::random_race(); 
 +        if (ecs::is_algue(race)) 
 +            ecs::add_algue(); 
 +        else 
 +            ecs::add_poisson(race, ecs::to_string(race), ecs::random_sexe(race)); 
 +    } 
 +  
 +    for (size_t tour {}; tour < 100; ++tour) { 
 +        // update pv and age 
 +        for (auto entity: ecs::internal::_entities) { 
 +            const auto type = ecs::get_component(ecs::internal::_types, entity); 
 +            // pv 
 +            if (ecs::is_algue_t(*type)) { 
 +                if (ecs::get_point_vie(*type) > 10) { 
 +                    ecs::get_point_vie(*type) /= 2; 
 +                    ecs::add_algue(ecs::get_point_vie(*type)); 
 +                } else { 
 +                    ecs::get_point_vie(*type) += 1; 
 +                } 
 +            } else { 
 +                ecs::get_point_vie(*type) -= 1; 
 +            } 
 +            // age 
 +            ecs::get_age(*type) += 1; 
 +        } 
 +  
 +        const auto herbivores = ecs::get_entities(ecs::is_herbivore_t); 
 +        const auto algues = ecs::get_entities(ecs::is_algue_t); 
 +        ecs::print(tour, ecs::internal::_entities.size(), algues.size(), herbivores.size(), ecs::internal::_entities.size() - algues.size() - herbivores.size()); 
 +  
 +        const auto poissons = ecs::get_entities(ecs::is_poisson_t); 
 +        for (const auto poisson_id: poissons) { 
 +            const auto type = ecs::get_component(ecs::internal::_types, poisson_id); 
 +            if (type != end(ecs::internal::_types)) 
 +            { 
 +                const auto race = std::get<1>(*type); 
 +  
 +                if (ecs::get_point_vie(*type) < 5) 
 +                { // manger 
 +                    if (ecs::is_herbivore_t(*type) && !algues.empty()) 
 +                    { 
 +                        std::uniform_int_distribution<size_t> algue_distribution(0, algues.size() - 1); 
 +                        const auto index { algue_distribution(ecs::internal::_random_engine) }; 
 +                        const auto algue_id = algues[index]; 
 +  
 +                        const auto herbivore_type = ecs::get_component(ecs::internal::_types, poisson_id); 
 +                        const auto algue_type = ecs::get_component(ecs::internal::_types, algue_id); 
 +  
 +                        if ((herbivore_type != end(ecs::internal::_types)) && (algue_type != end(ecs::internal::_types))) { 
 +                            ecs::get_point_vie(*herbivore_type) += 3; 
 +                            ecs::get_point_vie(*algue_type) -= 2; 
 +                        } 
 +                    }  
 +                    else 
 +                    { 
 +                        const auto bons_petits_plats = ecs::get_entities( 
 +                            [poisson_id, race](const auto _type){ 
 +                                const auto _id = std::get<0>(_type); 
 +                                const auto _race = std::get<1>(_type); 
 +                                return ( 
 +                                    ecs::is_poisson(_race) &&  
 +                                    (_id != poisson_id) && 
 +                                    (_race != race) 
 +                                ); 
 +                            } 
 +                        ); 
 +  
 +                        if (!bons_petits_plats.empty()) { 
 +                            std::uniform_int_distribution<size_t> entities_distribution(0, bons_petits_plats.size() - 1); 
 +                            const auto index { entities_distribution(ecs::internal::_random_engine) }; 
 +                            const auto entity_id = bons_petits_plats[index]; 
 +                            const auto entity_type = ecs::get_component(ecs::internal::_types, entity_id); 
 +  
 +                            if (entity_type != end(ecs::internal::_types)) 
 +                            { 
 +                                if (ecs::is_algue_t(*entity_type)) { 
 +                                    ecs::get_point_vie(*type) += 3; 
 +                                    ecs::get_point_vie(*entity_type) -= 2; 
 +                                } else { 
 +                                    ecs::get_point_vie(*type) += 5; 
 +                                    ecs::get_point_vie(*entity_type) -= 4
 +                                } 
 +                            } 
 +                        } 
 +                    } 
 +                } 
 +                else 
 +                { // bais... se reproduire 
 +                    std::uniform_int_distribution<size_t> poissons_distribution(0, poissons.size() - 1); 
 +                    const auto index { poissons_distribution(ecs::internal::_random_engine) }; 
 +                    const auto poisson_2_id = poissons[index]; 
 +  
 +                    const auto poisson_sex = ecs::get_component(ecs::internal::_details, poisson_id); 
 +                    const auto poisson_2_sex = ecs::get_component(ecs::internal::_details, poisson_2_id); 
 +                    const auto poisson_2_race = ecs::get_component(ecs::internal::_types, poisson_2_id); 
 +  
 +                    if ((poisson_sex != end(ecs::internal::_details)) && (poisson_2_sex !end(ecs::internal::_details)) && (poisson_2_race !end(ecs::internal::_types)) && 
 +                        (std::get<2>(*poisson_sex) !std::get<2>(*poisson_2_sex)) && (race != std::get<1>(*poisson_2_race))) 
 +                    { 
 +                        ecs::add_poisson(race, "Bebe", ecs::random_sexe(race), 5); 
 +                    } 
 +                } 
 +                 
 +                // update sex 
 +                const auto details = ecs::get_component(ecs::internal::_details, poisson_id); 
 +                if (details != end(ecs::internal::_details)) 
 +                { 
 +                    ecs::update_sexe(*type, ecs::is_male(*details)); 
 +                }    
 +            } 
 +        } 
 +  
 +        // deads 
 +        std::vector<ecs::entity::id> entities_to_remove; 
 +        for (auto entity: ecs::internal::_entities) { 
 +            const auto type = ecs::get_component(ecs::internal::_types, entity); 
 +  
 +            if (type != end(ecs::internal::_types) &&  
 +                (ecs::get_point_vie(*type) <= 0 || ecs::get_age(*type) > 20))  
 +            { 
 +                entities_to_remove.push_back(entity); 
 +            } 
 +        } 
 +  
 +        for (auto entity: entities_to_remove) { 
 +            ecs::remove_entity(entity); 
 +        } 
 +    } 
 +    std::cout << "===== Fini ======" << std::endl; 
 +  
 +    return 0;    
 +
 +</code>
  
-==== Exercice 4.2 Un fichier pour les enregistrer tous… ====+Affiche :
  
-==== Exercice 4.3 : … et dans un fichier les charger ====+<code> 
 +Tour Entities Algues Herbivores Carnivores 
 +0 20 2 10 8 
 +1 27 4 13 10 
 +2 33 4 15 14 
 +3 37 3 17 17 
 +4 45 3 23 19 
 +5 48 0 27 21 
 +6 44 0 25 19 
 +7 54 0 31 23 
 +8 54 0 30 24 
 +9 58 0 31 27 
 +10 57 0 30 27 
 +11 67 0 33 34 
 +12 72 0 32 40 
 +13 74 0 35 39 
 +14 85 0 37 48 
 +15 88 0 36 52 
 +16 95 0 40 55 
 +17 102 0 39 63 
 +18 111 0 43 68 
 +19 125 0 48 77 
 +20 135 0 48 87 
 +21 142 0 47 95 
 +22 138 0 46 92 
 +23 142 0 46 96 
 +24 145 0 44 101 
 +25 149 0 43 106 
 +26 146 0 36 110 
 +27 153 0 34 119 
 +28 160 0 35 125 
 +29 157 0 28 129 
 +30 163 0 25 138 
 +31 156 0 16 140 
 +32 148 0 8 140 
 +33 142 0 3 139 
 +34 136 0 0 136 
 +35 130 0 0 130 
 +36 125 0 0 125 
 +37 120 0 0 120 
 +38 115 0 0 115 
 +39 88 0 0 88 
 +40 58 0 0 58 
 +41 41 0 0 41 
 +42 23 0 0 23 
 +43 0 0 0 0 
 +===== Fini ====== 
 +</code>
  
-==== Exercice 4.4 : Tourne, la roue tourne… ====+=== Comment choisir les composants ? ===
  
-==== Exercice 4.5 : Le petit neveu ====+Dans cette partie, le but n'est pas simplement de discuter d’implémentation, mais plus le choix des composants du point de vue du jeu (//game design//).
  
 +Dans un ECS (tout au moins, dans la façon dont je comprends les ECS), les composants sont les données du jeu. La question est donc quelles données doivent (peuvent) être ensemble dans un composant et celle qui doivent (peuvent) être séparées dans des composants différents.
  
 +(Note : ces approches sont également valides si on considere que la première donnée est la liste des identifiants des entités et la seconde un composant ou une donnée quelconque. C'est également valide avec un composant et une liste de ressources dans un système.)
  
 +Pour bien comprendre, il est possible de représenter les composants dans un tableau a deux dimensions (similaire a une matrice creuse).
  
 +{{http://cowboyprogramming.com/images/eyh/Fig-2.gif}}
  
 +(Image provenant de http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/)
  
 +Le premier critère pour savoir si deux données peuvent appartenir au même composant est de savoir si une entité peut ou non avoir que l'une des deux données.
 +
 +{{ :ecs1.png |}}
 +
 +Par exemple, dans le javaquarium, toutes les entités "poisson" possèdent une données "nom" et "age". Il est donc possible de les mettre dans un même composant. Au contraire, la donnée "points de vie" concerne toutes les entités "être vivant", pas uniquement les entités "poisson". Il sera donc logique de la placer dans un composant différent.
 +
 +Il faut cependant modérer un peu ce critère. Supposons que 99 % des entités sont des poissons. Dans ce cas, cela signifie que si on regroupe les trois données (nom, age et points de vie), il y aura 1 % des composants qui auront deux données vides (nom et age, pour les entités "algue"). Or, séparer ces données dans deux composants aura un coût (mémoire et/ou performances). Il peut être intéressant d'avoir un seul composant qui regroupe toutes les données dans ce cas.
 +
 +<code cpp>
 +using Component = std::vector<A, B>;
 +</code>
 +
 +Un autre point a prendre en compte et qui peut modifier ce premier critère est l'utilisation des données par les algorithmes. Dans le javaquarium, la donnée "age" sera utilisée a chaque tour (pour la mettre a jour, pour mettre a jour le sexe des poissons hermaphrodites, etc.). Au contraire, la donnée "nom" ne sera pas utilisée aussi souvent, elle sera par exemple utilisée dans un éditeur graphique. Dans cette situation, il peut être intéressant d'optimiser le cache pour les données utilisées a chaque tour et donc a séparer les données "nom" et "age" dans deux composants différents.
 +
 +{{ :ecs2.png |}}
 +
 +<code cpp>
 +using ComponentA = std::vector<A>;
 +using ComponentB = std::vector<B>;
 +</code>
 +
 +Se pose alors le probleme de comment faire le lien entre les données dans des composants ? Plusieurs solutions sont possibles.
 +
 +{{ :ecs3.png |}}
 +
 +Dans la première approche, les données sont dans le même composant. Supprimer un composant supprime donc directement les deux données. L’accès au composant permet d'obtenir directement les deux données.
 +
 +Dans le seconde approche, les données sont séparées dans deux composants et chaque composant est manipulés dans deux collections de **même** taille.
 +
 +<code cpp>
 +struct Data {
 +    using ComponentA = std::vector<A>;
 +    using ComponentB = std::vector<B>;
 +    
 +    void insert(); // insert dans A et B
 +    void remove(); // remove dans A et B
 +    ...
 +};
 +</code>
 +
 +Dans cette approche, il est nécessaire de conserver la cohérence des données, c'est a dire que les données correspondant au même indice dans les deux collections appartiennent a la même entité. Avec cette structure de données, il est assez facile de maintenir la cohérence, il suffit d'effectuer les mêmes opérations sur les deux collections en même temps (insertion, suppression, tri, etc.).
 +
 +Lorsque l'on accède a un élément d'une de deux collections, il est possible d’accéder directement a la seconde collection, en utilisant l'indice ou avec deux boucles synchronisées (une autre approche est d’utiliser boost.zip_iterator). L'indice peut être déterminé via une variable (par exemple dans une boucle) ou avec ''std::distance'' (complexité algorithmique constant).
 +
 +<code cpp>
 +// boucles synchronisées
 +auto it_a = begin(v_a);
 +auto it_b = begin(v_b);
 +for (; it_a != end(v_a) && it_b != end(v_b); ++it_a, ++it_b) { ... }
 +
 +// une boucle avec indice
 +for (auto it = begin(v_a); it != end(v_a); ++it) {
 +    const auto i = std::distance(begin(v_a), it);
 +    assert(i < v_b.size());
 +    b = v_b[i];
 +}
 +</code>
 +
 +Dans la troisièmement approche, les éléments de la seconde collection ne sont plus identifiés par rapport a leur position dans la collection, mais par l'ajout d'une donnée supplémentaire permettant d'identifier chaque élément (par exemple l'identifiant des entités).
 +
 +<code cpp>
 +using ComponentA = std::vector<A>;
 +using ComponentB = std::vector<std::pair<entity_id, B>>;
 +</code>
 +
 +Dans cette approche, il n'est pas nécessaire de synchroniser les données, il est possible d’ajouter ou de supprimer un élément dans la collection A sans modifier la collection B (et réciproquement). Par contre, les accès aux composants A et B nécessiteront de faire une recherche dans les deux collections (par exemple pour supprimer les composants lors de la suppression d'une entité).
 +
 +Dans ce cas, plus une collection sera grande, plus le temps d’accès sera longue.
 +
 +<code cpp>
 +const auto it = std::find(begin(v_b), end(v_b), [entity_id](auto p){ return (p.second == entity_id); );
 +</code>
 +
 +La dernière approche consiste a conserver une indirection dans la donnée A vers la donnée B, ce qui permet un accès direct, sans devoir faire une recherche (coûteuse) dans la collection B. Cette indirection peut être un pointeur (nu a priori), un itérateur ou un indice. (Ou encore un pointeur vers une classe de base d'une hiérarchie d'objets, mais cela répondrait a d'autres problématiques.)
 +
 +<code cpp>
 +using ComponentA = std::vector<std::pair<A, ComponentB::iterator>>;
 +using ComponentB = std::vector<B>;
 +</code>
 +
 +L’intérêt de cette approche est bien sur la rapidité d’accès vers les composants B depuis les composants A. Par contre, cela implique qu'il faut mettre a jour les indirections a chaque fois qu'un élément est ajouté ou supprimé dans la collection B.
 +
 +Un critère intéressant a prendre en compte est l'efficace d'utilisation de la mémoire pour chaque approche. Le calcul de la taille totale des collections selon les différentes approches est simple. (Sans prendre en compte le surcoût lié a ''std::vector'').
 +
 +<code>
 +// Approche 1
 +size = v.size() * (sizeof(A) + sizeof(B));
 +
 +// Approche 2
 +size = v_a.size() * (sizeof(A) + sizeof(B));
 +
 +// Approche 3
 +size = v_a.size() * sizeof(A) + v_b.size() * (sizeof(id) + sizeof(B));
 +
 +// Approche 4
 +size = v_a.size() * (sizeof(A) + sizeof(void*)) + v_b.size() * sizeof(B);
 +</code>
 +
 +Avec ces formules, il est intéressant de simuler quelques valeurs. Pour cela, je prend ''sizeof(id) == sizeof(void*) == 1'', un rapport ''v_b.size() / v_a.size()'' (en ligne) compris entre 0,1 et 1 et ''sizeof(B) / sizeof(A)'' (en colonne) qui vaut 1, 2, 4 et 8. Et comme c'est la différence entre les approches qui nous intéresse, je représente uniquement les approches 2, 3 et 4, en pourcentage de taille par rapport a l'approche 1. (Donc par exemple "50" signifie que l'approche prend deux fois moins de mémoire que l'approche 1).
 +
 +{{ :ecs_mem.png |}}
 +
 +Sans surprise, l'approche 2 consomme la même quantité de mémoire que l'approche 1. L'approche 3 sera plus intéressante lorsque les données seront volumineuses  et qu'il y aura beaucoup plus d’entités que de composants. L’approche 4 sera plus intéressant quand les données seront volumineuses et qu'il y aura beaucoup d’entités avec ces composants.
 +
 +Bien sur, ces analyses ne prennent pas en compte l’efficacité du cache ou la corrélation avec l'algorithme. Dans tous les cas, une analyse plus approfondie (profiling) sera nécessaire pour bien choisir l'approche a utiliser.
 +
 +Voila les principales approches qui me semblent intéressantes pour un ECS. Mais je ne prétends pas être exhaustif. Par exemple, si la majorité des entités ont une majorité de composants ou si le nombre d’entités et de composants n'est pas important, il est possible de stocker toutes les données directement dans un tableau 2D, avec les entités en ligne et les systèmes en colonne. 
 +
 +Je me suis également limite aux collections triées, pour permettre les accès les plus rapides. Mais il est par exemple possible d'utiliser l'approche 4 sans trier les objets dans la collection B. Lors de la suppression d'un composant, on laisse simplement un "trou" dans la collection et on l'utilise lorsque l'on ajoute un nouveau composant. Dans ce cas, il y a un surcoût pour trouver les "trous" et un cache qui peut être moins efficace, mais on gagne par rapport au l’étape de "compactage" de la mémoire. Voire il est possible de conserver les composants B sans collection. Dans ce cas, on gagne sur les étapes de "compactage" et de gestion des "trous", mais on perd au niveau du cache mémoire et des allocations et désallocations.
 +
 +{{ :ecs4.png |}}
 +
 +De plus, l'approche ECS sera intéressante surtout si le tableau contient plus de ligne que de colonne. Si par exemple, on a 1000 composants possible et 10 entités utilisant 2 ou 3 composants différents, alors chaque collection de composants pourra contenir 0 ou 1 éléments. Et la complexité d'un ECS sera au final pénalisant. Dans ce cas, une approche orientée objet classique, basée sur une hiérarchie de classes, pourra être une approche intéressante.
 +
 +La conclusion est que le choix de l'approche ne peut pas être définie uniquement sur des considérations techniques d’implémentation. Le //game design// influencera beaucoup l'approche (ou plus probablement "les approches") selon les données a manipuler et les traitements appliquées. Par exemple, on pourra choisir l'approche 4 pour le système de rendu (la majorité des entités sera visibles) et l'approche 3 pour les armes (si les ennemis humains portant une arme sont minoritaires).
 +
 +Un bon moteur d'ECS, a mon sens, ne proposera donc pas une approche fixe dans son design, mais proposera aux développeurs de choisir le design qu'ils préfèrent utiliser, selon les besoin du //game design//. (Le C++ permet de proposer plusieurs approches sans surcoût a l’exécution. Ici, les classes de traits et de politique est une approche intéressante pour implémenter cela.)
 +
 +
 +=== Réécriture de la partie 3.3 ===
 +
 +Dans cette partie, je vais réécrire le code, mais en séparant a l’extrême les composants, c'est a dire en considérant que une information correspond a un composant.
 +
 +{{ :ecs5.png |}}
 +
 +
 +
 +===== Partie 4 : Rendons notre simulateur d'aquarium pratique =====
 +
 +==== Exercice 4.1 : Sauvez Willy ! ====
 +
 +==== Exercice 4.2 : Un fichier pour les enregistrer tous… ====
 +
 +==== Exercice 4.3 : … et dans un fichier les charger ====
 +
 +==== Exercice 4.4 : Tourne, la roue tourne… ====
 +
 +==== Exercice 4.5 : Le petit neveu ====
javaquarium.1463065496.txt.gz · Dernière modification: 2016/05/12 17:04 par gbdivers