Konzept einer Ereignisbehandlung



  • Hallo zusammen,

    ich wende mich heute mal wieder mit einem gedanklichen Problem an Euch.

    Mit schwebt vor, ein "Pong" strikt nach Model-View-Controller (MVC) zu schreiben. Hierbei soll vor allem das Model (in meinem Konzept die Spiellogik) vollkommen unabhänging von der Ausgabe (Bildschirm, Sound, etc.) und der Eingabe (via SDL-Events[1]) verarbeitet werden.

    Die Kommunikation zwischen den drei Komponenten soll über Ereignisse abgewickelt werden (auf welche reagiert werden KANN) - der Spiellogik ist es schlussendlich egal, ob die Züge dargestellt werden oder mit Musikeffekten untermalt werden. Rein theoretisch wären alle Komponenten nahtlos austauschbar.

    Ereignisse werden von der Basisklasse "Event" abgeleitet.

    Die Verteilung der Ereignisse erfolgt natürlich nicht willkürlich. So sollen sich Ereigniss-Handler (welche von der abstrakten Klasse "Handler" erben) beim Ereignisverteiler für jenen Ereignistyp registrieren, für welchen sie Ereignisse empfangen wollen.

    ---

    Mir ist nun in den Sinn gekommen, der Basisklasse "Event" ein Typ-Attribut zu verpassen. Anhand dieser Typ-ID werden nun die Ereignisse an die jeweiligen Handler verteilt.

    Meine Schwierigkeit (mehr ein Unbehagen) liegt nun darin, dass der jeweilige Handler entscheiden muss, welches Ereignis er gerade erhalten hat. Dies ist m. E. nur über die Abfrage der Typ-ID möglich. Hängen dem Ereignis weitere Attribute an (Positionen, Objektreferenzen, etc.) so bleibt mir nichts anderes übrig, als einen dynamischen Cast zu verwenden. Ich persönlich finde das unschön und ganz wohl ist mir bei dem Gedanken auch nicht.

    (Quelltext liegt im übrigen noch keine einzige Zeile vor, ich plane derzeit noch.)

    ---

    Was haltet Ihr von diesem Konzept?
    Gibt es bessere Lösungen?

    In diesem Sinne grüßt... Heiko

    [1]: http://www.libsdl.org



  • Hab das bisher, zumindest im Netzwerk, wie folgt gehandhabt, da die Nachrichten in der Regel eh sehr klein sind:

    struct Message{
        MessageID id;
        union MessageData{
            SoundRequest   sr;
            MoveRequest    mr;
            DisplayRequest dr;
            // ...
        } data;
    };
    

    Weiß jetzt nicht ob die Schreibweise korrekt ist... Wenn das ganze eh in einem Prozess läuft wären Pointer sicherlich Bandbreitenschonender, wirst dann um casts aber kaum herumkommen. Außer du machst zB ein union mit den unterschiedlichen Pointertypen drin, wobei ich gerade nicht einmal weiß ob das überhaupt geht. 😉

    EDIT: Was spricht gegen SDL_Event?



  • Gegen SDL_Event spricht als solches nur die Tatsache, dass die Applikationslogik unabhängig von libsdl sein soll.

    Nichts desto trotz habe ich gerade (mal eben) etwas zusammen gefummelt, was meiner Idee am nächsten kommt (Programmschleife, Queues und Handler-Liste muss man sich dazudenken). Die Basisklassen habe ich nicht aufgeführt, da diese recht simpel sind:

    #define EVT_TEXT 123
    
    // ----------------------------------------------------------------------------
    class TextEvent : public Event
    {
    private:
        string message_;
    public:
        TextEvent(const string& pmessage)
            : Event(EVT_TEXT), message_(pmessage)
            { }
        const string& message() const { return message_; }
    };
    // ----------------------------------------------------------------------------
    class Printer : public Handler
    {
    public:
        void on_event(const Event& event)
            {
                const TextEvent& e = dynamic_cast<const TextEvent&>(event);
                cout << e.message() << endl;
            }
    };
    // ----------------------------------------------------------------------------
    int main()
        {
            Printer printer;
            TextEvent e1("Hallo Welt!");
            printer.on_event(e1);
            return EXIT_SUCCESS;
        }
    

    Dieses const TextEvent& e = dynamic_cast<const TextEvent&>(event); macht auf mich persönlich den Eindruck eines ziemlichen Gemurkses aber es erfüllt seine Aufgabe. Ggf. sollte man vorher den Typ abfragen und eine möglich Bad-Cast-Exception auffangen.

    Grüße... Heiko



  • Warum benutzt du denn nicht konkret TextEvent als Parameter?

    void on_event(const TextEvent& event)
    

    Oder soll diese Methode alle Events der Klasse (bzw. des Objekts) bearbeiten?



  • Der Ereignisqueue nimmt konkret Zeiger auf Objekte vom Typ "Event" auf. Somit kann ich gewährleisten, dass alle möglichen Ereignisse darüber abgewickelt werden können. Des weiteren wird es einen Container mit Zeiger auf Handler-Objekte geben - diese müssen die genannte on_event-Methode implementieren.

    Es kann auch sein, dass sich ein und der selbe Handler mehrerer Ereignistypen annimmt.

    So wie es aussieht, scheint keine elegantere Möglichkeit zu existieren, als das Casting.

    Grüße... Heiko



  • Willst du das nicht nochmal überdenken? Irgendwie scheint das ein relativ unsauberer Ansatz zu sein, oder hat der auch seine Vorteile? Soll die "on_event"-Funktion auch Tastatur- und Maus-Nachrichten usw empfangen?
    Und gibt's einen konkreten Grund, warum du dich gegen Interfaces entschieden hast (reine Neugierde)?



  • Ja, ich grüble ja noch. Wenn Ihr "saubere" Quellen habt, nehme ich die gerne an. Fertige Frameworks bringen mir persönlich jetzt nichts, da ich das von Grund auf nachvollziehen wollte.

    Und ja, mir kommt das auch sehr unsauber vor...

    Grüße... Heiko



  • Also ich kenne Events so in dieser Art, finde es eigentlich auch ganz angenehm:

    // Maus-Krams:
    class MouseReceiver
    {
        public:
            virtual void OnMouseMove( int x, int y, int dx, int dy ) = 0;
    };
    
    class Mouse
    {
        public:
            void addReceiver( MouseReceiver* r )
            {
                recv.push_back( r );
            }
    
        protected:
            void HierWirdFestgestelltDassSichDieMausBewegtHat()
            {
                for ( alle recv )
                    recv[i]->OnMouseMove( ... );
            }
    
        private:
            vector<MouseReceiver*> recv;
    };
    
    // Die Klasse, die Mausnachrichten empfangen will:
    #include "MausKrams.h"
    
    class Sonstwas : public MouseReceiver
    {
        public:
            void OnMouseMove( int x, int y, int dx, int dy )
            {
                // Drauf reagieren
            }
    };
    
    // Registrieren..
    Mouse miep;
    Sonstwas yeah;
    miep.addReceiver( &yeah );
    

    So gibt es relativ wenige Abhängigkeiten und es bleibt recht flexibel. Ich weiß aber natürlich nicht, inwieweit es in dein Konzept passt, oder es bessere Alternativen gibt 🙂


Anmelden zum Antworten