component.hxx 8.76 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::ecs {
10
11

	class Sparse_index_policy {
12
13
14
15
16
17
18
19
20
	  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;
21
22
	};
	class Compact_index_policy {
23
24
25
26
27
28
29
30
31
32
	  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;
33
34
35
	};


36
	template <class T>
37
	struct Pool_storage_policy_value_traits {
38
39
40
41
		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;
42

43
44
		static constexpr const Marker_type* marker_addr(const T* inst)
		{
45
46
47
			return T::component_base_t::marker_addr(inst);
		}
	};
48
	template <class T>
49
	constexpr bool Pool_storage_policy_value_traits<T>::supports_empty_values;
50
	template <class T>
51
	constexpr int_fast32_t Pool_storage_policy_value_traits<T>::max_free;
52
53
54
	template <class T>
	constexpr typename Pool_storage_policy_value_traits<T>::Marker_type
	        Pool_storage_policy_value_traits<T>::free_mark;
55
56


57
	template <std::size_t Chunk_size, class T>
58
	class Pool_storage_policy {
59
		using pool_t = util::pool<T, Chunk_size, Component_index, Pool_storage_policy_value_traits<T>>;
60

61
62
	  public:
		using iterator = typename pool_t::iterator;
63

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

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

72
		template <typename F>
73
74
		void erase(Component_index idx, F&& relocate)
		{
75
76
			_pool.erase(idx, std::forward<F>(relocate));
		}
77

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

80
		template <typename F>
81
82
		void shrink_to_fit(F&& relocate)
		{
83
84
			_pool.shrink_to_fit(std::forward<F>(relocate));
		}
85

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

88
89
90
91
92
93
94
		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;
95
96
97
98
	};



99
	template <class T>
100
101
102
103
104
	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);

105
	  public:
106
107
		Component_container(Entity_manager& m) : _manager(m)
		{
108
109
110
111
112
			T::_validate_type_helper();
			_index.clear();
		}

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

114
	  protected:
115
116
		void restore(Entity_handle owner, Deserializer& deserializer) override
		{
117
118
			auto entity_id = get_entity_id(owner, _manager);
			if(entity_id == invalid_entity_id) {
119
				MIRRAGE_FAIL("emplace_or_find_now of component from invalid/deleted entity");
120
121
			}

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

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

133
134
			load_component(deserializer, comp);
		}
135

136
137
		bool save(Entity_handle owner, Serializer& serializer) override
		{
138
139
140
141
142
143
			return find(owner).process(false, [&](T& comp) {
				serializer.write_value(T::name_save_as());
				save_component(serializer, comp);
				return true;
			});
		}
144

145
146
		void clear() override
		{
147
148
149
150
151
152
153
154
155
156
157
			_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);
			});
		}

158
159
		void process_queued_actions() override
		{
160
161
			process_deletions();
			process_insertions();
162

163
			if(_unoptimized_deletes > 32) {
164
165
166
167
168
169
				_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);
				});
			}
170
		}
171

172
173
		void process_deletions()
		{
174
175
176
177
178
179
180
181
182
			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) {
183
184
185
							LOG(plog::warning)
							        << "Discard delete of component " << T::name()
							        << " from invalid/deleted entity: " << entity_name(deletions_buffer[i]);
186
187
							continue;
						}
188

189
190
191
192
						auto comp_idx_mb = _index.find(entity_id);
						if(!comp_idx_mb) {
							continue;
						}
193

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

197
198
199
200
						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) {
201
								_storage.erase(comp_idx, [&](auto, auto& comp, auto new_idx) {
202
									_index.attach(comp.owner_handle().id(), new_idx);
203
								});
204
205
206
							} else {
								_storage.replace(comp_idx, std::move(std::get<0>(insertion)));
								_index.attach(std::get<1>(insertion).id(), comp_idx);
207
							}
208
209
210
211
212
213
						} 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++;
214
215
						}
					}
216
217
218
219
220
				} else {
					break;
				}
			} while(true);
		}
221
222
		void process_insertions()
		{
223
224
225
226
227
228
229
230
231
			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) {
232
233
234
							LOG(plog::warning)
							        << "Discard insertion of component from invalid/deleted entity: "
							        << entity_name(std::get<1>(insertions_buffer[i]));
235
							continue;
236
						}
237
238
239

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

247
248
	  public:
		template <typename F, typename... Args>
249
250
		void emplace(F&& init, Entity_handle owner, Args&&... args)
		{
251
			MIRRAGE_INVARIANT(owner != invalid_entity, "emplace on invalid entity");
252
253
254
255
256
257
258
259

			// 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));
		}
260

261
262
		void erase(Entity_handle owner) override
		{
263
			MIRRAGE_INVARIANT(owner, "erase on invalid entity");
264
265
			_queued_deletions.enqueue(owner);
		}
266

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

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

278
279
			return _index.find(entity_id).is_some();
		}
280

281
282
283
284
		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(); }
285

286
		using iterator = typename T::storage_policy::iterator;
287

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

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

294
295
		typename T::index_policy   _index;
		typename T::storage_policy _storage;
296

297
298
299
300
301
		Entity_manager&      _manager;
		Queue<Entity_handle> _queued_deletions;
		Queue<Insertion>     _queued_insertions;
		int                  _unoptimized_deletes = 0;
	};
302
} // namespace mirrage::ecs