]> git.tdb.fi Git - libs/game.git/commitdiff
Redesign reflected base class discovery
authorMikko Rasa <tdb@tdb.fi>
Tue, 22 Apr 2025 21:06:07 +0000 (00:06 +0300)
committerMikko Rasa <tdb@tdb.fi>
Tue, 22 Apr 2025 21:19:22 +0000 (00:19 +0300)
It now uses subobject layout to find out which classes can be bases of
which other ones.  This allows the discovery to work even when an object
of the exact type is not available, such as intermediate base classes.

source/game/owned.h
source/game/reflection.cpp
source/game/reflection.h
source/game/stage.h
tests/reflection.cpp

index 359bddca55b73deb8510d6130ef303e2cdb3e3b7..bb09433da3bd21b819dfeb9e61080e8cce29c80d 100644 (file)
@@ -69,29 +69,20 @@ Owned<T>::Owned(Handle<P> parent, Args &&... args)
                throw std::invalid_argument("Owned::Owned");
 
        Stage &stage = get_stage(*parent);
-       /* TODO Add a flag to only do this when first_created in an outer call is
-       true. */
-       if(typeid(*parent)==typeid(P))
-       {
-               Reflection::Class<P> &parent_class = stage.get_reflector().get_or_create_class<P>();
-               if(!parent_class.is_up_to_date())
-                       parent_class.template set_polymorphic_base<Entity>(*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);
+                       stage.get_reflector().get_or_create_class<T>().discover_bases(this->ptr);
                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);
+                       stage.get_reflector().get_or_create_class<T>().discover_bases(this->ptr);
                parent->add_child(*this);
                stage.get_event_source().emit<Events::EntityCreated>(*this);
        }
index 43dd13a73e1c8ba01a64678aec85975213eb3eb8..a571dd62d2da43c874d3e5a67941ae6ef95c9b95 100644 (file)
@@ -1,4 +1,5 @@
 #include "reflection.h"
+#include <msp/core/algorithm.h>
 #include <msp/debug/demangle.h>
 
 using namespace std;
@@ -6,10 +7,19 @@ using namespace std;
 namespace Msp::Game {
 namespace Reflection {
 
-ClassBase::ClassBase(Reflector &r, type_index t):
+struct SubObject
+{
+       ClassBase *type = nullptr;
+       std::size_t offset = 0;
+       std::size_t size = 0;
+};
+
+
+ClassBase::ClassBase(Reflector &r, type_index t, size_t s):
        reflector(r),
        type(t),
-       name(Debug::demangle(t.name()))
+       name(Debug::demangle(t.name())),
+       size(s)
 { }
 
 bool ClassBase::is_direct_base_of(const ClassBase &other) const
@@ -24,6 +34,49 @@ bool ClassBase::is_base_of(const ClassBase &other) const
        return ranges::any_of(other.bases, [this](const ClassBase *b){ return is_base_of(*b); });
 }
 
+void ClassBase::discover_bases(const void *obj)
+{
+       if(!polymorphism)
+               return;
+
+       auto has_same_root = [this](const ClassBase &c){ return c.polymorphism && polymorphism->has_same_root(*c.polymorphism); };
+
+       std::vector<SubObject> hierarchy;
+       for(ClassBase *c: reflector.find_classes_if(has_same_root))
+               if(c!=this && polymorphism->is_instance_of(obj, *c->polymorphism))
+                       hierarchy.emplace_back(c, polymorphism->get_offset_of_base(obj, *c->polymorphism), c->get_size());
+
+       sort_member(hierarchy, &SubObject::size);
+       hierarchy.emplace_back(this, 0, size);
+
+       bool any_changed = false;
+       for(auto i=hierarchy.begin(); i!=hierarchy.end(); ++i)
+       {
+               vector<const ClassBase *> detected_bases;
+               for(auto j=hierarchy.begin(); (j!=hierarchy.end() && j->size<=i->size); ++j)
+               {
+                       bool enclosed = (j->offset>=i->offset && j->offset+j->size<i->offset+i->size);
+                       if(j!=i && j->offset<size && (i->type==this || enclosed || j->type->is_direct_base_of(*i->type)))
+                       {
+                               detected_bases.push_back(j->type);
+                               j->offset = size;
+                       }
+               }
+
+               if(detected_bases!=i->type->bases)
+               {
+                       i->type->bases = detected_bases;
+                       any_changed = true;
+               }
+       }
+
+       if(any_changed)
+               ++reflector.generation;
+
+       for(const SubObject &o: hierarchy)
+               o.type->generation = reflector.generation;
+}
+
 
 ClassBase *Reflector::find_class(const type_index &type) const
 {
index 7ae1f6a69f2789779a8175067e1664562f3af955..5478e301eb104378ebbb5aa0a6c55046b660b674 100644 (file)
@@ -20,7 +20,9 @@ class PolymorphismBase
 public:
        virtual ~PolymorphismBase() = default;
 
+       virtual bool has_same_root(const PolymorphismBase &) const = 0;
        virtual bool is_instance_of(const void *, const PolymorphismBase &) const = 0;
+       virtual std::size_t get_offset_of_base(const void *, const PolymorphismBase &) const = 0;
 };
 
 
@@ -28,7 +30,10 @@ template<typename B>
 class RootedPolymorphism: public PolymorphismBase
 {
 public:
+       bool has_same_root(const PolymorphismBase &p) const override { return dynamic_cast<const RootedPolymorphism *>(&p); }
        virtual bool is_instance(const B &) const = 0;
+       virtual const B &as_root(const void *) const = 0;
+       virtual const void *as_derived(const B &) const = 0;
 };
 
 
@@ -39,6 +44,9 @@ 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;
+       const B &as_root(const void *p) const override { return *static_cast<const T *>(p); }
+       const void *as_derived(const B &obj) const override { return dynamic_cast<const T *>(&obj); }
+       std::size_t get_offset_of_base(const void *, const PolymorphismBase &) const override;
 };
 
 
