Ein einfaches Nachrichtensystem
-
Hallo da! Danke, dass du dir die Zeit genommen hast, die Textwand durchzuarbeiten, hehe. boost::signal kommt in meiner Problemstellung einfach nicht in Frage. Bei 20 Nachrichten, die von einem Objekt ausgehen, müsste ich ja 20 Signale pro Objekt speichern. Außerdem sind bestimmte Nachrichten auch nur für bestimmte Objekte interessant (Tastendruck zB. nur für das aktive Objekt im Widget-System), manche für eine ganze Gruppe (Eine Gruppe wäre dann eine externe ChainableMessageSink sozusagen, durch die man alle interessierten Objekte erreicht). boost::signal finde ich eher für Dinge gut, wie zB. bei Buttons, wo es dann z.B. ein clicked-Signal gibt, also wo sich die Menge der "Nachrichten" in überschaubaren Grenzen hält.
Viele Grüße,
Michael
-
Decimad schrieb:
Hallo da! Danke, dass du dir die Zeit genommen hast, die Textwand durchzuarbeiten, hehe. boost::signal kommt in meiner Problemstellung einfach nicht in Frage. Bei 20 Nachrichten, die von einem Objekt ausgehen, müsste ich ja 20 Signale pro Objekt speichern. Außerdem sind bestimmte Nachrichten auch nur für bestimmte Objekte interessant (Tastendruck zB. nur für das aktive Objekt im Widget-System), manche für eine ganze Gruppe (Eine Gruppe wäre dann eine externe ChainableMessageSink sozusagen, durch die man alle interessierten Objekte erreicht). boost::signal finde ich eher für Dinge gut, wie zB. bei Buttons, wo es dann z.B. ein clicked-Signal gibt, also wo sich die Menge der "Nachrichten" in überschaubaren Grenzen hält.
Viele Grüße,
MichaelHmm, Du kannst aber auch Signale erstellen, die nur einen bestimmten Scope überleben. Du musst sie nicht im Objekt speichern. Und empfangen werden Signale ja nur von verbundenen Slots. Vielleicht verstehe ich aber das Problem auch noch nicht so ganz.
{ boost::signals::scoped_connection c = sig.connect(ShortLived()); sig(); // will call ShortLived function object } sig(); // ShortLived function object no longer connected to sigWenn ein SIgnal also nur für einen Slot interessant ist, dann verbindest Du es nur mit dem Interessenten. Du kannst auch Slots blocken, so dass sie, solange sie im geblockten Zustand sind, keine Signale annehmen.
Um nochmal auf Deine Liste zurückzukommen:
Decimad schrieb:
- Möglichst wenige Handler-Funktionen, damit der Klassen-Namensraum von abgeleiteten Klassen (Nachrichtenempfängern) nicht verseucht wird.
- Es soll dennoch eine komfortable Nachrichtenbehandlung möglich sein.
- Die Nachrichten sollen durch einen Punkt in das Objekt geleitet werden, damit andere Objekte einfach zuhören können.
- Man soll der Klassendeklaration auf einen Blick ansehen können, welche Nachrichten sie behandelt (evtl. nicht so wichtig?).
- Es sollen Wege denkbar sein, diese Nachrichten über Sprachgrenzen hinaus weiterzuleiten (Lua, COM, ...)
- Nachrichten sollen standardmäßig Bottom-Up verteilt werden. Das ewige weiterleiten an die Basisklasse müffelt. Wenn man es wirklich anders will, kann man zur Zeit OnMessage überschreiben, seine Behandlung machen und gegebenenfalls an die Basisklassen weiterleiten.
Zu 1.: Da ist boost.signals besser, da jede Memberfunktion als Signalempfänger genutzt werden kann, und Du nicht von irgendetwas erben musst.
Zu 2.: boost.signals
Zu 3.: boost.signals macht sowas einfach
Zu 4.: Da helfen entweder sprechende Namen, oder Kommentare
Zu 5.: Da brauchst Du so oder so einen Empfänger, welcher die entsprechende Umsetzung macht.
Zu 6.: Da bist Du mit boost.signals deutlich freier, Da Du keine Basisklasse benötigst, und ein Signal beliebig (und auch auf unterschiedliche Arten) an Empfänger weiterleiten kannst.
-
Hallo da!
Ich verstehe dein Enthusiasmus für boost::signal und ich habe die auch bei bestimmten Szenarien gerne verwendet (das ist jetzt übertrieben, ich mochte mich nie an den bind/_1 kram für Memberfunktionen gewöhnen, den einem die boost::-varianten nie abnahmen, deshalb habe ich meine eigenen Callback-Funktions-Templates geschrieben).Im Prinzip weiß aber das Signal nicht, wen genau es adressiert, wenn es einen gewissen Slot "aufruft". Man kann zwar sogar die Reihenfolge bestimmen/sortieren, aber einen Slot einem bestimmten Objekt zuordnen oder alle Slots eines gewissen Objekts zu finden und dann nur diejenigen aufzurufen wird kaum so einfach gehen.
Was ist, wenn bestimmte Nachrichten eben nicht an alle versendet werden sollen, die sich zwar potentiell für den Nachrichtentyp interessieren, sondern nur ein Interessent gemeint ist?
Ein Beispiel: Ziemlich viele Widgets interessieren sich für einen Tastendruck (oder auch Bewegung, Größenänderung, Aktivierung...), aber nur das vom System als aktiv bestimmte Widget soll wirklich benachrichtigt werden.
All solche Events müssten also als signal pro Widget implementiert werden (Namensraum-verseuchung, Speicherverschwendung!). Dann könnten natürlich auch wieder andere Widgets (wie in meinem System) bei an ein bestimmtes Widget gerichtete Nachrichten mitlauschen.Jetzt könnte man natürlich die Ereignistypen zerlegen in die genannten Klassen und diese unterschiedlich behandeln, aber dann ist mindestens Punkt 3 verletzt und die Übersetzung der Ereignisse über Sprachgrenzen dürfte auch nicht mehr so einheitlich werden).
Vielleicht verstehe aber auch ich dich einfach gar nicht. Könntest du etwas pseudo-code für beispielsweise ein WidgetMgr/Widget-System schreiben, dass ich deinen Vorschlag besser überblicken kann?
Viele Grüße,
MichaelPS: Ach verdammt, muss jetzt hier vom Rechner weg, wahrscheinlich komme ich erst morgen wieder dazu, hier zu lesen -.-
-
Decimad schrieb:
Was ist, wenn bestimmte Nachrichten eben nicht an alle versendet werden sollen, die sich zwar potentiell für den Nachrichtentyp interessieren, sondern nur ein Interessent gemeint ist?
Ein Beispiel: Ziemlich viele Widgets interessieren sich für einen Tastendruck (oder auch Bewegung, Größenänderung, Aktivierung...), aber nur das vom System als aktiv bestimmte Widget soll wirklich benachrichtigt werden.
All solche Events müssten also als signal pro Widget implementiert werden (Namensraum-verseuchung, Speicherverschwendung!). Dann könnten natürlich auch wieder andere Widgets (wie in meinem System) bei an ein bestimmtes Widget gerichtete Nachrichten mitlauschen.typedef boost::signal<void (WidgetMessageType)> WidgetSignalType; void SomeSignalSource::SendSignal(WidgetMessageType const & messsage) { WidgetSignalType signal; //abstrakt. hier muss gebunden werden ;) boost::signals::scoped_connection c = signal.connect(WidgetManager::getInstance().getActiveWidget().getSlot()); signal(message); //hier sterben das Signal und die Verbindung wieder. Nur das aktive Widget hat die Nachricht bekommen. }
-
Nachtrag:
Die Message könnte bei boost.signals natürlich auch so etwas sein, wie Deine abstrakte Basisklasse + Message-Helper.
Dabei gibst Du aber Typsicherheit auf. T im Empfänger muss ja T im Sender entsprechen, sonst macht es *bumm*. Der
assertlässt Dich das immerhin merken.
Du hast dann also nur noch einen Slot, aber darin müssen dann die verschiedenen Messages ausgepackt werden. Das wird also eine Orgie ausswitch(message.id)undstatic_cast<T*>(message)(Decode muss ja für das passende T aufgerufen werden).
Anstatt vieler kleiner, typsicherer Funktionen bzw. Slots hast Du dann also eine dicke unsichere Funktion, welche das gleiche machen würde. Ob das gerade besser ist, sei mal dahin gestellt.
-
Guten Abend da! Man hat mir kurz den WLAN-Notebook geliehen, daher kann ich mich fix melden.
Dein Beispiel hätte nun ja eigentlich auch keinen Unterschied mehr zu einem einfachen Methodenaufruf, wenn ich das richtig sehe? Man verlöre ja sozusagen die Möglichkeit, Nachrichten an ein Fenster "mitzulauschen" und im Falle von der absoluten Typsicherheit müsste man auch alle nötigen Slots in jedem Widget zur Verfügung haben, selbst wenn es sich um beispielsweise Tastatureingaben nicht kümmert. Zudem könnte es ja mehrere Slots für ein Event in einem Objekt geben. In deiner typunsicheren Variante bliebe natürlich nur noch die Sache mit dem "mitlauschen" als "Mangel".
Dieses Mitlauschen ohne Einwirkung/Kenntnisnahme des belauschten finde ich aber in einigen Situationen echt praktisch (zb. beim Anheften von Widgets und ähnlichem).Ich weiß jetzt nicht ob ich wirklich gut erklärt habe, wie ich mein System derzeit verwende. Die switch/case Orgien finden eigentlich nur in den Sniffer-Klassen statt, die ich zu gewissen Verbundmengen von Nachrichten erstelle (z.B. alle Keyboard-Nachrichten, Maus-Nachrichten, ...). Ein Widget sieht dann zB. so aus:
class SampleWidget : public Widget, private KeyboardSniffer<SampleWidget>, private MouseSniffer<SampleWidget> { // ... public: void OnKeyDown( ... ); void OnMouseMove( ... ); }Das heißt, ich füge durch Erstellen von neuen Widgets keine neuen möglichen Fehlerursachen mehr her. Wenn der Sniffer korrekt funktioniert, dann ist alles, was ich in meinem Code von der ganzen Sache mitbekomme zumindest dann typsicher.
Verstehe mich auch bitte nicht falsch, ich versperre mich hier nicht aktiv anderen/besseren Ansichten (ansonsten hätte ich mich hier wohl auch nicht gemeldet, hehe, und ich bin dir dankbar für die viele schöne Zeit, deine Beispiele zB. brennen sich zur Wiederverwendung ebenfalls gerade in mein Hirn ein^^). Es ist nur so, dass ich boost::signal beim Entwurf des Widget-Systems schon in Betracht gezogen hatte, es dann aber nicht als durchweg anwendbar erachtete und mich dann auf die Suche nach einer Lösung begeben habe, die mir einheitlich anwendbar erschien.
Vielen vielen Dank ersteinmal und noch einen schönen Abend,
Michael
-
Ich würde gerne mal ein einfaches Anwendungsbeispiel sehen, wie Du Dir die Verwendung Deines Frameworks vorstellst.
Irgendwie ein Sender und zwei Empfänger, wobei ein Empfänger auf zwei unterschiedliche Nachrichten reagieren soll. Vor allem das Behandeln der Nachrichten im Empfänger in Abhängigkeit vom Nachrichtentypen interessiert mich.Wo soll denn Mitlauschen von Nachrichten geschehen? Doch bestimmt in den jeweiligen Empfängern, oder?
Verstehe mich auch bitte nicht falsch, ich versperre mich hier nicht aktiv anderen/besseren Ansichten (ansonsten hätte ich mich hier wohl auch nicht gemeldet, hehe, und ich bin dir dankbar für die viele schöne Zeit, deine Beispiele zB. brennen sich zur Wiederverwendung ebenfalls gerade in mein Hirn ein^^).
PS: Ich fühle mich nicht angegriffen, oder so. Ich bin mir nur ziemlich sicher, dass Du Dir unnötig viel Mühe machst. Viele Widgetsysteme benutzen einen Signal-Slot-Mechanismus, der dem von boost.signals sehr ähnlich ist. Für sowas ist das ganze eigentlich genau richtig.
-
Moin moin!
Also Beispielsweise sieht derzeit meine Umsetzung eines Widgets, das einen Knoten meines Graphen repräsentiert (Sagt man Controller? Mit Nomenklatur habe ich so meine Probleme^^), so aus:
class NodeWidget : public widgets::FrameWidget, public NodeObserver , visual::geometry_sniffer< NodeWidget > , visual::activation_sniffer< NodeWidget > , visual::geometry_sniffer< NodeWidget, visual::remote_multisniffer > { public: NodeWidget( GraphControl* view, NodePtr node ); ~NodeWidget(); // Interface public: InputWidget* GetInput ( const std::wstring& name ); OutputWidget* GetOutput( const std::wstring& name ); NodePtr Node() const; std::wstring Name() const; // State managment public: const detail::NodeWidget_properties& Properties() { return props_; } void EnterWiring(); void LeaveWiring(); // Sniffer handlers public: void OnMove( const visual::point& oldPos, const visual::point& newPos ); void OnSize( const visual::size& oldPos, const visual::size& newPos ); void OnXputMove( const visual::point& oldPos, const visual::point& newPos ); void OnXputSize( const visual::size& oldPos, const visual::size& newPos ); void OnActivate(); void OnDeactivate(); // Node Observer public: void NewInput( Input* i ); void LostInput( Input* i ); void NewOutput( Output* o ); void LostOutput( Output* o ); // ... };Erstmal behandelt es Größen/Positions/Aktivierungs-Nachrichten für sich selbst und lauscht auch auf Größenveränderungen seine Eingangs- und Ausgangswidgets (d.h. wenn diese die Größe verändern, wird das Layout neu berechnet, dass das Gesamtkonstrukt noch schön aussieht). Die unter Sniffer Handlers aufgeführten Methoden werden dann bei entsprechenden Nachrichten aufgerufen (Von den Sniffern).
Wenn jemand eine Nachricht an dieses Widget schicken will, dann einfach so:
Widget *pWidget = pNodeWidget; SomeMessage some(); some.Send( pWidget ); // Bearbeitung von eventuellen Ausgabe-Membern (Rückgabewerten) in some.Für Events, die gebroadcastet werden, kann ich mir ein Event von ChainableMessageSink ableiten (Also eigentlich nur einer ChainableMessageSink einen etwas prägnanteren Namen wie Event oder so geben), bei dem sich interessenten registrieren (ChainAdd) können und die Nachricht wird dann nur an dieses "Event" geschickt und damit automatisch an alle interessenten geschickt (Praktisch äquivalent zu einem signal, außer diese Typunsicherheit zwischen versendung und dem sniffer).
Hier sieht man zum Beispiel, dass ich mir NodeObserver eigentlich schenken könnte, weil das genau so über das Nachrichtensystem zu implementieren wäre (Und ich dann sozusagen mich nur einmal darum zu kümmern hätte, wenn ich Nachrichten auch über Sprachgrenzen zulassen möchte, weil alle Events und Nachrichten usw. gleichförmig verteilt werden)!
Die etwas anderen Namen (Groß/Kleinschreibung und Unterstriche) begründen sich dadurch, dass ich zwischen erster Implementierung und jetzt meinen Geschmack in Sachen Benennung geändert habe, nur falls das etwas komisch anmutet.
Einen schönsten Tag ersteinmal!
Michael
-
Puh, ich durchdringe Dein Codebeispiel zugegebenermaßen nicht wirklich.
Wenn Deine Lösung so gut funktioniert, und Deine Anforderungen durch Dein Framework erfüllt werden, dann würde ich das aber so lassen. Ich nehme mal an, dass schon einiges an Code da ist, was auf dem Framework aufbaut, oder? Dann macht es nur Sinn, was daran zu ändern, wenn es irgendwelche gravierenden Mängel an Deiner Lösung gibt.
-
Hurra, ich habe kurz Internet.
Wollte mich nur fix melden und nochmal danke sagen, ich werde das erstmal so weitermachen, ich kann es ja bei Problemen das nächste Mal einfach besser machen, wenn ich mehr Erfahrung mit diesem System gesammelt habe!Viele Grüße,
Michael