Multicast-Closures?



  • Finde ich auch eine sinnvolle Erweiterung, aber zwei Anmerkungen noch:

    Das Destroy sollte man dann aber im passenden Destruktor der Klasse machen, da du ja das Anlegen der multicasts im Konstruktor der Klasse (nicht in FormCreate) gemacht hast. Außerdem bin ich ebenso wie Akari der Meinung, das Destroyen zu automatisieren.

    Die Syntax finde ich noch nicht so elegant (auch wenn man dann wahrscheinlich für die verschiedenen Event-Typen typedefs anlegen kann).

    Besser fände ich es wenn man entweder

    T x = createMulticastEvent1 <void, TObject*> (Button1->OnClick);
    x.addEvent (this->Button1Click);
    x.addEvent (this->Button2Click);
    

    wobei T dann ein passender Typ sein müßte, oder aber noch besser
    Streaming verwenden könnte, d.h.

    createMulticastEvent1 <void, TObject*> (Button1->OnClick)
    .addEvent (this->Button1Click)
    .addEvent (this->Button2Click);
    
    // oder
    createMulticastEvent1 <void, TObject*> (Button1->OnClick)
    << this->Button1Click << this->Button2Click;
    

    Analog dann das Removen von Events mittels 'removeEvent' bzw. '>>'.



  • Kann mir einer von euch mal erklären wozu man so etwas braucht? Mir fällt auf Anhieb hier keine sinnvolle Anwendung ein.



  • Damit kann man besser abstrahieren, d.h. wenn man eine Basisklasse (z.B. einer Form) entwickelt hat und diese schon mit Events ausgestattet hat und jetzt möchte man diese weiter ableiten und auch neue Methoden an einen Event hängen, dann muß man sich immer einen Wolf schreiben, um beide Events abarbeiten zu lassen.

    Ich habe selbst eine Basisklasse entwickelt (für mein eigenes Framework zum Entwickeln von Spielen), welche z.B. das OnMouseMove und OnKeyPress schon benutzt. Nun kann die abgeleitete Klasse nicht einfach

    OnMouseMove = FormMouseMove;
    

    schreiben, da dann die Methode 'BaseForm.OnMouseMove' nicht mehr aufgerufen wird, d.h. diese muß ich dann explizit aufrufen (quasi wie bei virtuellen Methoden), vorausgesetzt ich habe diese dann wenigstens als 'protected' deklariert.
    Auch das Anhängen und Abkoppeln von (temporären) Events ist immer etwas lästig, z.B.

    TEvent *old = OnEvent;
    OnEvent = FormEvent;
    ... // do something: call a method which tiggers the event
    OnEvent = old;
    


  • Wie ist das denn dann mit der Reihenfolge beim Multicast?
    Gerade bei dem Beispiel von Th wäre es ja unter Umständen sinnvoll, das sein OnMouseMove der Basisklasse vor dem dynamisch zugewiesenen OnMouseMove durchgeführt wird.
    Edit:
    Evlt. wäre auch ein Abbruch des Multicast sinnvoll?!?



  • Hallo

    Ja das ist auch gut. Die Reihenfolge sollte wichtig/manipulierbar sein (nicht nur add, sondern auch insert?). Und bei dem sequentiellen Abarbeiten der Eventliste sollte ein Aufruf die Abarbeitung aller nachfolgenden Aufrufe verhindern können.
    Das dürfte nur schwer zu realiseren sein, weil aus der gewöhnlichen Eventmethode kein Signal an den Aufrufenden Multicast-Handler zurückgegeben werden kann. Dazu müßte man jeweils die Original-Signatur um einen zusätzlichen Parameter erweiteren, oder den Rückgabewert bool statt void verwenden.

    bis bald
    akari



  • Vielen Dank für euer Feedback!

    akari schrieb:

    Noch nicht so schön ist natürlich das solche MulticastEvents manuell gelöscht werden müßen. Vielleicht könnte man da noch etwas automatisieren?

    Das habe ich noch vor 😉
    Ich werde mal daran arbeiten, mal sehen, ob und wie das klappt. Momentan schwebt mir eine Version ganz ohne manuelle Vor- und Nacharbeit vor.

    Th schrieb:

    Das Destroy sollte man dann aber im passenden Destruktor der Klasse machen, da du ja das Anlegen der multicasts im Konstruktor der Klasse (nicht in FormCreate) gemacht hast.

    Das kann ja jeder machen, wie ihm beliebt. Zuerst hatte ich auch die Initialisierung in OnCreate, aber in der BCB-Dokumentation steht:

    C++Builder 6 Dokumentation - TCustomForm::OnCreate schrieb:

    Hinweis: Das Ereignis OnCreate sollte in C++Builder nicht verwendet werden, da es zu Konflikten mit dem Formularkonstruktor führen kann (siehe OldCreateOrder). Es wird empfohlen, statt dessen den Formularkonstruktor entsprechend zu überschreiben.

    Von OnDestroy wird nicht abgeraten. Da der Konstruktor immer automatisch generiert wird und OnDestroy sich über den Objektinspektor erstellen läßt, hatte ich dieses genommen.

    Th schrieb:

    Die Syntax finde ich noch nicht so elegant (auch wenn man dann wahrscheinlich für die verschiedenen Event-Typen typedefs anlegen kann).

    Besser fände ich es wenn man entweder

    T x = createMulticastEvent1 <void, TObject*> (Button1->OnClick);
    x.addEvent (this->Button1Click);
    x.addEvent (this->Button2Click);
    

    Das wäre zwar problemlos möglich, aber suboptimal - nicht immer möchte man ein Event dort hinzufügen, wo man die Events zu Multicast-Events macht. Und wenn die explizite Erstellung der Multicast-Events wegfallen sollte, geht das ohnehin nicht mehr.
    Wenn man überladene Operatoren verwendet, kann man leider nicht ohne weiteres Closures wie this->Button1Click übergeben (ursprünglich wollte ich += und -= anstatt von addEvent und removeEvent verwenden).

    -=]xXx[=- schrieb:

    Wie ist das denn dann mit der Reihenfolge beim Multicast?

    Reihenfolge des Hinzufügens.
    Ich könnte auch Iteratoren implementieren, mit denen sich die Eventliste manipulieren läßt...

    -=]xXx[=- schrieb:

    Evlt. wäre auch ein Abbruch des Multicast sinnvoll?!?

    Wie würdest du dir das konkret vorstellen? Einen optionalen Parameter würde ich eigentlich gern vermeiden.
    Vielleicht mit einer speziellen Exception...



  • audacia schrieb:

    -=]xXx[=- schrieb:

    Evlt. wäre auch ein Abbruch des Multicast sinnvoll?!?

    Wie würdest du dir das konkret vorstellen? Einen optionalen Parameter würde ich eigentlich gern vermeiden.
    Vielleicht mit einer speziellen Exception...

    Kein Plan 🙂
    Ich hatte nur die Idee 😉 Ich würde es vermutlich ganz unkompliziert über einen direkten Zugriffe auf irgendeine Flag machen... Ob das gut ist, darüber lässt sich noch streiten....

    mfg
    xXx



  • Mittlerweile bin ich schon recht weit fortgeschritten. Folgende Dateien dürften Funktionalität und Syntax ansatzweise demonstrieren:

    multicast_demo.hpp:

    //---------------------------------------------------------------------------
    
    #ifndef main_unitH
    #define main_unitH
    //---------------------------------------------------------------------------
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    
    #include <ucl/bcc/multicastsupport.hpp>
    //---------------------------------------------------------------------------
    class TFrmMain : public TForm
    {
    __published:	// Von der IDE verwaltete Komponenten
        TButton *Button1;
        TButton *Button2;
        TButton *Button3;
        TLabel *Label1;
        TMemo *MmoOutput;
        TButton *BtnSwap13;
        void __fastcall Button1Click(TObject *Sender);
        void __fastcall Button2Click(TObject *Sender);
        void __fastcall Button3Click(TObject *Sender);
        void __fastcall BtnSwap13Click(TObject *Sender);
    private:	// Benutzer-Deklarationen
        ucl::MulticastContainer mcc; // Besitzer der MulticastClosure-Objekte
    public:		// Benutzer-Deklarationen
    
        __fastcall TFrmMain(TComponent* Owner);
        void __fastcall AnotherEventHandler(TObject *Sender);
        void __fastcall FooBar(TObject *Sender);
    };
    //---------------------------------------------------------------------------
    extern PACKAGE TFrmMain *FrmMain;
    //---------------------------------------------------------------------------
    #endif
    

    multicast_demo.hpp:

    //---------------------------------------------------------------------------
    
    #include <vcl.h>
    #include <ucl/bcc/multicast.hpp>
    #pragma hdrstop
    
    #include "main_unit.h"
    #include <ucl/bcc/multicastspec.hpp>
    
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TFrmMain *FrmMain;
    
    //---------------------------------------------------------------------------
    __fastcall TFrmMain::TFrmMain(TComponent* Owner)
        : TForm(Owner)
    {
            /*
             * ucl::asMulticast() erstellt das MulticastClosure-Objekt bei Bedarf.
             * die Objekte werden in der MulticastContainer-Klasse registriert und
             * gelöscht, wenn deren Destruktor aufgerufen wird.
             */
    
            // einen Event-Handler zu Button1->OnClick hinzufügen
        ucl::asMulticast (mcc, Button1->OnClick).push_back (this->AnotherEventHandler);
    
            // ein Closure explizit in ein Multicast-Closure umwandeln
        ucl::asMulticast (mcc, Button2->OnClick);
    
            // Referenz auf MultiCast-Objekt zurückgeben
        ucl::MulticastClosure1 <void, TObject*>& mc = ucl::asMulticast (mcc, Button3->OnClick);
    
            // einen Event-Handler hinzufügen
        mc.push_front (this->Button1Click);
    
            // MulticastClosure-Objekte definieren eine Containerschnittstelle
        ucl::MulticastClosure1 <void, TObject*>::iterator i = mc.begin ();
        mc.insert (++i, this->Button2Click);
    
            // diese Anweisungen sind äquivalent:
        //mc (this);               // direkter Aufruf des MulticastClosure-Objekts
        //Button3->OnClick (this); // Aufruf des Events, dem das Objekt gehört
    }
    
    //---------------------------------------------------------------------------
    
    void __fastcall TFrmMain::Button1Click(TObject *Sender)
    {
        MmoOutput->Lines->Add (AnsiString (__FUNC__) + " called.");
    }
    void __fastcall TFrmMain::Button2Click(TObject *Sender)
    {
        MmoOutput->Lines->Add (AnsiString (__FUNC__) + " called.");
        if (Sender == Button2)
        {
            ucl::MulticastClosure1 <void, TObject*>& mc
                = ucl::asMulticast (mcc, Button2->OnClick);
    
                // hier eine weitere Anwendung der Containerschnittstelle
            if (mc.contains (this->FooBar))
                mc.remove (this->FooBar);
            else
                mc.push_back (this->FooBar);
        }
    }
    void __fastcall TFrmMain::Button3Click(TObject *Sender)
    {
        MmoOutput->Lines->Add (AnsiString (__FUNC__) + " called.");
    }
    
    void __fastcall TFrmMain::AnotherEventHandler(TObject *Sender)
    {
        MmoOutput->Lines->Add (AnsiString (__FUNC__) + " called.");
    }
    void __fastcall TFrmMain::FooBar(TObject *Sender)
    {
        MmoOutput->Lines->Add (AnsiString (__FUNC__) + " called.");
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TFrmMain::BtnSwap13Click(TObject *Sender)
    {
        ucl::asMulticast (mcc, Button1->OnClick)
            .swap (ucl::asMulticast (mcc, Button3->OnClick));    
    }
    //---------------------------------------------------------------------------
    

    ucl::asMulticast() läßt sich nun ohne explizite Angabe der Template-Parameter verwenden - allerdings nur, weil es in ucl/bcc/multicastsupport.hpp für TNotifyEvent spezialisiert wurde. Leider ist der BCC hier etwas buggy.

    ucl::MulticastClosureN <void, TObject*> ließe sich seit C++Builder 2007 auch in Funktorschreibweise schreiben (MulticastClosure <void (TObject*)>), aber das habe ich noch nicht implementiert.

    Hier das Demoprojekt, allerdings noch ohne den interessanten Quelltext - den muß ich noch ein wenig überarbeiten.



  • Aus dem Projekt habe ich mal eine kleine Bibliothek gebastelt, die hier verfügbar ist:
    http://www.audacia-software.de/de/win/ucl/index.htm

    Header-Dateien, Quelltext, Demo-Projekt und Dokumentation liegen bei. Die Bibliothek läuft ab C++Builder 6 (mit C++Builder 5 habe ich es noch nicht getestet).

    Über Feedback jeder Art würde ich mich freuen 🙂



  • Update auf v0.1.1:
    - Unterstützung für C++Builder 5, 6, 2006 und 2007 (auf C++Builder 3 und 4 habe ich keinen Zugriff, und C++Builder 1 ist definitiv zu alt)
    - kleine Bugfixes

    Leider scheint das Interesse ja doch nicht so groß gewesen zu sein...



  • audacia schrieb:

    Leider scheint das Interesse ja doch nicht so groß gewesen zu sein...

    Also ich kann nur für mich sprechen, also ich wüsste nicht wo ich das einsetzten sollte bzw. hat mir diese Funktionalität noch nie gefehlt.



  • Das geht mir genau so. 😞



  • Ich hab in meinem Projekt einen Anwendungsfall, hab das aber einfach über nen Vector mit Funktions-Pointern gelöst.
    Und momentan hab ich keine Zeit das umzubauen ^^

    mfg
    xXx



  • VergissEs schrieb:

    Also ich kann nur für mich sprechen, also ich wüsste nicht wo ich das einsetzten sollte

    Beispiel: ein dezentrales Kommunikationstool (z.B. ein Multiuser-Chat ohne zentralen Server). Auf das Senden einer Nachricht durch einen "Send"-Button müssen alle Teilnehmer reagieren. Für die Kommunikation mit den Teilnehmern sorgt jeweils eine Instanz einer Klasse. Jedes dieser Objekte registriert mittels Multicast-Closure einen Eventhandler bei dem OnClick-Event des Buttons. Wenn Chat-Teilnehmer und entsprechende Objekte hinzukommen, registrieren diese zur Laufzeit einen weiteren Handler, wenn sich ein User verabschiedet, entfernt das Objekt im Destruktor seinen Handler wieder.

    Natürlich ginge das auch anders. Aber das dürfte die sauberste Methode sein 😉



  • Ich würde für diesen Fall eine Liste der Klasseninstanzen nehmen und die dann abarbeiten. 🙂



  • Das ist ein konzeptioneller Unterschied. Im einen Fall melden sich die "Listeners" zentral beim Ereignis an, im anderen kümmert sich das Ereignis selbst um die Verteilung.

    Klar kann man das auch anders machen, aber wenn du so etwas mit mehreren Events zugleich hast, mußt du für jedes einen Wrapper bauen. Und da wird die Multicast-Closure-Lösung dann plötzlich viel übersichtlicher 😉


Anmelden zum Antworten