]> git.tdb.fi Git - libs/game.git/commitdiff
Add utilities for keeping track of entities with specific components
authorMikko Rasa <tdb@tdb.fi>
Sat, 15 Mar 2025 15:15:45 +0000 (17:15 +0200)
committerMikko Rasa <tdb@tdb.fi>
Sat, 15 Mar 2025 15:15:45 +0000 (17:15 +0200)
source/game/archetype.h [new file with mode: 0644]
source/game/handle.h

diff --git a/source/game/archetype.h b/source/game/archetype.h
new file mode 100644 (file)
index 0000000..f6fef8c
--- /dev/null
@@ -0,0 +1,147 @@
+#ifndef MSP_GAME_ARCHETYPE_H_
+#define MSP_GAME_ARCHETYPE_H_
+
+#include <vector>
+#include <msp/core/algorithm.h>
+#include <msp/core/meta.h>
+#include "eventobserver.h"
+#include "events.h"
+#include "handle.h"
+
+namespace Msp::Game {
+
+template<auto first, auto... rest>
+       requires (std::is_same_v<typename PointerToMemberTraits<decltype(first)>::Class, typename PointerToMemberTraits<decltype(rest)>::Class> && ...)
+struct CommonClassOfMembers
+{
+       using Type = typename PointerToMemberTraits<decltype(first)>::Class;
+};
+
+template<auto first, auto... rest>
+struct ArchetypeComponents
+{
+       using Wrapper = typename CommonClassOfMembers<first, rest...>::Type;
+
+       template<typename F>
+       static void dispatch(const F &, Handle<Component>);
+
+       static bool all_null(const Wrapper &);
+};
+
+
+template<typename T>
+concept ValidArchetype =
+       std::is_same_v<typename T::Components::Wrapper, T> &&
+               requires(T t) { { t.entity } -> std::convertible_to<Handle<Entity>>; };
+
+
+template<typename T>
+       requires ValidArchetype<T>
+class ArchetypeMonitor
+{
+private:
+       EventObserver &event_observer;
+       std::vector<T> &entities;
+       std::function<void(T &)> changed_callback;
+
+public:
+       ArchetypeMonitor(EventObserver &, std::vector<T> &);
+
+       void set_changed_callback(std::function<void(T &)>);
+
+private:
+       void component_created(const Events::ComponentCreated &);
+       void component_destroyed(const Events::ComponentDestroyed &);
+
+       T &get_or_create_wrapper(Handle<Entity>);
+};
+
+
+template<auto first, auto... rest>
+template<typename F>
+void ArchetypeComponents<first, rest...>::dispatch(const F &func, Handle<Component> comp)
+{
+       if(auto cast_comp = dynamic_handle_cast<typename PointerToMemberTraits<decltype(first)>::Type::element_type>(comp))
+               func(cast_comp, first);
+       else if constexpr(sizeof...(rest)>0)
+               ArchetypeComponents<rest...>::dispatch(func, comp);
+}
+
+template<auto first, auto... rest>
+bool ArchetypeComponents<first, rest...>::all_null(const Wrapper &wrapper)
+{
+       return (!(wrapper.*first) && ... && !(wrapper.*rest));
+}
+
+
+template<typename T>
+       requires ValidArchetype<T>
+ArchetypeMonitor<T>::ArchetypeMonitor(EventObserver &o, std::vector<T> &v):
+       event_observer(o),
+       entities(v)
+{
+       event_observer.observe<Events::ComponentCreated>([this](auto &e){ component_created(e); });
+       event_observer.observe<Events::ComponentDestroyed>([this](auto &e){ component_destroyed(e); });
+}
+
+template<typename T>
+       requires ValidArchetype<T>
+void ArchetypeMonitor<T>::set_changed_callback(std::function<void(T &)> cb)
+{
+       changed_callback = cb;
+}
+
+template<typename T>
+       requires ValidArchetype<T>
+void ArchetypeMonitor<T>::component_created(const Events::ComponentCreated &event)
+{
+       T::Components::dispatch([this]<typename C>(Handle<C> comp, Handle<C> T::*member){
+               Handle<Entity> entity = comp->get_entity();
+               T &wrapper = get_or_create_wrapper(entity);
+               wrapper.*member = comp;
+
+               if(changed_callback)
+                       changed_callback(wrapper);
+       }, event.component);
+}
+
+template<typename T>
+       requires ValidArchetype<T>
+void ArchetypeMonitor<T>::component_destroyed(const Events::ComponentDestroyed &event)
+{
+       T::Components::dispatch([this]<typename C>(Handle<C> comp, Handle<C> T::*member){
+               Handle<Entity> entity = comp->get_entity();
+               auto i = lower_bound_member(entities, entity, &T::entity);
+               if(i!=entities.end() && i->entity==entity)
+               {
+                       (*i).*member = nullptr;
+                       if(changed_callback)
+                               changed_callback(*i);
+               }
+
+               if(T::Components::all_null(*i))
+                       entities.erase(i);
+       }, event.component);
+}
+
+template<typename T>
+       requires ValidArchetype<T>
+T &ArchetypeMonitor<T>::get_or_create_wrapper(Handle<Entity> entity)
+{
+       auto i = lower_bound_member(entities, entity, &T::entity);
+       if(i!=entities.end() && i->entity==entity)
+               return *i;
+
+       if constexpr(std::is_constructible_v<T, Handle<Entity>>)
+               return *entities.emplace(i, entity);
+       else
+       {
+               auto &e = entities.emplace(i);
+               e.entity = entity;
+               return e;
+       }
+}
+
+} // namespace Msp::Game
+
+#endif
index 183fc416dc47617d6de4ba28fc720c9008c4cd5f..73d4d1117ae96d16549996f058dee5df4d0a1dfd 100644 (file)
@@ -18,6 +18,9 @@ class Handle
        template<typename U>
        friend class Handle;
 
+public:
+       using element_type = T;
+
 protected:
        T *ptr = nullptr;