From f377d216dfc44e36da3697557f38dfedcbec45d1 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Sat, 3 Dec 2022 10:47:45 +0200 Subject: [PATCH] Add reflection infrastructure This allows checking for type relationships at runtime, which will be useful for annotating system dependencies and iterating over subclasses. --- source/game/director.cpp | 7 +- source/game/director.h | 2 + source/game/owned.h | 5 + source/game/pool.h | 1 + source/game/reflection.cpp | 28 ++++++ source/game/reflection.h | 194 +++++++++++++++++++++++++++++++++++++ source/game/stage.cpp | 3 +- source/game/stage.h | 5 +- 8 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 source/game/reflection.cpp create mode 100644 source/game/reflection.h diff --git a/source/game/director.cpp b/source/game/director.cpp index 4a4c126..53b2e7f 100644 --- a/source/game/director.cpp +++ b/source/game/director.cpp @@ -1,6 +1,8 @@ #include "director.h" #include #include +#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().set_polymorphic_base(); + reflector.get_or_create_class().set_polymorphic_base(); } // Hide ~unique_ptr from the header @@ -22,7 +27,7 @@ Director::~Director() Stage &Director::create_stage() { - stages.emplace_back(std::make_unique(std::ref(resources))); + stages.emplace_back(std::make_unique(std::ref(reflector), std::ref(resources))); event_source.emit(std::ref(*stages.back())); return *stages.back(); } diff --git a/source/game/director.h b/source/game/director.h index 1be3e8f..775d7d2 100644 --- a/source/game/director.h +++ b/source/game/director.h @@ -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 access_guard; + Reflection::Reflector reflector; DataFile::Collection &resources; EventBus event_bus; EventSource event_source; diff --git a/source/game/owned.h b/source/game/owned.h index fc2acab..d6f9f17 100644 --- a/source/game/owned.h +++ b/source/game/owned.h @@ -53,14 +53,19 @@ Owned::Owned(Handle

parent, Args &&... args) Stage &stage = get_stage(*parent); Pool &pool = stage.get_pools().get_pool(); + bool first_created = !pool.get_capacity(); this->ptr = pool.create(parent, std::forward(args)...); if constexpr(std::is_base_of_v) { + if(first_created) + stage.get_reflector().get_or_create_class().template set_polymorphic_base(**this); parent->add_component(*this); stage.get_event_source().emit(*this); } else { + if(first_created) + stage.get_reflector().get_or_create_class().template set_polymorphic_base(**this); parent->add_child(*this); stage.get_event_source().emit(*this); } diff --git a/source/game/pool.h b/source/game/pool.h index 9b55e1d..2b8e799 100644 --- a/source/game/pool.h +++ b/source/game/pool.h @@ -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 index 0000000..d188e50 --- /dev/null +++ b/source/game/reflection.cpp @@ -0,0 +1,28 @@ +#include "reflection.h" +#include + +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 index 0000000..df974b1 --- /dev/null +++ b/source/game/reflection.h @@ -0,0 +1,194 @@ +#ifndef MSP_GAME_REFLECTION_H_ +#define MSP_GAME_REFLECTION_H_ + +#include +#include +#include +#include +#include + +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 +class RootedPolymorphism: public PolymorphismBase +{ +public: + virtual bool is_instance(const B &) const = 0; +}; + + +template + requires std::is_base_of_v +class Polymorphism: public RootedPolymorphism +{ +public: + bool is_instance(const B &obj) const override { return std::is_same_v || dynamic_cast(&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 bases; + std::unique_ptr 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 + bool is_instance(const T &) const; + + template + bool has_polymorphic_base() const { return dynamic_cast *>(polymorphism.get()); } +}; + + +template +class Class: public ClassBase +{ +public: + Class(Reflector &r): ClassBase(r, typeid(T)) { } + + template + void set_polymorphic_base(); + + template + void set_polymorphic_base(const T &obj) { set_polymorphic_base(); check_bases(obj); } + +private: + template + void check_bases(const T &obj); +}; + + +class Reflector +{ +private: + std::vector> classes; + + std::vector>::const_iterator lower_bound(const std::type_index &) const; + +public: + template + Class *find_class() const; + + template + Class &get_or_create_class(); + + template + std::vector find_classes_if(F &&) const; +}; + + +inline std::vector>::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 +inline Class *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 *>(i->get()) : nullptr); +} + +template +inline Class &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>(std::ref(*this))); + return static_cast &>(*i->get()); +} + +template +inline std::vector Reflector::find_classes_if(F &&pred) const +{ + std::vector result; + for(auto &c: classes) + if(pred(*c)) + result.push_back(&*c); + return result; +} + + +template +inline bool Polymorphism::is_instance_of(const void *obj, const PolymorphismBase &other) const +{ + if(const RootedPolymorphism *rooted = dynamic_cast *>(&other)) + return rooted->is_instance(*static_cast(obj)); + return false; +} + + +template +inline bool ClassBase::is_instance(const T &obj) const +{ + if(const RootedPolymorphism *p = dynamic_cast *>(polymorphism.get())) + return p->is_instance(obj); + else if(ClassBase *c = reflector.find_class(); c->polymorphism) + return c->polymorphism->is_instance_of(&obj, *polymorphism); + return false; +} + + +template +template +inline void Class::set_polymorphic_base() +{ + if(!polymorphism) + polymorphism = std::make_unique>(); + else if(!dynamic_cast *>(polymorphism.get())) + throw std::logic_error("conflicting polymorphism"); +} + +template +template +inline void Class::check_bases(const T &obj) +{ + std::vector candidate_bases; + for(const ClassBase *b: reflector.find_classes_if([](const ClassBase &c){ return c.has_polymorphic_base(); })) + if(b!=this && b->is_instance(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 diff --git a/source/game/stage.cpp b/source/game/stage.cpp index bdd0ed6..5663a71 100644 --- a/source/game/stage.cpp +++ b/source/game/stage.cpp @@ -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), diff --git a/source/game/stage.h b/source/game/stage.h index a046947..283dc81 100644 --- a/source/game/stage.h +++ b/source/game/stage.h @@ -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 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; } -- 2.43.0