shared_ptr - Alternative?



  • Hallo, in meinem aktuellen Projekt nutze ich ein Component Based Design (bzw. ein Variante des Patterns), bei welchem ich alle zu einer Entity (diese besteht nur aus einer ID) gehörenden Komponenten in einem Vektor und die Zuordnung Entity -> Komponenten in einer Map speichere.
    Komponenten sind lediglich eine Ansammlung von Daten, welche dann von den "Komponentensystemen" verarbeitet werden.
    Um verschiedene Komponenten in einem Vektor zu speichern nutze ich einen shared_ptr auf die Basisklasse.
    Bei der Verarbeitung der Komponenten werden diese dann zur Spezialisierung gecasted (ich kann den richtigen Typ garantieren, da ich bei der Erstellung von Komponenten immer speichere, welcher Entität diese gehört, d.h. der cast ist niemals falsch), d.h. zwar werden die Komponentne woanders verarbeitet, aber eigentlich ist der Verwalter/Besitzer der Entitätenmanager.

    Das geht nun nicht ganz d'accord mit der Ansicht über shared_ptr, welche ich so im Internet gefunden habe, das dies eher zur Modellierung von geteiltem Besitz dient.
    Grund für shared_ptr-Nutzung war für mich:
    a) Keine Leaks
    b) Kopierbar d.h. nutzbar in anderen Klassen die beim Entitätenmanager nachfragen, ob Entität A Komponente B besitzt und diese anfordert.

    unique_ptr würde das Verarbeiten in einer anderen Klasse nicht gestatten, nackte Pointer sind mir suspekt, da verliert man (oder zumindest ich) zu leicht die Übersicht und vergisst einen delete call.

    Wie sehen das denn die Forenpros, shared_ptr schlechter Stil in diesem Falle?



  • Die Idee wäre ja, dass die nackten Zeiger nur überall dort auftauchen, wo sich niemand um die Lebenszeit kümmert.



  • Wenn der Entitätenmanager der Verwalter/Besitzer ist, überlebt er dann wahrscheinlich auch die einzelnen Komponenten? Dann sollte der Entitätenmanager die Komponenten besitzen und rohe Pointer darauf rausgeben.

    Die Meinung hier im Forum ist:

    Skym0sh0 schrieb:

    Standard-C++ hat in seienr STL schon einige Smart-Pointer:
    - std::auto_ptr (alt, schlecht und deprecated)
    - std::shared_ptr (auf den ersten Blick das NonPlusUltra)
    - std::weak_ptr (eine Erweiterung für std::shared_ptr)
    - std::unique_ptr (der eigentliche Soll-Standard!)

    Bzw. meine Meinung ist shared_ptr/weak_ptr nur bei Multithreading mit immutable Datenstrukturen.

    Für den Rest unique_ptr (Besitzer) und rohe Pointer (Benutzer).

    Was bei dir auch nicht ganz klar ist, wieso die Komponenten in ihrer Basisklasse gehalten werden, wenn sie sowieso gecastet werden müssen. Oft reicht da auch ein unordered_map<Id, Typ1>, unordered_map<Id, Typ2>, aber das kommt auf die Anwendung drauf an.



  • Dann müsste ich für jede Komponente ne eigene map oder einen anderen Container verwenden. Find ich unschön, auf die Art wie ich es mache kann ich jede beliebige Komponente in der gleichen Datenstruktur speichern und ob des sicheren Wissens über den Typ beim casten ist dieses sicher. Das ist zwar langsamer als das Halten in separaten Strukturen, aber das nehme ich gerne in Kauf zugunsten einer zentraleren Lagerung.



  • Namenloser324 schrieb:

    unique_ptr würde das Verarbeiten in einer anderen Klasse nicht gestatten, [...]

    Wieso?

    Namenloser324 schrieb:

    Wie sehen das denn die Forenpros, shared_ptr schlechter Stil in diesem Falle?

    Hast du geteilte Besitzverhältnisse? Wenn nicht, dann ist shared_ptr automatisch unangebracht, denn der ist nunmal nur für den Fall geteilter Besitzverhältnisse gedacht, wie ja der Name schon sagt...



  • 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.


Anmelden zum Antworten