shared_ptr - Alternative?



  • Er.

    Ist einer der grossen Vorteile von Component Based Design nicht, dass alle Komponenten des selben Typs hübsch in einem vector<ComponentType> liegen? So zwecks Cache-freundlichkeit usw.?

    Und die Entities enthalten dann nur Component-IDs (z.B. einfach der index im Vektor).

    So hab' ich das auf jeden Fall verstanden.



  • Also die eigentliche Motivation für das Component Based Design Pattern ist meines Wissens nach das Verringern von Vererbungshierarchien.
    Da gibt es verschiedene Realisierungen die möglicherweise die von hustbaer angesprochenen Vorteile wie z.B. Cache-freundlichkeit mitbringen können.
    Ich habe nun mal versucht unique_ptr einzusetzen aber irgendwie verliert einer der Pointer seine Gültigkeit:

    Erst poste ich den Quellcode danach eine Beschreibung des Problems:

    Ich generiere als Test zwei Kreise via:

    void Factory::createCircle(sf::Vector2<float> position,sf::Vector2<float> direction,float speed)
    {
        Entity& e = _entitymanager.createEntity();
        _entitymanager.addComponent(new RenderComponent(position),e);
        if(speed > 0)
        {
            _entitymanager.addComponent(new MoveComponent(direction,speed),e);
        }
    }
    
    Entity& EntityManager::createEntity(void)
    {
        entities.push_back(Entity());
        return entities.back();
    }
    
    componentlist& EntityManager::getComponentsOfEntity(Entity& entity)
    {
        return id2components[entity.id()];
    }
    
    void EntityManager::addComponent(constptr<Component> component,Entity& entity)
    {
       componentlist& cl = getComponentsOfEntity(entity);
       entitylist&    el = getEntitiesWithComponent(component);
       cl.push_back(unique_ptr<Component>(component));[code="c"]
       el.push_back(entity);
    }
    
    entitylist& EntityManager::getEntitiesWithComponent(constptr<Component> component)
    {
        return component2entity[component->getType()];
    }
    

    Der Header zum EntityManager:

    #ifndef ENTITYMANAGER_H
    #define ENTITYMANAGER_H
    #include "entity.h"
    #include "component.h"
    #include <map>
    #include <vector>
    #include <memory>
    #include <iostream>
    
    using namespace std;
    using ID              = unsigned long;
    using componenttype   = unsigned long;
    template <class T>
    using constptr    = T* const;
    using componentlist   = vector<unique_ptr<Component>>;
    using entitylist      = vector<Entity>;
    
    class EntityManager
    {
        private:
            entitylist        entities;
            map<ID,componentlist> id2components;
            map<componenttype,entitylist> component2entity;
            constptr<Component> toComponentPtr(unique_ptr<Component>& uc)
            {
                return uc.get();
            }
        public:
            Entity& createEntity(void);
            componentlist& getComponentsOfEntity(Entity& entity);
            entitylist& getEntitiesWithComponent(constptr<Component> component);
            void addComponent(constptr<Component> component,Entity& entity);
    
            template <class T_COMPONENT>
            entitylist& getEntitiesWithComponent(void);
            template <class T_COMPONENT>
            constptr<Component> getComponent(Entity& entity);
            template <class T_COMPONENT>
            bool hasComponent(Entity& entity);
    
    };
    
    template <class T_COMPONENT>
    constptr<Component> EntityManager::getComponent(Entity& entity)
    {
        componentlist& cl = getComponentsOfEntity(entity);
        for(auto& x: cl)
           if(x->getType() == T_COMPONENT::type)
              return toComponentPtr(x);
    
        return nullptr;
    }
    
    template <class T_COMPONENT>
    bool EntityManager::hasComponent(Entity& entity)
    {
        entitylist& el = component2entity[T_COMPONENT::type];
        for(auto x: el)
        if(x.id() == entity.id())
            return true;
    
        return false;
    }
    
    template <class T_COMPONENT>
    entitylist& EntityManager::getEntitiesWithComponent(void)
    {
        return component2entity[T_COMPONENT::type];
    }
    
    #endif // ENTITYMANAGER_H
    

    Hiermit werden die Kreise periodisch bewegt:

    void MoveSystem::update(float dt)
    {
       entitylist& entities = _entitymanager.getEntitiesWithComponent<MoveComponent>();
    
       for(auto x:entities)
       {
           if(_entitymanager.hasComponent<RenderComponent>(x))
           {
                constptr<MoveComponent> mc = static_cast<constptr<MoveComponent>>(_entitymanager.getComponent<MoveComponent>(x));
                constptr<RenderComponent> rc = static_cast<constptr<RenderComponent>>(_entitymanager.getComponent<RenderComponent>(x));
                rc->_position += (mc->_direction)*mc->_speed*dt;
           }
    
       }
    }
    

    Hiermit gezeichnet:

    void RenderSystem::draw(void)
    {
        entitylist& el = _entitymanager.getEntitiesWithComponent<RenderComponent>();
        _window.clear();
        for(auto x: el)
        {
            constptr<RenderComponent> rc = static_cast<constptr<RenderComponent>>(_entitymanager.getComponent<RenderComponent>(x));
            sf::CircleShape cs(100);
            cs.setPosition(rc->_position);
            cs.setFillColor(sf::Color::Green);
            _window.draw(cs);
        }
        _window.display();
    }
    
    EntityManager em;
        Factory f(em);
        f.createCircle(sf::Vector2<float>(200.f,200.f),sf::Vector2<float>(-1.f,0.f),1.f);
        f.createCircle(sf::Vector2<float>(50.f,50.f),sf::Vector2<float>(-1.f,0.f),10.f);
        RenderSystem rs(em,window);
        MoveSystem ms(em);
        while (window.isOpen())
        {
            sf::Event event;
            while (window.pollEvent(event))
            {
                if(event.type == sf::Event::Closed)
                    window.close();
            }
            ms.update(0.05f);
            rs.draw();
            sf::sleep(sf::seconds(0.05f));
        }
    

    Als Test wollte ich schlicht zwei Entitäten mit einer Render- und einer MoveComponent erstellen.
    Die addComponent-Methode des Entitymanagers scheint jedoch nicht richtig zu funktioniert bzw. die Datenstrukturen auf denen sie operiert:
    Die Kreise werden richtig gezeichnet jedoch nicht bewegt. Der Inhalt der MoveComponents ist nicht definiert bzw. hat aufjedenfall nicht die übergebenen Werte, scheinbar ist ein unique_ptr ungültig geworden.
    Vertausche ich die Reihenfolge in der ich die Komponenten hinzufüge, also erst die MoveComponent addiere und anschließend die RenderComponent, bewegen sich die Kreise und werden gezeichnet, dies scheint allerdings auch fehlerhaft zu sein. So ist dann z.B. die Position der Kreise identisch und die Geschwindigkeeit scheint sich zu ändern.

    Ich habe schon zig Dinge ausprobiert, aber irgendwie sehe ich den Fehler nicht. Auch in Testprogrammen ist das pushen von unique_ptr auf einen vector eigentlich kein Problem.



  • Kaum gepostet schon den Fehler gefunden:
    Mein Typenerkennunssystem funktioniert nicht.

    Hier mal der Code:

    class Component
    {
    public:
          virtual unsigned long getType() const = 0;
    };
    
    namespace
    {
        static unsigned long nextType = 0;
    }
    template <class T>
    class ComponentBase : public Component
    {
        public:
        static unsigned long type;
        unsigned long getType() const {return T::type;}
    };
    
    template <class T> unsigned long ComponentBase<T>::type(nextType++);
    

    Das sollte eigentlich automatisch für jede Spezialisierung eines Component eine eindeutige ID generieren. Tut es in meinem Code aber nicht mehr.
    Der IDENTISCHE code zur ID-Generierung funktioniert aber in einem separaten Testprogramm?!

    Hier diese Methode als kompilierbares Testprogramm:

    #include <iostream>
    #include <memory>
    #include <vector>
    
    using namespace std;
    class Component {
    public:
      virtual unsigned long getType() const = 0;
    };
    
    template <class T>
    class ComponentBase : public Component {
    public:
      static unsigned long type;
      unsigned long getType() const { return T::type; }
    };
    static unsigned long nextType = 0;
    template <typename T> unsigned long ComponentBase<T>::type(nextType++);
    
    class PositionComponent : public ComponentBase<PositionComponent> {
    public:
      PositionComponent(double x, double y, double z)
      : x(x), y(y), z(z) {}
    
      double x, y, z;
    };
    
    class VelocityComponent : public ComponentBase<VelocityComponent> {
    public:
      VelocityComponent(double x, double y, double z)
      : x(x), y(y), z(z) {}
    
      double x, y, z;
    };
    
    int main()
    {
        std::cout << VelocityComponent::type << std::endl;
        std::cout << VelocityComponent::type << std::endl;
        std::cout << PositionComponent::type << std::endl;
    
        return 0;
    }
    


  • namespace 
    { 
         static unsigned long nextType = 0; 
    }
    

    Das kann nicht gehen. Damit hast du in jeder Übersetzungseinheit nen eigenen Zähler.
    Was IMO auch das Programm "malformed" macht, weil die Definition von ComponentBase<T>::type in jeder Übersetzungseinheit dadurch unterschiedlich ist.

    Mach "nextType" zu ner statischen Variable einer (nicht-template) Klasse, oder zu ner ganz normalen globalen Variable, dann sollte es gehen.

    Bzw. falls du keine fortlaufenden Nummern brauchst, kannst du ComponentBase<T> einfach irgend eine statische Variable spendieren, und deren Adresse dann als ID verwenden.



  • Oder du nimmst direkt std::type_index . Kann auch fürs Debugging sehr nützlich sein, weil du dann für Komponenten automatisch einen Namen hast.

    Übrigens würde ichs mit den Typedefs/Aliases nicht übertreiben. Die Abstraktion macht den Code sehr schwierig zu lesen, gerade Dinge wie constptr<T> sind sehr verwirrend. Vor allem bringt dir die Abstraktion nichts, da du ohnehin nicht einfach auf einen Smart-Pointer wechseln kannst (du triffst im Code viele implizite Annahmen, dass es sich um const T* handelt).

    Hier habe ich auch ein mögliches Design für komponentenbasierte Systeme erläutert, du kannst dir ja den Thread mal anschauen.



  • Danke euch beiden. Ich probiers gleich mal aus Hustbaer, meine das zwar gestern geändert zu haben ohne Erfolg, aber mal schauen.

    @Nexus
    Dein Einwand ist goldrichtig, ich bin selbst genervt davon wie es aussieht (also z.B. die typedefs), werde das wohl nochmal neuschreiben.



  • Ich würde mir ehrlich gesagt die Frage stellen, wofür genau du dieses getType() eigentlich brauchst und ob das nicht vielleicht eher Symptom eines Problems in deinem Design ist...sobald so eine Methode auftaucht, wird in der Regel irgendwo das Liskovsche Substitutionsprinzip verletzt...



  • <agressiv>
    Ich kann diese verkackte Design-Keule nicht mehr lesen.
    </agressiv>



  • Definiere "brauchen". Ich könnte die Komponenten auch in separate Datenstrukturen abspeichern z.B. Mag ich aber nicht so sehr.

    Nun nochmal zum getType() (Ich werde vermutlich auch wenn ich das zum Laufen bringe std::type_index verwenden, davon wusste ich bis dato nichts):

    Es wundert mich massiv, das diese nicht funktioniert, denn die Beispielimplementation die ich hier gepostet hab, welche identisch zu jener in meinem Projekt ist geht:

    Kompilierbare Beispielimplementation:

    #include <iostream>
    #include <memory>
    #include <vector>
    
    using namespace std;
    class Component {
    public:
      virtual unsigned long getType() const = 0;
    };
    
    template <class T>
    class ComponentBase : public Component {
    public:
      static unsigned long type;
      unsigned long getType() const { return T::type; }
    };
    static unsigned long nextType = 0;
    template <typename T> unsigned long ComponentBase<T>::type(nextType++);
    
    class PositionComponent : public ComponentBase<PositionComponent> {
    public:
      PositionComponent(double x, double y, double z)
      : x(x), y(y), z(z) {}
    
      double x, y, z;
    };
    
    class VelocityComponent : public ComponentBase<VelocityComponent> {
    public:
      VelocityComponent(double x, double y, double z)
      : x(x), y(y), z(z) {}
    
      double x, y, z;
    };
    
    int main()
    {
        std::cout << PositionComponent::type << std::endl;
        std::cout << VelocityComponent::type << std::endl;
        std::cout << VelocityComponent::type << std::endl;
    
        return 0;
    }
    

    Und aus meinem Projekt:

    Component:

    #ifndef COMPONENT_H_INCLUDED
    #define COMPONENT_H_INCLUDED
    
    class Component {
    public:
      virtual unsigned long getType() const = 0;
    };
    
    #endif // COMPONENT_H_INCLUDED
    

    BaseComponent:

    #ifndef COMPONENTBASE_H_INCLUDED
    #define COMPONENTBASE_H_INCLUDED
    #include "component.h"
    
    template <class T>
    class ComponentBase : public Component {
    public:
      static unsigned long type;
      unsigned long getType() const { return T::type; }
    };
    static unsigned long nextType = 0;
    template <typename T> unsigned long ComponentBase<T>::type(nextType++);
    
    #endif // COMPONENTBASE_H_INCLUDED
    

    Beispiel Component:

    #ifndef MOVECOMPONENT_H_INCLUDED
    #define MOVECOMPONENT_H_INCLUDED
    #include "componentbase.h"
    #include <SFML/System/Vector2.hpp>
    
    class MoveComponent
    :public ComponentBase<MoveComponent>
    {
        public:
            MoveComponent(sf::Vector2<float> direction,float speed)
            :_direction(direction),_speed(speed>=0.f?speed:0.f)
            {}
        sf::Vector2<float> _direction;
        float _speed;
    
    };
    
    #endif // MOVECOMPONENT_H_INCLUDED
    

    Identisch für alle anderen Komponenten.
    Nun rufe ich in meiner main einfach

    cout << RenderComponent::type << endl;
           cout << MoveComponent::type << endl;
    

    auf und erhalte identische Werte?!

    Wie ist das zu erklären? Einziger Unterschied ist die Aufteilung von Component und BaseComponent in separate Headerfiles.



  • Namenloser324 schrieb:

    Definiere "brauchen".

    Naja, zu welchem Zweck wird diese Methode verwendet?

    Namenloser324 schrieb:

    Wie ist das zu erklären?

    Die Reihenfolge, in der Objekte mit static storage duration initialisiert werden, ist undefiniert. Könnte mir vorstellen, dass der Compiler das nextType++ einfach durch die Bank zu 1 optimiert.

    Lösungsvorschlag:

    inline unsigned long getNextType()
    {
      static unsigned long next = 0;
      return next++;
    }
    
    template <typename T> unsigned long ComponentBase<T>::type(getNextType());
    

    Das eigentliche Problem liegt aber vermutlich dort, wo die Notwendigkeit eines getType() hekommt...

    Btw: Deine Interface-Klassen sollten sehr wahrscheinlich besser keine öffentlichen Copy-Konstruktoren und Copy-Assignment-Operatoren haben.



  • Ich finde zwar das Design hier auch etwas merkwürdig, und teilweise unnötig ineffizient (z.B. lineare Suche nach Komponenten).

    Aber grundsätzlich, dot: Wie würdest du sowas wie

    C& component = entity.getComponent<C>()
    

    implementieren? Wenn du das ganze dynamisch halten willst, musst du einen Container mit verschiedenen Komponenten halten und die gewünschte Komponente C schnell darin finden. Nun, wie kommst du vom Typ C auf einen Schlüssel/Index im Container? Mittels getType , typeid oder einem ähnlichem Mechanismus.

    Wie im verlinkten Thread angetönt, könnte man z.B.

    std::unordered_map<std::type_index, std::unique_ptr<Component>> components;
    

    verwenden. Oder auch einfach

    std::vector<std::unique_ptr<Component>>
    

    , wenn man fortlaufende Indizes hat und Typen darauf abbilden kann.

    P.S. Die von hustbaer angetönte Cache-Lokalität geht mit std::unique_ptr natürlich verloren. Aber verschiedene Container mit statischen Typen sind unpraktisch, wenn man die Komponenten nicht hardcodet. Ein Kompromiss bestünde z.B. darin, die Komponenten zentral (nicht in den Entities) abzuspeichern, und dabei gleiche Komponententypen zusammenzufassen (in Containern von Containern à la std::deque ). Macht dann aber alles andere komplizierter, vor allem das Hinzufügen/Entfernen. Würde ich also wirklich erst tun, wenn die reine Iteration das Bottleneck wird. Und dazu müssen die Aktionen der Komponenten vergleichsweise billig sein.



  • Nexus schrieb:

    Aber grundsätzlich, dot: Wie würdest du sowas wie

    C& component = entity.getComponent<C>()
    

    implementieren? Wenn du das ganze dynamisch halten willst, musst du einen Container mit verschiedenen Komponenten halten und die gewünschte Komponente C schnell darin finden. Nun, wie kommst du vom Typ C auf einen Schlüssel/Index im Container? Mittels getType , typeid oder einem ähnlichem Mechanismus.

    Ich würde mir erst zumindest mal die Frage stellen, wieso genau ich eigentlich Komponenten in einen allgemeinen Container packen muss, nur damit ich sie mir dann, unter Verlust der Typinformation, dort wieder rausfischen darf... 😉



  • Sagte ich doch: Weil du bei Containern mit statischen Typinformationen zu unflexibel bist. Die Entity muss die möglichen Komponententypen kennen, was eine unnötige Abhängigkeit darstellt und es verunmöglicht, unbekannte Komponenten einzufügen.

    Oder sag mir, wie du

    C& component = entity.getComponent<C>();
    

    implementieren würdest, wenn du Container mit statischen Typinformationen hast.



  • Nexus schrieb:

    Sagte ich doch: Weil du bei Containern mit statischen Typinformationen zu unflexibel bist. Die Entity muss die möglichen Komponententypen kennen, was eine unnötige Abhängigkeit darstellt und es verunmöglicht, unbekannte Komponenten einzufügen.

    So wie ich das verstehe, könnte genausogut jede Komoponente intern ihre eigene Liste aus Entities (nach außen über eine ID ansprechbar) und den zugehörigen Infos haben, was vermutlich (z.B. vom Datenlayout her) sowieso effizienter wäre (jede Komponente wird ja z.B. wohl oft über all ihre Entites iterieren und dabei auf die komponentenspezifischen Daten zugreifen wollen). Ich seh jedenfalls im Moment keinen Grund, wieso man das Problem so herum lösen müsste, dass man ein Entity Objekt hat, welches nichts anderes tut, als eine Liste von Komponenten zu speichern...man rührt damit vermutlich vor allem schön den Cache um... 😉



  • dot schrieb:

    Namenloser324 schrieb:

    Wie ist das zu erklären?

    Die Reihenfolge, in der Objekte mit static storage duration initialisiert werden, ist undefiniert. Könnte mir vorstellen, dass der Compiler das nextType++ einfach durch die Bank zu 1 optimiert.

    Lösungsvorschlag:

    inline unsigned long getNextType()
    {
      static unsigned long next = 0;
      return next++;
    }
    
    template <typename T> unsigned long ComponentBase<T>::type(getNextType());
    

    Das eigentliche Problem liegt aber vermutlich dort, wo die Notwendigkeit eines getType() hekommt...

    Btw: Deine Interface-Klassen sollten sehr wahrscheinlich besser keine öffentlichen Copy-Konstruktoren und Copy-Assignment-Operatoren haben.

    Super, hat funktioniert, danke 🙂
    Deine Erklärung der Ursache ist auch sinnvoll, danke, wieder was gelernt.

    Eine Frage noch an Nexus dann mach ich erstmal weiter:

    und teilweise unnötig ineffizient (z.B. lineare Suche nach Komponenten).

    Du meinst sicherlich das Suchen nach einer speziellen Komponente in einem std::vector<Component*>? Meinst du ich sollte das auch durch eine Map ersetzen?



  • Auch das sprach ich an.

    Nexus schrieb:

    Ein Kompromiss bestünde z.B. darin, die Komponenten zentral (nicht in den Entities) abzuspeichern, und dabei gleiche Komponententypen zusammenzufassen (in Containern von Containern à la std::deque ). Macht dann aber alles andere komplizierter, vor allem das Hinzufügen/Entfernen. Würde ich also wirklich erst tun, wenn die reine Iteration das Bottleneck wird. Und dazu müssen die Aktionen der Komponenten vergleichsweise billig sein.

    Vor allem aber löst es das Problem nicht. Wie würdest du

    C& component = entity.getComponent<C>();
    

    implementieren? Diese Funktionalität brauchst du, wenn du von einer Komponente auf eine andere zugreifen willst (z.B. Movement benötigt Informationen über Pathfinding). Wie löst du Abhängigkeiten zwischen Komponenten auf?

    Wenn du die Idee umkehrst, sodass Komponenten mehrere Entities enthalten und nicht umgekehrt, kannst du natürlich

    componentContainer.find(entity)
    

    benutzen -- nur, wie kommst du von C auf componentContainer ?

    Wäre wirklich interessant, das zu klären. Wenn du generisch bleiben willst, musst du irgendwo Typ-IDs benutzen.



  • Voneinander abhängige Komponenten sollten wohl auch voneinander wissen. Sobald sie das tun, können sie über typspezifische Interfaces, welche nur den abhängigen Komponenten bekannt sein müssen, entsprechende Informationen austauschen (die Entity ID ist ja global bekannt). Sobald das Movement das Pathfinding kennt (muss ja nur bei der Initialisierung übergeben werden), kann dieses z.B. die Positionen jederzeit dort abfragen. Umgekehrt kann das Pathfinding jederzeit das Movement fragen, welche Bewegungen von einem konkreten Entity ausgeführt werden können etc. Soweit ich das sehe sollte sich das alles so auch in einem ansonsten dynamischen System völlig typsicher und ohne Downcast welcher Art auch immer umsetzen lassen...



  • Du lässt die Frage unbeantwortet, wie sich Movement und PathFinding überhaupt erst kennen lernen. Verdrahtest du etwa manuell Zeiger zwischen allen abhängigen Komponenten? Das ist unnötiger und fehleranfälliger Verwaltungsaufwand, der einfach automatisiert werden kann.

    Ne, idealerweise sagt die Movement-Implementierung: "Gib mir einen Zeiger auf das PathFinding der gleichen Entity". Nach einem Mal kann man den immer noch cachen, um die Abfrage zu sparen. Der Punkt ist, dass du diese Information über eine Komponenten-Tabelle lösen musst, wenn du Hardcoding vermeiden willst -- eine Tabelle, deren Schlüssel Typ-IDs sind.



  • Nexus schrieb:

    Du lässt die Frage unbeantwortet, wie sich Movement und PathFinding überhaupt erst kennen lernen. Verdrahtest du etwa manuell Zeiger zwischen allen abhängigen Komponenten? Das ist unnötiger und fehleranfälliger Verwaltungsaufwand, der einfach automatisiert werden kann.

    Na dann automatisier es halt, sobald sie sich kennen, kennen sie sich; seh nicht, wo es da jetzt konkret scheitert...

    Nexus schrieb:

    Ne, idealerweise sagt die Movement-Implementierung: "Gib mir einen Zeiger auf das PathFinding der gleichen Entity". Nach einem Mal kann man den immer noch cachen, um die Abfrage zu sparen. Der Punkt ist, dass du diese Information über eine Komponenten-Tabelle lösen musst, wenn du Hardcoding vermeiden willst -- eine Tabelle, deren Schlüssel Typ-IDs sind.

    Nun, ich würds nicht so lösen, da ich keinen Sinn darin sehe. Irgendwo muss festgelegt werden, genau welche Komponenten (in genau welcher Reihenfolge) geladen werden müssen. Und das wird wohl irgendwo ganz oben in der Anwendung passieren, da die Anwendung ohne zumindest ihre Kernkomponenten sowieso nicht funktioniert. Ergo würd ich den jeweiligen Komponenten, ganz im Sinne von Dependency Injection, bei der Instanzierung einfach Zeiger auf die entsprechenden anderen Komponenten übergeben oder ähnliches. Man kann es natürlich auch voll data driven machen oder was weiß ich, macht aber in 99% der Fälle vermutlich alles nur unnötig kompliziert...



  • Bei der Instanzierung fällt aus, weil das eine fixe Anzahl von typisierten Komponenten voraussetzen würde. Die Kernidee ist es aber doch, die Einschränkungen, die man durch eine fixe Anzahl von Komponententypen und Abhängigkeiten durch die gesamte Quellcodebasis ziehen würde, zu vermeiden.

    Ob mann nun aber get<X>(entity) oder entity.get<X>() schreibt, ist ja ziemlich Rille, hinter den Vorhängen könnte beides auch ohne GetType ineinander übersetzt werden.
    Man verliert ohne eine GetType-Version allerdings die Möglichkeit, die Verwaltung für Plugins umzusetzen, weil alle Komponententypen zur Compile-Zeit bekannt sein müssen.
    Außerdem verliert man auch eine Menge Möglichkeiten im Zusammenhang mit Skripting und Serialisierung meinetwegen, wenn man nicht irgendwelche Typinformationen zur Verfügung hat.
    type_index hat nicht die Skripting- und Serialisierungs-Problematik, allerdings ebenfalls die Probleme mit Plugins.


Anmelden zum Antworten