component.hxx 8.77 KB
Newer Older
1
2
3
4
5
6
7
8
#pragma once

#include <moodycamel/concurrentqueue.hpp>

#ifndef ECS_COMPONENT_INCLUDED
#include "component.hpp"
#endif

9
namespace mirrage {
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
namespace ecs {


	class Sparse_index_policy {
		public:
			void attach(Entity_id, Component_index);
			void detach(Entity_id);
			void shrink_to_fit();
			auto find(Entity_id)const -> util::maybe<Component_index>;
			void clear();

		private:
			std::unordered_map<Entity_id, Component_index> _table;
	};
	class Compact_index_policy {
		public:
			Compact_index_policy();
			void attach(Entity_id, Component_index);
			void detach(Entity_id);
			void shrink_to_fit();
			auto find(Entity_id)const -> util::maybe<Component_index>;
			void clear();

		private:
			std::vector<Component_index> _table;
	};


	template<class T>
	struct Pool_storage_policy_value_traits {
		static constexpr bool supports_empty_values = true;
		static constexpr int_fast32_t max_free = 8;
		using Marker_type = Entity_handle;
		static constexpr Marker_type free_mark = invalid_entity;

		static constexpr const Marker_type* marker_addr(const T* inst) {
			return T::component_base_t::marker_addr(inst);
		}
	};
	template<class T>
	constexpr bool Pool_storage_policy_value_traits<T>::supports_empty_values;
	template<class T>
	constexpr int_fast32_t Pool_storage_policy_value_traits<T>::max_free;
	template<class T>
	constexpr typename Pool_storage_policy_value_traits<T>::Marker_type Pool_storage_policy_value_traits<T>::free_mark;


	template<std::size_t Chunk_size, class T>
	class Pool_storage_policy {
			using pool_t = util::pool<T, Chunk_size, Component_index, Pool_storage_policy_value_traits<T>>;
		public:
			using iterator = typename pool_t::iterator;

			template<class... Args>
			auto emplace(Args&&... args) -> std::tuple<T&, Component_index> {
				return _pool.emplace_back(std::forward<Args>(args)...);
			}

			void replace(Component_index idx, T&& new_element) {
				_pool.replace(idx, std::move(new_element));
			}

			template<typename F>
			void erase(Component_index idx, F&& relocate) {
				_pool.erase(idx, std::forward<F>(relocate));
			}

			void clear() {
				_pool.clear();
			}

			template<typename F>
			void shrink_to_fit(F&& relocate) {
				_pool.shrink_to_fit(std::forward<F>(relocate));
			}

			auto get(Component_index idx) -> T& {
				return _pool.get(idx);
			}

			auto begin()noexcept -> iterator {
				return _pool.begin();
			}
			auto end()noexcept -> iterator {
				return _pool.end();
			}
			auto size()const -> Component_index {
				return _pool.size();
			}
			auto empty()const -> bool {
				return _pool.empty();
			}

		private:
			pool_t _pool;
	};



	template<class T>
	class Component_container : public Component_container_base {
		friend class Entity_manager;
		friend void load(sf2::JsonDeserializer& s, Entity_handle& e);
		friend void save(sf2::JsonSerializer& s, const Entity_handle& e);

		public:
			Component_container(Entity_manager& m) : _manager(m) {
				T::_validate_type_helper();
				_index.clear();
			}

			auto value_type()const noexcept -> Component_type override {
				return component_type_id<T>();
			}

		protected:
			void restore(Entity_handle owner, Deserializer& deserializer)override {
				auto entity_id = get_entity_id(owner, _manager);
				if(entity_id==invalid_entity_id) {
					FAIL("emplace_or_find_now of component from invalid/deleted entity");
				}

				auto& comp = [&]() -> T& {
					auto comp_idx =_index.find(entity_id);
					if(comp_idx.is_some()) {
						return _storage.get(comp_idx.get_or_throw());
					}

					auto comp = _storage.emplace(_manager, owner);
					_index.attach(entity_id, std::get<1>(comp));
					return std::get<0>(comp);
				}();

				load_component(deserializer, comp);
			}

			bool save(Entity_handle owner, Serializer& serializer)override {
				return find(owner).process(false, [&](T& comp) {
148
					serializer.write_value(T::name_save_as());
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
					save_component(serializer, comp);
					return true;
				});
			}

			void clear() override {
				_queued_deletions = Queue<Entity_handle>{}; // clear by moving a new queue into the old
				_queued_insertions = Queue<Insertion>{}; // clear by moving a new queue into the old
				_index.clear();
				_storage.clear();
				_unoptimized_deletes = 0;
				_index.shrink_to_fit();
				_storage.shrink_to_fit([&](auto, auto& comp, auto new_idx) {
					_index.attach(comp.owner_handle().id(), new_idx);
				});
			}