@@ -48,16 +56,18 @@ protected:
        Reflector &reflector;
        std::type_index type;
        std::string name;
+       std::size_t size;
        std::vector<const ClassBase *> bases;
        std::unique_ptr<PolymorphismBase> polymorphism;
        unsigned generation = 0;
 
-       ClassBase(Reflector &, std::type_index);
+       ClassBase(Reflector &, std::type_index, std::size_t);
 public:
        virtual ~ClassBase() = default;
 
        const std::type_index &get_type() const { return type; }
        const std::string &get_name() const { return name; }
+       std::size_t get_size() const { return size; }
 
        const std::vector<const ClassBase *> &get_direct_bases() const { return bases; }
        bool is_direct_base_of(const ClassBase &) const;
@@ -66,13 +76,9 @@ public:
        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()); }
+       virtual void discover_bases(const void *);
 
        bool is_up_to_date() const;
-
-protected:
-       void increment_generation() const;
 };
 
 
@@ -84,13 +90,6 @@ public:
 
        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);
 };
 
 
@@ -164,11 +163,24 @@ inline bool Polymorphism<T, B>::is_instance_of(const void *obj, const Polymorphi
        return false;
 }
 
+template<typename T, typename B>
+       requires std::is_base_of_v<B, T>
+inline std::size_t Polymorphism<T, B>::get_offset_of_base(const void *obj, const PolymorphismBase &other) const
+{
+       const void *base_obj = dynamic_cast<const RootedPolymorphism<B> &>(other).as_derived(as_root(obj));
+       return reinterpret_cast<std::uintptr_t>(base_obj)-reinterpret_cast<std::uintptr_t>(obj);
+}
+
 
 template<typename T>
 inline bool ClassBase::is_instance(const T &obj) const
 {
-       if(const RootedPolymorphism<T> *p = dynamic_cast<const RootedPolymorphism<T> *>(polymorphism.get()))
+       if constexpr(requires { typename T::PolymorphicBase; })
+       {
+               if(auto p = dynamic_cast<const RootedPolymorphism<typename T::PolymorphicBase> *>(polymorphism.get()))
+                       return p->is_instance(obj);
+       }
+       else if(auto 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);
@@ -180,15 +192,10 @@ inline bool ClassBase::is_up_to_date() const
        return generation==reflector.get_generation();
 }
 
