diff options
Diffstat (limited to 'src/entity_manager.h')
-rw-r--r-- | src/entity_manager.h | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/src/entity_manager.h b/src/entity_manager.h new file mode 100644 index 0000000..b2ef0de --- /dev/null +++ b/src/entity_manager.h | |||
@@ -0,0 +1,230 @@ | |||
1 | #ifndef ENTITY_MANAGER_H_C5832F11 | ||
2 | #define ENTITY_MANAGER_H_C5832F11 | ||
3 | |||
4 | #include <map> | ||
5 | #include <vector> | ||
6 | #include <typeindex> | ||
7 | #include <set> | ||
8 | #include <stdexcept> | ||
9 | #include "component.h" | ||
10 | #include "util.h" | ||
11 | |||
12 | class EntityManager { | ||
13 | private: | ||
14 | |||
15 | struct EntityData { | ||
16 | std::map<std::type_index, std::unique_ptr<Component>> components; | ||
17 | }; | ||
18 | |||
19 | using database_type = std::vector<EntityData>; | ||
20 | |||
21 | public: | ||
22 | |||
23 | using id_type = database_type::size_type; | ||
24 | |||
25 | private: | ||
26 | |||
27 | database_type entities; | ||
28 | std::vector<bool> slotAvailable; | ||
29 | std::set<id_type> allEntities; | ||
30 | |||
31 | mutable std::map<std::set<std::type_index>, std::set<id_type>> | ||
32 | cachedComponents; | ||
33 | |||
34 | id_type nextEntityID = 0; | ||
35 | |||
36 | template <class T, class... R> | ||
37 | std::set<id_type> getEntitiesWithComponentsHelper( | ||
38 | std::set<std::type_index>& componentTypes) const | ||
39 | { | ||
40 | componentTypes.insert(typeid(T)); | ||
41 | |||
42 | return getEntitiesWithComponents<R...>(componentTypes); | ||
43 | } | ||
44 | |||
45 | template <class... R> | ||
46 | std::set<id_type> getEntitiesWithComponents( | ||
47 | std::set<std::type_index>& componentTypes) const | ||
48 | { | ||
49 | return getEntitiesWithComponentsHelper<R...>(componentTypes); | ||
50 | } | ||
51 | |||
52 | public: | ||
53 | |||
54 | EntityManager() = default; | ||
55 | |||
56 | EntityManager(const EntityManager& copy) = delete; | ||
57 | |||
58 | id_type emplaceEntity() | ||
59 | { | ||
60 | if (nextEntityID >= entities.size()) | ||
61 | { | ||
62 | // If the database is saturated, add a new element for the new entity. | ||
63 | entities.emplace_back(); | ||
64 | slotAvailable.push_back(false); | ||
65 | allEntities.insert(nextEntityID); | ||
66 | |||
67 | return nextEntityID++; | ||
68 | } else { | ||
69 | // If there is an available slot in the database, use it. | ||
70 | id_type id = nextEntityID++; | ||
71 | slotAvailable[id] = false; | ||
72 | allEntities.insert(id); | ||
73 | |||
74 | // Fast forward the next available slot pointer to an available slot. | ||
75 | while ((nextEntityID < entities.size()) && !slotAvailable[nextEntityID]) | ||
76 | { | ||
77 | nextEntityID++; | ||
78 | } | ||
79 | |||
80 | return id; | ||
81 | } | ||
82 | } | ||
83 | |||
84 | void deleteEntity(id_type entity) | ||
85 | { | ||
86 | if ((entity >= entities.size()) || slotAvailable[entity]) | ||
87 | { | ||
88 | throw std::invalid_argument("Cannot delete non-existent entity"); | ||
89 | } | ||
90 | |||
91 | // Uncache components | ||
92 | for (auto& cache : cachedComponents) | ||
93 | { | ||
94 | cache.second.erase(entity); | ||
95 | } | ||
96 | |||
97 | allEntities.erase(entity); | ||
98 | |||
99 | // Destroy the data | ||
100 | entities[entity].components.clear(); | ||
101 | |||
102 | // Mark the slot as available | ||
103 | slotAvailable[entity] = true; | ||
104 | |||
105 | if (entity < nextEntityID) | ||
106 | { | ||
107 | nextEntityID = entity; | ||
108 | } | ||
109 | } | ||
110 | |||
111 | template <class T, class... Args> | ||
112 | T& emplaceComponent(id_type entity, Args&&... args) | ||
113 | { | ||
114 | if ((entity >= entities.size()) || slotAvailable[entity]) | ||
115 | { | ||
116 | throw std::invalid_argument("Cannot get non-existent entity"); | ||
117 | } | ||
118 | |||
119 | EntityData& data = entities[entity]; | ||
120 | std::type_index componentType = typeid(T); | ||
121 | |||
122 | if (data.components.count(componentType)) | ||
123 | { | ||
124 | throw std::invalid_argument("Cannot emplace already-existent component"); | ||
125 | } | ||
126 | |||
127 | // Initialize the component | ||
128 | std::unique_ptr<T> ptr(new T(std::forward<Args>(args)...)); | ||
129 | T& component = *ptr; | ||
130 | data.components[componentType] = std::move(ptr); | ||
131 | |||
132 | // Invalidate related caches | ||
133 | erase_if( | ||
134 | cachedComponents, | ||
135 | [&componentType] ( | ||
136 | std::pair<const std::set<std::type_index>, std::set<id_type>>& cache) { | ||
137 | return cache.first.count(componentType) == 1; | ||
138 | }); | ||
139 | |||
140 | return component; | ||
141 | } | ||
142 | |||
143 | template <class T> | ||
144 | void removeComponent(id_type entity) | ||
145 | { | ||
146 | if ((entity >= entities.size()) || slotAvailable[entity]) | ||
147 | { | ||
148 | throw std::invalid_argument("Cannot get non-existent entity"); | ||
149 | } | ||
150 | |||
151 | EntityData& data = entities[entity]; | ||
152 | std::type_index componentType = typeid(T); | ||
153 | |||
154 | if (!data.components.count(componentType)) | ||
155 | { | ||
156 | throw std::invalid_argument("Cannot delete non-existent component"); | ||
157 | } | ||
158 | |||
159 | // Destroy the component | ||
160 | data.components.erase(componentType); | ||
161 | |||
162 | // Uncache the component | ||
163 | for (auto& cache : cachedComponents) | ||
164 | { | ||
165 | if (cache.first.count(componentType) == 1) | ||
166 | { | ||
167 | cache.second.erase(entity); | ||
168 | } | ||
169 | } | ||
170 | } | ||
171 | |||
172 | template <class T> | ||
173 | const T& getComponent(id_type entity) const | ||
174 | { | ||
175 | if ((entity >= entities.size()) || slotAvailable[entity]) | ||
176 | { | ||
177 | throw std::invalid_argument("Cannot get non-existent entity"); | ||
178 | } | ||
179 | |||
180 | const EntityData& data = entities[entity]; | ||
181 | std::type_index componentType = typeid(T); | ||
182 | |||
183 | if (!data.components.count(componentType)) | ||
184 | { | ||
185 | throw std::invalid_argument("Cannot get non-existent component"); | ||
186 | } | ||
187 | |||
188 | return *dynamic_cast<const T*>(data.components.at(componentType).get()); | ||
189 | } | ||
190 | |||
191 | template <class T> | ||
192 | T& getComponent(id_type entity) | ||
193 | { | ||
194 | return const_cast<T&>( | ||
195 | static_cast<const EntityManager&>(*this).getComponent<T>(entity)); | ||
196 | } | ||
197 | |||
198 | template <class T> | ||
199 | bool hasComponent(id_type entity) const | ||
200 | { | ||
201 | if ((entity >= entities.size()) || slotAvailable[entity]) | ||
202 | { | ||
203 | throw std::invalid_argument("Cannot get non-existent entity"); | ||
204 | } | ||
205 | |||
206 | const EntityData& data = entities[entity]; | ||
207 | std::type_index componentType = typeid(T); | ||
208 | |||
209 | return data.components.count(componentType); | ||
210 | } | ||
211 | |||
212 | template <class... R> | ||
213 | std::set<id_type> getEntitiesWithComponents() const | ||
214 | { | ||
215 | std::set<std::type_index> componentTypes; | ||
216 | |||
217 | return getEntitiesWithComponentsHelper<R...>(componentTypes); | ||
218 | } | ||
219 | |||
220 | const std::set<id_type>& getEntities() const | ||
221 | { | ||
222 | return allEntities; | ||
223 | } | ||
224 | }; | ||
225 | |||
226 | template <> | ||
227 | std::set<EntityManager::id_type> EntityManager::getEntitiesWithComponents<>( | ||
228 | std::set<std::type_index>& componentTypes) const; | ||
229 | |||
230 | #endif /* end of include guard: ENTITY_MANAGER_H_C5832F11 */ | ||