]> git.tdb.fi Git - libs/game.git/commitdiff
Add reflection infrastructure
authorMikko Rasa <tdb@tdb.fi>
Sat, 3 Dec 2022 08:47:45 +0000 (10:47 +0200)
committerMikko Rasa <tdb@tdb.fi>
Sat, 3 Dec 2022 13:17:10 +0000 (15:17 +0200)
This allows checking for type relationships at runtime, which will be
useful for annotating system dependencies and iterating over subclasses.

source/game/director.cpp
source/game/director.h
source/game/owned.h
source/game/pool.h
source/game/reflection.cpp [new file with mode: 0644]
source/game/reflection.h [new file with mode: 0644]
source/game/stage.cpp
source/game/stage.h

index 4a4c1265e82d12ba897f65f1879b8db8f6b7ac33..53b2e7f9a3a0c8668580a139355133eebe7b3f61 100644 (file)
@@ -1,6 +1,8 @@
 #include "director.h"
 #include <stdexcept>
 #include <msp/time/utils.h>
+#include "component.h"
+#include "entity.h"
 #include "stage.h"
 
 using namespace std;
@@ -14,6 +16,9 @@ Director::Director(DataFile::Collection &r):
 #if DEBUG
        access_guard.emplace();
 #endif
+
+       reflector.get_or_create_class<Entity>().set_polymorphic_base<Entity>();
+       reflector.get_or_create_class<Component>().set_polymorphic_base<Component>();
 }
 
 // Hide ~unique_ptr<Stage> from the header
@@ -22,7 +27,7 @@ Director::~Director()
 
 Stage &Director::create_stage()
 {
-       stages.emplace_back(std::make_unique<Stage>(std::ref(resources)));
+       stages.emplace_back(std::make_unique<Stage>(std::ref(reflector), std::ref(resources)));
        event_source.emit<Events::StageCreated>(std::ref(*stages.back()));
        return *stages.back();
 }
index 1be3e8fa31bbf59c892866c436439ed7ee8c87da..775d7d2915941a2edb8247a47cddaaceca6277b1 100644 (file)
@@ -11,6 +11,7 @@
 #include "eventbus.h"
 #include "events.h"
 #include "eventsource.h"
+#include "reflection.h"
 
 namespace Msp::Game {
 
@@ -23,6 +24,7 @@ public:
 
 private:
        std::optional<AccessGuard> access_guard;
+       Reflection::Reflector reflector;
        DataFile::Collection &resources;
        EventBus event_bus;
        EventSource event_source;
index fc2acab6ecb02ab0d4079630dae5f5fe5b8fc939..d6f9f17a9ce170d7456dbafe08b3e53fe1a29418 100644 (file)
@@ -53,14 +53,19 @@ Owned<T>::Owned(Handle<P> parent, Args &&... args)
 
        Stage &stage = get_stage(*parent);
        Pool<T> &pool = stage.get_pools().get_pool<T>();
+       bool first_created = !pool.get_capacity();
        this->ptr = pool.create(parent, std::forward<Args>(args)...);
        if constexpr(std::is_base_of_v<Component, T>)
        {
+               if(first_created)
+                       stage.get_reflector().get_or_create_class<T>().template set_polymorphic_base<Component>(**this);
                parent->add_component(*this);
                stage.get_event_source().emit<Events::ComponentCreated>(*this);
        }
        else
        {
+               if(first_created)
+                       stage.get_reflector().get_or_create_class<T>().template set_polymorphic_base<Entity>(**this);
                parent->add_child(*this);
                stage.get_event_source().emit<Events::EntityCreated>(*this);
        }
index 9b55e1df5f192e1d844796b2aa80fa3ce81c72d9..2b8e799d158b17e12bcb9f3c277f81bb553fa918 100644 (file)
@@ -72,6 +72,7 @@ private:
        void add_block();
 
 public:
+       std::uint32_t get_capacity() const { return capacity; }
        void destroy(void *);
 };
 
