Generalizing Repeated Functions.

I'm working on a game with an entity-component system. I have a Manager class that is handling all of the components by feeding them into vectors set up for each type of component. Right now I have a lot of repeated code for each kind of component.

1
2
3
4
5
6
7
void add_pos_comp(shared_ptr<Position> pos_comp);
shared_ptr<Position> get_pos_comp(int id);
const vector<shared_ptr<Position>>& get_pos_comps();

void add_vel_comp(shared_ptr<Velocity> vel_comp);
shared_ptr<Velocity> get_vel_comp(int id);
const vector<shared_ptr<Velocity>>& get_vel_comps();


I've tried generalizing this with templates, but I've been confused by how I should organize the collection of vectors for each component. I had it setup so that they were all in an unordered_map, but I couldn't figure out how to get the templated functions to find the right map slot to use based simply on the template's paramaters.

Further, recently I've read a couple of professional game developers that say they simply don't allow templates to be used in their developement at all. Any general advice for how to proceed would be appreciated, including suggestions to use a completely different approach. Thanks.

The full project is here for reference: https://github.com/kabbotta/last-ditch-cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <unordered_map>
#include <memory>
#include <vector>

template < typename COMPONENT > struct attribute_collection
{
    using POINTER = std::shared_ptr<COMPONENT> ;
    using ID = int ;
    std::unordered_map< ID, POINTER > attributes ;

    void add( ID id, const POINTER& c ) { attributes.emplace( id, c ); }

    POINTER get( ID id ) const
    {
        const auto iter = attributes.find(id) ;
        return iter != attributes.end() ? iter->second : nullptr ;
    }

    std::vector<POINTER> all() const
    {
        std::vector<POINTER> result ;
        for( const auto pair : attributes ) result.push_back(pair.second) ;
        return result ;
    }
};

template < typename... > struct components_have {} ;
template < typename T > struct components_have<T> : protected attribute_collection<T> {} ;
template < typename FIRST, typename... REST >
struct components_have<FIRST,REST...> : protected attribute_collection<FIRST>, protected components_have<REST...> {} ;

struct position { /* ... */ };
struct velocity { /* ... */ };
struct orientation { /* ... */ };

struct entity : components_have<position,velocity,orientation>
{
    template < typename COMPONENT >
    void add_component( int id, const std::shared_ptr<COMPONENT>& c )
    { attribute_collection<COMPONENT>::add(id,c) ; }

    template < typename COMPONENT > std::shared_ptr<COMPONENT> get( int id ) const
    { return attribute_collection<COMPONENT>::get(id) ; }

    template < typename COMPONENT > std::vector< std::shared_ptr<COMPONENT> > all() const
    { return attribute_collection<COMPONENT>::all() ; }
};

int main()
{
   entity e ;

   e.add_component( 99, std::make_shared<position>() ) ;
   e.add_component( 56, std::make_shared<position>() ) ;
   e.add_component( 234, std::make_shared<position>() ) ;

   e.add_component( 22, std::make_shared<velocity>() ) ;
   e.add_component( 38, std::make_shared<velocity>() ) ;

   auto v = e.all<position>() ;

   std::cout << v.size() << '\n' // 3
             << e.all<velocity>().size() << '\n' // 2
             << std::boolalpha << ( e.get<position>(56) != nullptr ) << '\n' // true
             << ( e.get<velocity>(45) != nullptr ) << '\n' ; // false
}

http://coliru.stacked-crooked.com/a/0c4bd05971894256
Wow. Thank you! This was very helpful. It really helped clear up some of the confusion I had about templates in this case, and it gave me some new ideas to work with. Thank you for your time.
Topic archived. No new replies allowed.