-inline void ClassBase::increment_generation() const
-{
-       ++reflector.generation;
-}
-
 
 template<typename T>
 Class<T>::Class(Reflector &r):
-       ClassBase(r, typeid(T))
+       ClassBase(r, typeid(T), sizeof(T))
 {
        if constexpr(requires { typename T::PolymorphicBase; })
                set_polymorphic_base<typename T::PolymorphicBase>();
@@ -204,34 +211,6 @@ inline void Class<T>::set_polymorphic_base()
                throw std::logic_error("conflicting polymorphism");
 }
 
-template<typename T>
-template<typename B>
-inline void Class<T>::check_bases(const T &obj)
-{
-       if(typeid(obj)!=type)
-               throw std::invalid_argument("Class::check_bases");
-
-       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;
-       }
-
-       if(candidate_bases!=bases)
-       {
-               bases = std::move(candidate_bases);
-               increment_generation();
-       }
-       generation = reflector.get_generation();
-}
-
 } // namespace Reflection
 } // namespace Msp::Game
 
index c27058eccc2bae9d0e14fe4fb6b0b9d4f76e2d18..0ae9b263315039f96dfc1457b79a17ffea8e047c 100644 (file)
@@ -100,7 +100,7 @@ template<typename T, typename... Args>
 inline T &Stage::add_system(Args &&... args)
 {
        auto &sys = static_cast<T &>(*systems.emplace_back(std::make_unique<T>(*this, std::forward<Args>(args)...)));
-       reflector.get_or_create_class<T>().template set_polymorphic_base<System>(sys);
+       reflector.get_or_create_class<T>().discover_bases(&sys);
        scheduler.add_system(sys);
        return sys;
 }
index a40ada3c86adf8278e9f3896e560dfff727b3687..21184bfa35ef98351583fc1eefb9d27a8b3aa335 100644 (file)
@@ -17,13 +17,21 @@ private:
        void polymorphism_conflict();
        void deep_polymorphism();
        void branching_polymorphism();
+       void empty_intermediate_base();
 };
 
-struct A { virtual ~A() = default; };
-struct B: A { };
-struct C: B { };
-struct D: C { };
-struct C2: B { };
+
+namespace {
+
+struct A { using PolymorphicBase = A; void *a_member; virtual ~A() = default; };
+struct B: A { void *b_member; };
+struct C: B { void *c_member; };
+struct D: C { void *d_member; };
+struct C2: B { void *c2_member; };
+struct E: A { };
+struct F: E { void *f_member; };
+
+} // anonymous namespace
 
 
 ReflectionTests::ReflectionTests()
@@ -33,6 +41,7 @@ ReflectionTests::ReflectionTests()
        add(&ReflectionTests::polymorphism_conflict, "Polymorphism conflict").expect_throw<std::logic_error>();
        add(&ReflectionTests::deep_polymorphism, "Deep polymorphism");
        add(&ReflectionTests::branching_polymorphism, "Branching polymorphism");
+       add(&ReflectionTests::empty_intermediate_base, "Empty intermediate base");
 }
 
 
@@ -52,9 +61,10 @@ void ReflectionTests::basic_polymorphism()
        Game::Reflection::Reflector reflector;
 
        Game::Reflection::Class<A> &a_class = reflector.get_or_create_class<A>();
-       a_class.set_polymorphic_base<A>();
        Game::Reflection::Class<B> &b_class = reflector.get_or_create_class<B>();
-       b_class.set_polymorphic_base<A>(B());
+
+       B b_obj;
+       b_class.discover_bases(&b_obj);
 
        EXPECT(a_class.is_direct_base_of(b_class));
        EXPECT(!b_class.is_direct_base_of(a_class));