			void process_queued_actions() override {
				process_deletions();
				process_insertions();

				if(_unoptimized_deletes>32) {
					_unoptimized_deletes = 0;
					_index.shrink_to_fit();
					_storage.shrink_to_fit([&](auto, auto& comp, auto new_idx) {
						_index.attach(comp.owner_handle().id(), new_idx);
					});
				}
			}

			void process_deletions() {
				std::array<Entity_handle, 16> deletions_buffer;

				do {
					std::size_t deletions = _queued_deletions.try_dequeue_bulk(deletions_buffer.data(),
					                                                           deletions_buffer.size());
					if(deletions>0) {
						for(auto i=0ull; i<deletions; i++) {
							auto entity_id = get_entity_id(deletions_buffer[i], _manager);
							if(entity_id==invalid_entity_id) {
								WARN("Discard delete of component "<<T::name()<<" from invalid/deleted entity: "<<entity_name(deletions_buffer[i]));
								continue;
							}

							auto comp_idx_mb = _index.find(entity_id);
							if(!comp_idx_mb) {
								continue;
							}

							auto comp_idx = comp_idx_mb.get_or_throw();
							_index.detach(entity_id);

							Insertion insertion;
							if(_queued_insertions.try_dequeue(insertion)) {
								auto entity_id = get_entity_id(std::get<1>(insertion), _manager);
								if(entity_id==invalid_entity_id) {
									_storage.erase(comp_idx, [&](auto, auto& comp, auto new_idx) {
										_index.attach(comp.owner_handle().id(), new_idx);
									});

								} else {
									_storage.replace(comp_idx, std::move(std::get<0>(insertion)));
									_index.attach(std::get<1>(insertion).id(), comp_idx);
								}
							} else {
								_storage.erase(comp_idx, [&](auto, auto& comp, auto new_idx) {
									auto entity_id = get_entity_id(comp.owner_handle(), _manager);
									_index.attach(entity_id, new_idx);
								});
								_unoptimized_deletes++;
							}
						}
					} else {
						break;
					}
				} while(true);
			}
			void process_insertions() {
				std::array<Insertion, 8> insertions_buffer;

				do {
					std::size_t insertions = _queued_insertions.try_dequeue_bulk(insertions_buffer.data(),
					                                                             insertions_buffer.size());
					if(insertions>0) {
						for(auto i=0ull; i<insertions; i++) {
							auto entity_id = get_entity_id(std::get<1>(insertions_buffer[i]), _manager);
							if(entity_id==invalid_entity_id) {
								WARN("Discard insertion of component from invalid/deleted entity: "<<entity_name(std::get<1>(insertions_buffer[i])));
								continue;
							}

							auto comp = _storage.emplace(std::move(std::get<0>(insertions_buffer[i])));
							_index.attach(entity_id, std::get<1>(comp));
						}
					} else {
						break;
					}
				} while(true);
			}

		public:
			template<typename F, typename... Args>
			void emplace(F&& init, Entity_handle owner, Args&&... args) {
				INVARIANT(owner!=invalid_entity, "emplace on invalid entity");

				// construct T inplace inside the pair to avoid additional move
				auto inst = Insertion(std::piecewise_construct,
				                      std::forward_as_tuple(_manager, owner, std::forward<Args>(args)...),
				                      std::forward_as_tuple(owner));
				std::forward<F>(init)(inst.first);
				_queued_insertions.enqueue(std::move(inst));
			}

			void erase(Entity_handle owner)override {
				INVARIANT(owner, "erase on invalid entity");
				_queued_deletions.enqueue(owner);
			}

			auto find(Entity_handle owner) -> util::maybe<T&> {
				auto entity_id = get_entity_id(owner, _manager);

				return _index.find(entity_id).process(util::maybe<T&>(), [&](auto comp_idx) {
					return util::justPtr(&_storage.get(comp_idx));
				});
			}
			auto has(Entity_handle owner)const -> bool {
				auto entity_id = get_entity_id(owner, _manager);

				return _index.find(entity_id).is_some();
			}

			auto begin()noexcept {
				return _storage.begin();
			}
			auto end()noexcept {
				return _storage.end();
			}
			auto size()const noexcept {
				return _storage.size();
			}
			auto empty()const noexcept {
				return _storage.empty();
			}
			
			using iterator = typename T::storage_policy::iterator;

		private:
			using Insertion = std::pair<T,Entity_handle>;

			template<class E>
			using Queue = moodycamel::ConcurrentQueue<E>;

			typename T::index_policy   _index;
			typename T::storage_policy _storage;

			Entity_manager&      _manager;
			Queue<Entity_handle> _queued_deletions;
			Queue<Insertion>     _queued_insertions;
			int                  _unoptimized_deletes = 0;
	};

}
}