neoGFX
Cross-platform C++ app/game engine
Loading...
Searching...
No Matches
component.hpp
Go to the documentation of this file.
1// component.hpp
2/*
3 * Copyright (c) 2018, 2020 Leigh Johnston.
4 *
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are
9 * met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * * Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * * Neither the name of Leigh Johnston nor the names of any
19 * other contributors to this software may be used to endorse or
20 * promote products derived from this software without specific prior
21 * written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
24 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35
36#pragma once
37
38#include <neolib/neolib.hpp>
39#include <atomic>
40#include <vector>
41#include <unordered_map>
42#include <string>
46#include <neolib/ecs/i_ecs.hpp>
47
48namespace neolib::ecs
49{
50 class i_ecs;
51
52 template <typename Data>
53 struct shared;
54
55 template <typename T>
56 inline bool batchable(const std::optional<T>& lhs, const std::optional<T>& rhs)
57 {
58 return !!lhs == !!rhs && (lhs == std::nullopt || batchable(*lhs, *rhs));
59 }
60
61 template <typename Data>
62 inline bool batchable(const shared<Data>& lhs, const shared<Data>& rhs)
63 {
64 if (!!lhs.ptr != !!rhs.ptr)
65 return false;
66 if (lhs.ptr == nullptr)
67 return true;
68 return batchable(*lhs.ptr, *rhs.ptr);
69 }
70
71 namespace detail
72 {
73 template <typename Data>
75 {
78 typedef std::vector<value_type> container_type;
79 static constexpr bool optional = false;
80 };
81
82 template <typename Data>
84 {
86 typedef std::optional<data_type> value_type;
87 typedef std::vector<value_type> container_type;
88 static constexpr bool optional = true;
89 };
90
91 template <typename Data>
93 {
96 typedef std::pair<const std::string, mapped_type> value_type;
97 typedef std::unordered_map<std::string, mapped_type> container_type;
98 static constexpr bool optional = false;
99 };
100
101 template <typename Data>
103 {
105 typedef std::optional<data_type> mapped_type;
106 typedef std::pair<const std::string, mapped_type> value_type;
107 typedef std::unordered_map<std::string, mapped_type> container_type;
108 static constexpr bool optional = true;
109 };
110 }
111
112 // Mutex tagged with component data type (visible in debugger) to help debugging multi-threaded issues
113 template <typename Data>
117
118 template <typename Data, typename Base>
119 class component_base : public Base
120 {
121 typedef component_base<ecs_data_type_t<Data>, Base> self_type;
122 public:
123 struct entity_record_not_found : std::logic_error { entity_record_not_found() : std::logic_error("neolib::component::entity_record_not_found") {} };
124 struct invalid_data : std::logic_error { invalid_data() : std::logic_error("neolib::component::invalid_data") {} };
125 public:
127 typedef typename data_type::meta data_meta_type;
130 public:
132 iEcs{ aEcs }
133 {
134 }
135 component_base(const self_type& aOther) :
136 iEcs{ aOther.iEcs },
137 iComponentData{ aOther.iComponentData }
138 {
139 }
140 public:
141 self_type& operator=(const self_type& aRhs)
142 {
143 iComponentData = aRhs.iComponentData;
144 return *this;
145 }
146 public:
147 i_ecs& ecs() const override
148 {
149 return iEcs;
150 }
151 const component_id& id() const override
152 {
153 return data_meta_type::id();
154 }
155 public:
156 component_mutex<Data>& mutex() const override
157 {
158 return iMutex;
159 }
160 public:
161 bool is_data_optional() const override
162 {
164 }
165 const neolib::i_string& name() const override
166 {
167 return data_meta_type::name();
168 }
169 uint32_t field_count() const override
170 {
171 return data_meta_type::field_count();
172 }
173 component_data_field_type field_type(uint32_t aFieldIndex) const override
174 {
175 return data_meta_type::field_type(aFieldIndex);
176 }
177 neolib::uuid field_type_id(uint32_t aFieldIndex) const override
178 {
179 return data_meta_type::field_type_id(aFieldIndex);
180 }
181 const neolib::i_string& field_name(uint32_t aFieldIndex) const override
182 {
183 return data_meta_type::field_name(aFieldIndex);
184 }
185 public:
187 {
188 return iComponentData;
189 }
191 {
192 return iComponentData;
193 }
194 const value_type& operator[](typename component_data_t::size_type aIndex) const
195 {
196 return *std::next(component_data().begin(), aIndex);
197 }
198 value_type& operator[](typename component_data_t::size_type aIndex)
199 {
200 return *std::next(component_data().begin(), aIndex);
201 }
202 private:
203 mutable component_mutex<Data> iMutex;
204 i_ecs& iEcs;
205 component_data_t iComponentData;
206 };
207
208 template <typename Data>
209 class component : public component_base<Data, i_component>
210 {
213 public:
214 using typename base_type::entity_record_not_found;
215 using typename base_type::invalid_data;
216 public:
221 typedef std::vector<entity_id> component_data_entities_t;
222 typedef typename component_data_t::size_type reverse_index_t;
223 typedef std::vector<reverse_index_t> reverse_indices_t;
224 public:
225 typedef std::unique_ptr<self_type> snapshot_ptr;
227 {
228 public:
230 iOwner{ aOwner }
231 {
232 ++iOwner.iUsingSnapshot;
233 }
235 iOwner{ aOther.iOwner }
236 {
237 ++iOwner.iUsingSnapshot;
238 }
240 {
241 --iOwner.iUsingSnapshot;
242 }
243 public:
245 {
246 return *iOwner.iSnapshot;
247 }
248 private:
249 self_type& iOwner;
250 };
251 private:
252 static constexpr reverse_index_t invalid = ~reverse_index_t{};
253 public:
254 component(i_ecs& aEcs) :
255 base_type{ aEcs },
256 iHaveSnapshot{ false },
257 iUsingSnapshot{ 0u }
258 {
259 }
260 component(const self_type& aOther) :
261 base_type{ aOther },
262 iEntities{ aOther.iEntities },
263 iReverseIndices{ aOther.iReverseIndices },
264 iHaveSnapshot{ false },
265 iUsingSnapshot{ 0u }
266 {
267 }
268 public:
270 {
272 iEntities = aRhs.iEntities;
273 iReverseIndices = aRhs.iReverseIndices;
274 return *this;
275 }
276 public:
277 using base_type::ecs;
278 using base_type::id;
279 using base_type::mutex;
280 public:
282 using base_type::name;
287 public:
289 using base_type::operator[];
290 public:
291 entity_id entity(const value_type& aData) const
292 {
293 const value_type* lhs = &aData;
294 const value_type* rhs = &base_type::component_data()[0];
295 auto index = lhs - rhs;
296 return entities()[index];
297 }
299 {
300 return iEntities;
301 }
303 {
304 return iEntities;
305 }
307 {
308 return iReverseIndices;
309 }
311 {
312 return iReverseIndices;
313 }
315 {
316 if (reverse_indices().size() > aEntity)
317 return reverse_indices()[aEntity];
318 return invalid;
319 }
320 bool has_entity_record_no_lock(entity_id aEntity) const override
321 {
322 return reverse_index_no_lock(aEntity) != invalid;
323 }
325 {
326 auto reverseIndex = reverse_index_no_lock(aEntity);
327 if (reverseIndex == invalid)
328 throw entity_record_not_found();
329 return base_type::component_data()[reverseIndex];
330 }
331 value_type& entity_record_no_lock(entity_id aEntity, bool aCreate = false)
332 {
333 if (aCreate && !has_entity_record_no_lock(aEntity))
334 populate(aEntity, value_type{});
335 return const_cast<value_type&>(to_const(*this).entity_record_no_lock(aEntity));
336 }
338 {
339 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
340 return reverse_index_no_lock(aEntity);
341 }
342 bool has_entity_record(entity_id aEntity) const override
343 {
344 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
345 return has_entity_record_no_lock(aEntity);
346 }
347 const value_type& entity_record(entity_id aEntity) const
348 {
349 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
350 return entity_record_no_lock(aEntity);
351 }
352 value_type& entity_record(entity_id aEntity, bool aCreate = false)
353 {
354 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
355 return entity_record_no_lock(aEntity, aCreate);
356 }
357 void destroy_entity_record(entity_id aEntity) override
358 {
359 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
360 auto reverseIndex = reverse_index(aEntity);
361 if (reverseIndex == invalid)
362 throw entity_record_not_found();
363 if constexpr (data_meta_type::has_handles)
364 data_meta_type::free_handles(base_type::component_data()[reverseIndex], ecs());
366 base_type::component_data().pop_back();
367 auto tailEntity = entities().back();
368 std::swap(entities()[reverseIndex], entities().back());
369 entities().pop_back();
370 reverse_indices()[tailEntity] = reverseIndex;
371 reverse_indices()[aEntity] = invalid;
372 if (have_snapshot())
373 {
374 auto ss = snapshot();
375 if (ss.data().has_entity_record(aEntity))
376 ss.data().destroy_entity_record(aEntity);
377 }
378 }
379 value_type& populate(entity_id aEntity, const value_type& aData)
380 {
381 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
382 return do_populate(aEntity, aData);
383 }
385 {
386 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
387 return do_populate(aEntity, aData);
388 }
389 const void* populate(entity_id aEntity, const void* aComponentData, std::size_t aComponentDataSize) override
390 {
391 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
392 if ((aComponentData == nullptr && !is_data_optional()) || aComponentDataSize != sizeof(data_type))
393 throw invalid_data();
394 if (aComponentData != nullptr)
395 return &do_populate(aEntity, *static_cast<const data_type*>(aComponentData));
396 else
397 return &do_populate(aEntity, value_type{}); // empty optional
398 }
399 public:
400 bool have_snapshot() const
401 {
402 return iHaveSnapshot;
403 }
405 {
406 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
407 if (!iUsingSnapshot)
408 {
409 if (iSnapshot == nullptr)
410 iSnapshot = snapshot_ptr{ new self_type{*this} };
411 else
412 *iSnapshot = *this;
413 iHaveSnapshot = true;
414 }
415 }
417 {
418 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
419 return scoped_snapshot{ *this };
420 }
421 template <typename Compare>
422 void sort(Compare aComparator)
423 {
424 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
426 [this](auto lhs, auto rhs)
427 {
428 std::swap(*lhs, *rhs);
429 auto lhsIndex = lhs - base_type::component_data().begin();
430 auto rhsIndex = rhs - base_type::component_data().begin();
431 auto& lhsEntity = entities()[lhsIndex];
432 auto& rhsEntity = entities()[rhsIndex];
433 std::swap(lhsEntity, rhsEntity);
434 if (lhsEntity != invalid)
435 reverse_indices()[lhsEntity] = lhsIndex;
436 if (rhsEntity != invalid)
437 reverse_indices()[rhsEntity] = rhsIndex;
438 }, aComparator);
439 }
440 public:
441 template <typename Callable>
442 void apply(const Callable& aCallable)
443 {
444 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
445 for (auto& data : component_data())
446 aCallable(*this, data);
447 }
448 template <typename Callable>
449 void parallel_apply(const Callable& aCallable, std::size_t aMinimumParallelismCount = 0)
450 {
451 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
452 neolib::parallel_apply(ecs().thread_pool(), component_data(), [&](value_type& aData) { aCallable(*this, aData); }, aMinimumParallelismCount);
453 }
454 private:
455 template <typename T>
456 value_type& do_populate(entity_id aEntity, T&& aComponentData)
457 {
458 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
459 if (has_entity_record(aEntity))
460 return do_update(aEntity, aComponentData);
461 reverse_index_t reverseIndex = invalid;
462 reverseIndex = base_type::component_data().size();
463 base_type::component_data().push_back(std::forward<T>(aComponentData));
464 try
465 {
466 entities().push_back(aEntity);
467 }
468 catch (...)
469 {
470 base_type::component_data().pop_back();
471 throw;
472 }
473 try
474 {
475 if (reverse_indices().size() <= aEntity)
476 reverse_indices().resize(aEntity + 1, invalid);
477 reverse_indices()[aEntity] = reverseIndex;
478 }
479 catch (...)
480 {
481 entities()[reverseIndex] = null_entity;
482 throw;
483 }
484 return base_type::component_data()[reverseIndex];
485 }
486 template <typename T>
487 value_type& do_update(entity_id aEntity, T&& aComponentData)
488 {
489 std::scoped_lock<component_mutex<Data>> lock{ mutex() };
490 auto& record = entity_record(aEntity);
491 record = aComponentData;
492 return record;
493 }
494 private:
496 reverse_indices_t iReverseIndices;
497 mutable std::atomic<bool> iHaveSnapshot;
498 mutable std::atomic<uint32_t> iUsingSnapshot;
499 mutable snapshot_ptr iSnapshot;
500 };
501
502 template <typename Data>
503 struct shared
504 {
506
508
510 ptr{ nullptr }
511 {
512 }
513 shared(const mapped_type* aData) :
514 ptr{ aData }
515 {
516 }
517 shared(const mapped_type& aData) :
518 ptr { &aData }
519 {
520 }
521 };
522
523 template <typename Data>
524 class shared_component : public component_base<shared<ecs_data_type_t<Data>>, i_shared_component>
525 {
526 typedef shared_component<Data> self_type;
528 public:
529 using typename base_type::entity_record_not_found;
530 using typename base_type::invalid_data;
531 public:
536 typedef typename component_data_t::mapped_type mapped_type;
537 public:
539 base_type{ aEcs }
540 {
541 }
542 public:
543 using base_type::ecs;
544 using base_type::id;
545 using base_type::mutex;
546 public:
548 using base_type::name;
553 public:
555 public:
556 const mapped_type& operator[](typename component_data_t::size_type aIndex) const
557 {
558 return std::next(component_data().begin(), aIndex)->second;
559 }
560 mapped_type& operator[](typename component_data_t::size_type aIndex)
561 {
562 return std::next(component_data().begin(), aIndex)->second;
563 }
564 const mapped_type& operator[](const std::string& aName) const
565 {
566 return component_data()[aName];
567 }
568 mapped_type& operator[](const std::string& aName)
569 {
570 return component_data()[aName];
571 }
572 public:
573 shared<mapped_type> populate(const std::string& aName, const mapped_type& aData)
574 {
575 base_type::component_data()[aName] = aData;
576 auto& result = base_type::component_data()[aName];
577 if constexpr (mapped_type::meta::has_updater)
578 mapped_type::meta::update(result, ecs(), null_entity);
579 return shared<mapped_type> { &result };
580 }
581 shared<mapped_type> populate(const std::string& aName, mapped_type&& aData)
582 {
583 base_type::component_data()[aName] = std::move(aData);
584 auto& result = base_type::component_data()[aName];
585 if constexpr (mapped_type::meta::has_updater)
586 mapped_type::meta::update(result, ecs(), null_entity);
587 return shared<mapped_type> { &result };
588 }
589 const void* populate(const std::string& aName, const void* aComponentData, std::size_t aComponentDataSize) override
590 {
591 if ((aComponentData == nullptr && !is_data_optional()) || aComponentDataSize != sizeof(mapped_type))
592 throw invalid_data();
593 if (aComponentData != nullptr)
594 return populate(aName, *static_cast<const mapped_type*>(aComponentData)).ptr;
595 else
596 return populate(aName, mapped_type{}).ptr; // empty optional
597 }
598 };
599}
scoped_snapshot(const scoped_snapshot &aOther)
detail::crack_component_data< Data >::value_type value_type
const component_id & id() const override
const neolib::i_string & name() const override
component_base(const self_type &aOther)
bool is_data_optional() const override
value_type & operator[](typename component_data_t::size_type aIndex)
detail::crack_component_data< Data >::container_type component_data_t
component_data_t & component_data()
const neolib::i_string & field_name(uint32_t aFieldIndex) const override
self_type & operator=(const self_type &aRhs)
data_type::meta data_meta_type
component_mutex< Data > & mutex() const override
neolib::uuid field_type_id(uint32_t aFieldIndex) const override
detail::crack_component_data< Data >::data_type data_type
const component_data_t & component_data() const
uint32_t field_count() const override
i_ecs & ecs() const override
const value_type & operator[](typename component_data_t::size_type aIndex) const
component_data_field_type field_type(uint32_t aFieldIndex) const override
component_data_t::size_type reverse_index_t
entity_id entity(const value_type &aData) const
base_type::component_data_t component_data_t
reverse_index_t reverse_index(entity_id aEntity) const
component_data_entities_t & entities()
void apply(const Callable &aCallable)
std::unique_ptr< self_type > snapshot_ptr
std::vector< reverse_index_t > reverse_indices_t
value_type & entity_record_no_lock(entity_id aEntity, bool aCreate=false)
value_type & populate(entity_id aEntity, const value_type &aData)
base_type::data_type data_type
reverse_indices_t & reverse_indices()
scoped_snapshot snapshot()
base_type::value_type value_type
bool has_entity_record(entity_id aEntity) const override
void parallel_apply(const Callable &aCallable, std::size_t aMinimumParallelismCount=0)
bool have_snapshot() const
value_type & entity_record(entity_id aEntity, bool aCreate=false)
bool has_entity_record_no_lock(entity_id aEntity) const override
value_type & populate(entity_id aEntity, value_type &&aData)
const reverse_indices_t & reverse_indices() const
void destroy_entity_record(entity_id aEntity) override
void sort(Compare aComparator)
std::vector< entity_id > component_data_entities_t
component(const self_type &aOther)
const void * populate(entity_id aEntity, const void *aComponentData, std::size_t aComponentDataSize) override
const value_type & entity_record_no_lock(entity_id aEntity) const
const component_data_entities_t & entities() const
base_type::data_meta_type data_meta_type
const value_type & entity_record(entity_id aEntity) const
self_type & operator=(const self_type &aRhs)
reverse_index_t reverse_index_no_lock(entity_id aEntity) const
const mapped_type & operator[](typename component_data_t::size_type aIndex) const
mapped_type & operator[](const std::string &aName)
const void * populate(const std::string &aName, const void *aComponentData, std::size_t aComponentDataSize) override
base_type::component_data_t component_data_t
component_data_t::mapped_type mapped_type
base_type::data_type data_type
const mapped_type & operator[](const std::string &aName) const
base_type::value_type value_type
shared< mapped_type > populate(const std::string &aName, mapped_type &&aData)
shared< mapped_type > populate(const std::string &aName, const mapped_type &aData)
base_type::data_meta_type data_meta_type
mapped_type & operator[](typename component_data_t::size_type aIndex)
basic_size< coordinate > size
constexpr entity_id null_entity
Definition ecs_ids.hpp:52
id_t entity_id
Definition ecs_ids.hpp:51
std::remove_cv_t< std::remove_reference_t< _Ty > > ecs_data_type_t
constexpr auto invalid
Definition neolib.hpp:218
to_const_reference_t< T > to_const(T &&object)
Definition neolib.hpp:113
void intrusive_sort(RandomIt first, RandomIt last, Swapper swapper, Compare comp)
void parallel_apply(thread_pool &aThreadPool, Container &aContainer, std::function< void(typename Container::value_type &aElement)> aFunction, std::size_t aMinimumParallelismCount=0)
Definition plf_hive.h:79
void swap(plf::hive< element_type, allocator_type > &a, plf::hive< element_type, allocator_type > &b) noexcept(std::allocator_traits< allocator_type >::propagate_on_container_swap::value||std::allocator_traits< allocator_type >::is_always_equal::value)
Definition plf_hive.h:4776
it_type next(it_type it, const typename iterator_traits< it_type >::difference_type distance=1)
Definition plf_hive.h:89
std::pair< const std::string, mapped_type > value_type
Definition component.hpp:96
std::unordered_map< std::string, mapped_type > container_type
Definition component.hpp:97
std::vector< value_type > container_type
Definition component.hpp:78
shared(const mapped_type *aData)
const mapped_type * ptr
detail::crack_component_data< shared< Data > >::mapped_type mapped_type
shared(const mapped_type &aData)