diff --git a/source/game/reflection.cpp b/source/game/reflection.cpp
new file mode 100644 (file)
index 0000000..d188e50
--- /dev/null
@@ -0,0 +1,28 @@
+#include "reflection.h"
+#include <msp/debug/demangle.h>
+
+using namespace std;
+
+namespace Msp::Game {
+namespace Reflection {
+
+ClassBase::ClassBase(Reflector &r, type_index t):
+       reflector(r),
+       type(t),
+       name(Debug::demangle(t.name()))
+{ }
+
+bool ClassBase::is_direct_base_of(const ClassBase &other) const
+{
+       return ranges::find(other.bases, this)!=other.bases.end();
+}
+
+bool ClassBase::is_base_of(const ClassBase &other) const
+{
+       if(is_direct_base_of(other))
+               return true;
+       return ranges::any_of(other.bases, [this](const ClassBase *b){ return is_base_of(*b); });
+}
+
+} // namespace Reflection
+} // namespace Msp::Game
diff --git a/source/game/reflection.h b/source/game/reflection.h
new file mode 100644 (file)
index 0000000..df974b1
--- /dev/null
@@ -0,0 +1,194 @@
+#ifndef MSP_GAME_REFLECTION_H_
+#define MSP_GAME_REFLECTION_H_
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <typeindex>
+#include <vector>
+
+namespace Msp::Game {
+namespace Reflection {
+
+class ClassBase;
+class Reflector;
+
+class PolymorphismBase
+{
+public:
+       virtual ~PolymorphismBase() = default;
+
+       virtual bool is_instance_of(const void *, const PolymorphismBase &) const = 0;
+};
+
+
+template<typename B>
+class RootedPolymorphism: public PolymorphismBase
+{
+public:
+       virtual bool is_instance(const B &) const = 0;
+};
+
+
+template<typename T, typename B>
+       requires std::is_base_of_v<B, T>
+class Polymorphism: public RootedPolymorphism<B>
+{
+public:
+       bool is_instance(const B &obj) const override { return std::is_same_v<B, T> || dynamic_cast<const T *>(&obj); }
+       bool is_instance_of(const void *, const PolymorphismBase &) const override;
+};
+
+
+class ClassBase
+{
+protected:
+       Reflector &reflector;
+       std::type_index type;
+       std::string name;
+       std::vector<const ClassBase *> bases;
+       std::unique_ptr<PolymorphismBase> polymorphism;
+
+       ClassBase(Reflector &, std::type_index);
+public:
+       virtual ~ClassBase() = default;
+
+       const std::type_index &get_type() const { return type; }
+       const std::string &get_name() const { return name; }
+
+       bool is_direct_base_of(const ClassBase &) const;
+       bool is_base_of(const ClassBase &) const;
+
+       template<typename T>
+       bool is_instance(const T &) const;
+
+       template<typename T>
+       bool has_polymorphic_base() const { return dynamic_cast<const RootedPolymorphism<T> *>(polymorphism.get()); }
+};
+
+
+template<typename T>
+class Class: public ClassBase
+{
+public:
+       Class(Reflector &r): ClassBase(r, typeid(T)) { }
+
+       template<typename B>
+       void set_polymorphic_base();
+
+       template<typename B>
+       void set_polymorphic_base(const T &obj) { set_polymorphic_base<B>(); check_bases<B>(obj); }
+
+private:
+       template<typename B>
+       void check_bases(const T &obj);
+};
+
+
+class Reflector
+{
+private:
+       std::vector<std::unique_ptr<ClassBase>> classes;
+
+       std::vector<std::unique_ptr<ClassBase>>::const_iterator lower_bound(const std::type_index &) const;
+
+public:
+       template<typename T>
+       Class<T> *find_class() const;
+
+       template<typename T>
+       Class<T> &get_or_create_class();
+
+       template<typename F>
+       std::vector<ClassBase *> find_classes_if(F &&) const;
+};
+
+
+inline std::vector<std::unique_ptr<ClassBase>>::const_iterator Reflector::lower_bound(const std::type_index &type) const
+{
+       return std::ranges::lower_bound(classes, type, {}, [](auto &c){ return c->get_type(); });
+}
+
+template<typename T>
+inline Class<T> *Reflector::find_class() const
+{
+       std::type_index type = typeid(T);
+       auto i = lower_bound(type);
+       return (i!=classes.end() && (*i)->get_type()==type ? static_cast<Class<T> *>(i->get()) : nullptr);
+}
+
+template<typename T>
+inline Class<T> &Reflector::get_or_create_class()
+{
+       std::type_index type = typeid(T);
+       auto i = lower_bound(type);
+       if(i==classes.end() || (*i)->get_type()!=type)
+               i = classes.emplace(i, std::make_unique<Class<T>>(std::ref(*this)));
+       return static_cast<Class<T> &>(*i->get());
+}
+
+template<typename F>
+inline std::vector<ClassBase *> Reflector::find_classes_if(F &&pred) const
+{
+       std::vector<ClassBase *> result;
+       for(auto &c: classes)
+               if(pred(*c))
+                       result.push_back(&*c);
+       return result;
+}
+
+
+template<typename T, typename B>
+inline bool Polymorphism<T, B>::is_instance_of(const void *obj, const PolymorphismBase &other) const
+{
+       if(const RootedPolymorphism<B> *rooted = dynamic_cast<const RootedPolymorphism<B> *>(&other))
+               return rooted->is_instance(*static_cast<const T *>(obj));
+       return false;
+}
+
+
+template<typename T>
+inline bool ClassBase::is_instance(const T &obj) const
+{
+       if(const RootedPolymorphism<T> *p = dynamic_cast<const RootedPolymorphism<T> *>(polymorphism.get()))
+               return p->is_instance(obj);
+       else if(ClassBase *c = reflector.find_class<T>(); c->polymorphism)
+               return c->polymorphism->is_instance_of(&obj, *polymorphism);
+       return false;
+}
+
+
+template<typename T>
+template<typename B>
+inline void Class<T>::set_polymorphic_base()
+{
+       if(!polymorphism)
+               polymorphism = std::make_unique<Polymorphism<T, B>>();
+       else if(!dynamic_cast<const Polymorphism<T, B> *>(polymorphism.get()))
+               throw std::logic_error("conflicting polymorphism");
+}
+
+template<typename T>
+template<typename B>
+inline void Class<T>::check_bases(const T &obj)
+{
+       std::vector<const ClassBase *> candidate_bases;
+       for(const ClassBase *b: reflector.find_classes_if([](const ClassBase &c){ return c.has_polymorphic_base<B>(); }))
+               if(b!=this && b->is_instance<B>(obj))
+                       candidate_bases.push_back(b);
+
+       for(auto i=candidate_bases.begin(); i!=candidate_bases.end(); )
+       {
+               if(std::ranges::any_of(candidate_bases, [i](const ClassBase *c){ return (c!=*i && (*i)->is_direct_base_of(*c)); }))
+                       i = candidate_bases.erase(i);
+               else
+                       ++i;
+       }
+
+       bases = std::move(candidate_bases);
+}
+
+} // namespace Reflection
+} // namespace Msp::Game
+
+#endif
index bdd0ed676d27200fe87c9ee89e4231874b643f69..5663a71fadf8b36e1db21267724cf3d27e9530c2 100644 (file)
@@ -8,7 +8,8 @@ using namespace std;
 
 namespace Msp::Game {
 
-Stage::Stage(DataFile::Collection &r):
+Stage::Stage(Reflection::Reflector &f, DataFile::Collection &r):
+       reflector(f),
        resources(r),
        event_source(event_bus),
        event_observer(event_bus),
index a046947f57ad7d0f49e43621f003ac739a4d5713..283dc81c0747776875cfed9d0048aa720b1bfbba 100644 (file)
@@ -8,6 +8,7 @@
 #include "events.h"
 #include "eventsource.h"
 #include "handle.h"
+#include "reflection.h"
 
 namespace Msp::Game {
 
@@ -22,6 +23,7 @@ public:
                Events::ComponentCreated, Events::ComponentDestroyed, Events::CameraChanged>;
 
 private:
+       Reflection::Reflector &reflector;
        DataFile::Collection &resources;
        PoolPool pools;
        EventBus event_bus;
@@ -34,9 +36,10 @@ private:
        Handle<Camera> active_camera;
 
 public:
-       Stage(DataFile::Collection &);
+       Stage(Reflection::Reflector &, DataFile::Collection &);
        ~Stage();
 
+       Reflection::Reflector &get_reflector() const { return reflector; }
        DataFile::Collection &get_resources() const { return resources; }
        PoolPool &get_pools() { return pools; }
        EventBus &get_event_bus() { return event_bus; }