dynamic_cast böse



  • volkard schrieb:

    cooky451 schrieb:

    std::map<ComponentType, std::unique_ptr<Component>> components_;
    

    Quark.
    http://en.wikipedia.org/wiki/Inner-platform_effect

    Hm. Was schlägst du als Verbesserung vor? Oder würdest du weiter ein OOP Modell bevorzugen?



  • cooky451 schrieb:

    volkard schrieb:

    cooky451 schrieb:

    std::map<ComponentType, std::unique_ptr<Component>> components_;
    

    Quark.
    http://en.wikipedia.org/wiki/Inner-platform_effect

    Hm. Was schlägst du als Verbesserung vor? Oder würdest du weiter ein OOP Modell bevorzugen?

    Du machst hier einen Baukasten, der Layering ermöglicht, falls ich den Sinn recht verstehe, obwohl das viel besser als Sprachmittel schon da ist.
    Was ich als Verbesserung vorschlage? Na, Löschen natürlich.



  • Erstmal zu einer Implementierung von sowas wie der entity Klasse:
    Das kann man noch etwas sauberer machen, indem man type erasure verwendet. Ich selbst habe eine Klasse die dieses interface anbietet:

    class entity
    {
        template< class Type >
        bool add_component( Type && ); //false falls Type schon in entity
        template< class Type >
        Type *query(); //gibt pointer auf die interne Instanz von Type zurück (oder 0)
    };
    ////////////////////////////////////////////////////////////////////
    class A{};
    entity ent;
    ent.add_component( A() );
    ent.query< A >();
    

    Allerdings ist das nicht Sinn eines entity systems.
    Ich speichere jeden Komponententyp in einem seperaten Container ab (nicht nur Pointer, die eigentlichen Instanzen). Jedes Komponentobjekt hat zusätzlich einen Pointer auf eine Struktur, die nicht mehr beinhaltet als eine id und Referenzzähler und damit die entity repräsentiert. Jeder id können so mehrer Komponenten zugeordnet sein (aber immer nur 1 je Typ).
    Nachdem eine entity zum entity system hinzugefügt wurde, übergebe ich jedem Komponenten eine Klasse, in der Pointer auf alle zur entity gehörigen Komponenten per query (s.o.) abgefragt werden können. Die Komponente kann damit anstellen, was sie will, z.B. pointer auf andere Komponenten speichern (die RenderComponent braucht einen pointer auf die TransformComponent, um an der richtigen Stelle gezeichnet werden zu können), oder callbacks in anderen Komponenten setzen (z.B. sich bei onCollision in der TransformComponent registrieren).
    Im mainloop muss jetzt nur jedes sub system einmal geupdatet werden. Der Renderer wird beispielsweise über den Container mit RenderComponent s iterieren und jedes Objekt zeichnen (oder auch nicht, je nachdem). Das PhysicsSystem wird sich darum kümmern, dass auf Kollision überprüft wird und evtl. callbacks aufrufen.
    Man spart sich virtual function calls ohne Ende, und hat zusätzlich verschiedene Aspekte der Simulation voneinander getrennt (Warum sollte Grafik irgendwas mit Input zu tun haben?). Das eröffnet die Möglichkeit, mehr parallel abzuarbeiten.

    volkard schrieb:

    cooky451 schrieb:

    std::map<ComponentType, std::unique_ptr<Component>> components_;
    

    Quark.
    http://en.wikipedia.org/wiki/Inner-platform_effect

    Das sehe ich nicht so. Wie würdest du denn das Problem mit Lastwagen, Panzer und Geschützturm von vorhin mit Polymorphie lösen?
    Außerdem ist rtti viel langsamer als es sein könnte, weil die Compilerbauer Rücksicht auf dlls nehmen. Da kann es sinnvoller sein, sich selbst was zu bauen, vor allem wenn es von außen mindestens genauso gut aussieht (und nichtmal besonders komplex ist).



  • GorbGorb schrieb:

    volkard schrieb:

    Quark.
    http://en.wikipedia.org/wiki/Inner-platform_effect

    ]
    Das sehe ich nicht so. Wie würdest du denn das Problem mit Lastwagen, Panzer und Geschützturm von vorhin mit Polymorphie lösen?

    Also das da:
    Lastwagen: beweglich, unbewaffnet
    Panzer: beweglich, bewaffnet
    Geschützturm: unbeweglich, bewaffnet

    a) bewegeDich() auf Geschütztürmen tut nichts.
    a1) Geschütztürme haben eine Geschwindigkeit von 0
    c) Lastwagen und Panzer erben von BeweglichesDing
    c1) und getBeweglichesDingPtr(){return this;}
    Ach, mir fiele schon was passendes ein, denke ich.

    GorbGorb schrieb:

    Außerdem ist rtti viel langsamer als es sein könnte, weil die Compilerbauer Rücksicht auf dlls nehmen. Da kann es sinnvoller sein, sich selbst was zu bauen,

    Ups, hab ich in c1) ja schon gemacht.

    GorbGorb schrieb:

    vor allem wenn es von außen mindestens genauso gut aussieht (und nichtmal besonders komplex ist).

    Im Gegensatz zu Dir, sehe ich nicht, was durch solche Entities gelöst wird, was sich anders nicht viel besser anfühlen würde. Zum Beispiel denke ich beim Bedarf einer Liste aller beweglichen Objekte zuerst an eine intrusive doppelt verkettete Liste.



  • @volkard
    Hast du das schon mal mit etwas komplexeren Dingen ausprobiert? Bei mir endet das dann so:

    class Object
    {};
    class MovingObject : virtual public Object
    {};
    class VisibleObject : virtual public Object
    {};
    class PhysicalObject : virtual public Object
    {};
    class ActiveObject : virtual public Object
    {};
    ...
    

    Und jedes neue Objekt muss weiter hart gecoded werden. Modulbasiert kannst du die Objekte z.B. einfach aus einem Script parsen - der C++ Code muss nicht mal wissen, was für Objekte es in dem Spiel eigentlich gibt.
    Es geht natürlich auch so, aber wie verhinderst du dann die dynamic_cast Orgie?



  • cooky451 schrieb:

    @volkard
    Hast du das schon mal mit etwas komplexeren Dingen ausprobiert?

    Nein. Komplexere Dinge vermeide ich.



  • cooky451 schrieb:

    class Object
    {};
    class MovingObject : virtual public Object
    {};
    class VisibleObject : virtual public Object
    {};
    class PhysicalObject : virtual public Object
    {};
    class ActiveObject : virtual public Object
    {};
    ...
    

    Ich fürchte, dass diese 4 Unterklassen bei mir eine einzige Klassen wären, die die verschiedenen Möglichkeiten einfach durch interne states kodiert. Ich schätze, dass in dem Fall die Klassen, die eine Kombination dieser 4 sind einfach gegenüber den Spezialfällen überwiegen.



  • volkard schrieb:

    Also das da:
    Lastwagen: beweglich, unbewaffnet
    Panzer: beweglich, bewaffnet
    Geschützturm: unbeweglich, bewaffnet

    a) bewegeDich() auf Geschütztürmen tut nichts.
    a1) Geschütztürme haben eine Geschwindigkeit von 0
    c) Lastwagen und Panzer erben von BeweglichesDing
    c1) und getBeweglichesDingPtr(){return this;}
    Ach, mir fiele schon was passendes ein, denke ich.

    Es läuft am Ende darauf hinaus, dass ein Objekt über Schnittstellen verfügt, mit denen es überhaupt nichts zu tun hat. Wenn eine Simulation ausreichend groß wird, werden sicher ziemlich viele verschiedene Eigenschaften von einem anderen Objekt abgefragt werden müssen. Soll für jede einfach das GameObjekt eine neue virtuelle Funktion bekommen?
    Code wird nicht leserlicher und verständlicher, wenn ein unbeweglicher Geschützturm in irgendeiner Form von Moving abgeleitet wird.



  • GorbGorb schrieb:

    Es läuft am Ende darauf hinaus, dass ein Objekt über Schnittstellen verfügt, mit denen es überhaupt nichts zu tun hat. Wenn eine Simulation ausreichend groß wird, werden sicher ziemlich viele verschiedene Eigenschaften von einem anderen Objekt abgefragt werden müssen. Soll für jede einfach das GameObjekt eine neue virtuelle Funktion bekommen?

    Das kommt darauf an, wie der Raum der Objekte aussieht. Ist er sehr strukturiert, da sheißt, gibt es starke Abhängigkeiten zwischen den Eigenschaften, dann lässt sich das Problem mit mehreren abgeleiteten Klassen problemlos erschlagen.

    Anders sieht es wiederum aus, wenn es wenig Struktur gibt, und die Eigenschaften weitestgehend unabhängig voneinander sind. Dann kann man wirklich in ein Problem der exponentiell vielen Klassen laufen. Meistens ist das aber nicht wirklich der Fall. Statistisch dominieren nur wenige Objekttypen, und der Rest sind Spezialfälle und "Was-wäre-wenns" in der Projektplanung.

    Ich habe deinen Link durchgelesen, bis ich dann nach Seite 3 merkte, dass hinter dem Geschwurbel einfach Data-Oriented Programming steht. Die Idee, voneinander unabhängige Daten getrennt zu speichern und zu verarbeiten ist alt. Und im Gegensatz zu dem Artikel ist das kein Gegensätzliches Konzept zur OOP -> Aggregation. Und es funktioniert auch nur in so wenig Spezialfällen, dass man daraus keine allgemeine Regeln ableiten kann. Die Fälle, in denen die Eigenschaftskombinationen unabhängig voneinander sind, sind wenige. Meistens kann zwar Eigenschaft A ohne Eigenschaft B auftreten, aber wenn beide zusammen auftreten, beeinflussen sie sich gegenseitig. Und schon geht das tolle data oriented programming den Bach runter.



  • cooky451 schrieb:

    @volkard
    Hast du das schon mal mit etwas komplexeren Dingen ausprobiert? Bei mir endet das dann so:

    class Object
    {};
    class MovingObject : virtual public Object
    {};
    class VisibleObject : virtual public Object
    {};
    class PhysicalObject : virtual public Object
    {};
    class ActiveObject : virtual public Object
    {};
    ...
    

    Ach und ein aktives, sichtbares, bewegliches Objekt soll dann wiederum von MovingObject, VisibleObject, ActiveObject abgeleitet werden?

    Viel Spaß bei einer unpflegbaren Klassenhirachie. Vielleicht schon einmal das Strategiemuster angeschaut?

    cooky451 schrieb:

    Es geht natürlich auch so, aber wie verhinderst du dann die dynamic_cast Orgie?

    Auch wenn es nicht unbedingt die Ideallösung ist:

    Object * object = ...;
    if(object->MovingStrategie)
      object->move();
    


  • Ich hab etwas ähnliches wie asc:

    Object * object = ...;
    if (object->is(enum_of_types::MOVABLE))
      static_cast<Movable*>(object)->move();
    

    Hält zwar das Interface sauber, aber zufrieden bin ich damit nicht.



  • asc schrieb:

    Ach und ein aktives, sichtbares, bewegliches Objekt soll dann wiederum von MovingObject, VisibleObject, ActiveObject abgeleitet werden?

    Viel Spaß bei einer unpflegbaren Klassenhirachie. Vielleicht schon einmal das Strategiemuster angeschaut?

    Das war gerade ein negativ Beispiel, aber ist ja toll, dass es so gut ankommt. 😉

    otze schrieb:

    Ich fürchte, dass diese 4 Unterklassen bei mir eine einzige Klassen wären, die die verschiedenen Möglichkeiten einfach durch interne states kodiert. Ich schätze, dass in dem Fall die Klassen, die eine Kombination dieser 4 sind einfach gegenüber den Spezialfällen überwiegen.

    Bedenke dass man hierüber auch Dinge wie Sounds, Trigger, Licht, etc. regeln möchte. Das würde das Interface irgendwann halt schon zumüllen.



  • dynamit-karst schrieb:

    Ich hab etwas ähnliches wie asc:

    Ich sehe da schon gravierende Unterschiede im Detail.
    1. Komposition versus Vererbung.
    2. In deinem Fall ist ein Cast nötig.


  • Administrator

    asc schrieb:

    Auch wenn es nicht unbedingt die Ideallösung ist:

    Object * object = ...;
    if(object->MovingStrategie)
      object->move();
    

    Um es ideal zu machen: Null-Objects einführen. Stationäre Objekte haben dann z.B. die Strategie "Stationary". Eine Überprüfung ist dadurch nicht mehr nötig. Die Null-Objects fangen dies ab.

    Grüssli



  • asc schrieb:

    Auch wenn es nicht unbedingt die Ideallösung ist:

    Object * object = ...;
    if(object->MovingStrategie)
      object->move();
    

    Und die Aussage dessen ist wohl dann: Jedes Object könnte move() können. Ob es das wirklich kann, darf man (leider erst) zur Laufzeit abfragen, wenn man will. Die genau Abbildung, welche Klasse was kann, will ich dem Compiler nicht verraten, das würde zu kompliziert.

    Dann kannste auch in der Basisklasse virtual move(){} anbieten, die bei manchen Klassen nichts tut (oder Fehler wirft).

    Falls das Stategy-Pattern verwendet wird, um aktuelle Befehle zu speichern, kannst nach außen wieder versteckt werden mit

    void HottePferd::move(){
      if(this->MovingStrategie)//Diese Zeile fällt mit Draveres Null-Strategie weg
        this->MovingStrategie(this);
    }
    

    Von außen würde ich vermutlich gerne nur move() aufrufen und mich nicht um das Pattern kümmern müssen.


Anmelden zum Antworten