@@ -63,7 +73,6 @@ void ReflectionTests::basic_polymorphism()
        EXPECT(a_class.is_instance(a_obj));
        EXPECT(!b_class.is_instance(a_obj));
 
-       B b_obj;
        EXPECT(a_class.is_instance(b_obj));
        EXPECT(b_class.is_instance(b_obj));
 
@@ -85,13 +94,12 @@ void ReflectionTests::deep_polymorphism()
        Game::Reflection::Reflector reflector;
 
        Game::Reflection::Class<A> &a_class = reflector.get_or_create_class<A>();
-       a_class.set_polymorphic_base<A>();
        Game::Reflection::Class<B> &b_class = reflector.get_or_create_class<B>();
-       b_class.set_polymorphic_base<A>(B());
        Game::Reflection::Class<C> &c_class = reflector.get_or_create_class<C>();
-       c_class.set_polymorphic_base<A>(C());
        Game::Reflection::Class<D> &d_class = reflector.get_or_create_class<D>();
-       d_class.set_polymorphic_base<A>(D());
+
+       D d_obj;
+       d_class.discover_bases(&d_obj);
 
        EXPECT(a_class.is_direct_base_of(b_class));
        EXPECT(!a_class.is_direct_base_of(c_class));
@@ -103,7 +111,6 @@ void ReflectionTests::deep_polymorphism()
        EXPECT(b_class.is_base_of(d_class));
        EXPECT(c_class.is_direct_base_of(d_class));
 
-       D d_obj;
        EXPECT(a_class.is_instance(d_obj));
        EXPECT(b_class.is_instance(d_obj));
        EXPECT(c_class.is_instance(d_obj));
@@ -127,13 +134,14 @@ void ReflectionTests::branching_polymorphism()
        Game::Reflection::Reflector reflector;
 
        Game::Reflection::Class<A> &a_class = reflector.get_or_create_class<A>();
-       a_class.set_polymorphic_base<A>();
        Game::Reflection::Class<B> &b_class = reflector.get_or_create_class<B>();
-       b_class.set_polymorphic_base<A>(B());
        Game::Reflection::Class<C> &c_class = reflector.get_or_create_class<C>();
-       c_class.set_polymorphic_base<A>(C());
        Game::Reflection::Class<C2> &c2_class = reflector.get_or_create_class<C2>();
-       c2_class.set_polymorphic_base<A>(C2());
+
+       C c_obj;
+       C2 c2_obj;
+       c_class.discover_bases(&c_obj);
+       c2_class.discover_bases(&c2_obj);
 
        EXPECT(a_class.is_direct_base_of(b_class));
        EXPECT(a_class.is_base_of(c_class));
@@ -143,8 +151,6 @@ void ReflectionTests::branching_polymorphism()
        EXPECT(!c_class.is_base_of(c2_class));
        EXPECT(!c2_class.is_base_of(c_class));
 
-       C c_obj;
-       C2 c2_obj;
        EXPECT(a_class.is_instance(c_obj));
        EXPECT(a_class.is_instance(c2_obj));
        EXPECT(b_class.is_instance(c_obj));
@@ -154,3 +160,22 @@ void ReflectionTests::branching_polymorphism()
        EXPECT(c2_class.is_instance(c2_obj));
        EXPECT(!c2_class.is_instance(c_obj));
 }
+
+void ReflectionTests::empty_intermediate_base()
+{
+       Game::Reflection::Reflector reflector;
+
+       Game::Reflection::Class<A> &a_class = reflector.get_or_create_class<A>();
+       Game::Reflection::Class<E> &e_class = reflector.get_or_create_class<E>();
+       Game::Reflection::Class<F> &f_class = reflector.get_or_create_class<F>();
+
+       E e_obj;
+       F f_obj;
+       e_class.discover_bases(&e_obj);
+       f_class.discover_bases(&f_obj);
+
+       EXPECT(a_class.is_direct_base_of(e_class));
+       EXPECT(!a_class.is_direct_base_of(f_class));
+       EXPECT(e_class.is_direct_base_of(f_class));
+       EXPECT(a_class.is_base_of(f_class));
+}