[Gelöst]Mulitcast Events

Für allgemeine Fragen zur Programmierung, welche nicht! direkt mit Lazarus zu tun haben.
Antworten
Marsmännchen
Beiträge: 294
Registriert: So 4. Mai 2014, 21:32
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10
CPU-Target: 64bit
Wohnort: Oranienburg

[Gelöst]Mulitcast Events

Beitrag von Marsmännchen »

Hi,

ich habe versucht, mit folgendem Code mehrere Handler an einen Event zu hängen:

Code: Alles auswählen

 
constructor TSpiel.Create;
begin
  FSpielfeld := TSpielfeld.Create;
  FView := TView.Create;
  (* Events mit den Handlern verbinden *)
  FSpielfeld.BeiSpielerBewegung := @FView.SpielerBewegen;
  FSpielfeld.BeiSpielerBewegung := @self.BewegungAusgefuehrt;
  FView.UserAktion := @FSpielfeld.VerarbeiteUserAktion;
  FView.UserAktion := @self.VerarbeiteUserAktion;
 
end;       

Funktionierte nicht, es wurde immer nur der zweite Handler aufgerufen. Wie ich beim Recherchieren erfuhr, unterstützt (Free-)Pascal keine sogenannten Multicast-Handler (also jenes Verhalten, dass ich in meinem jugendlichen Leichtsinn analog C# und Konsorten auch Pascal unterstellt habe). Ich habe weiterhin gelesen, dass es in Delphi ein Workaround für solches Verhalten gibt, über Interfaces etc... ziemlich komplex : http://www.delphipraxis.net/172339-auf-interface-basierende-multicastevents.html

Ich könnte das natürlich versuchen so nachzucoden. Allerdings bei meinem bisherigen Wissensstand wäre das ein blindes Hinterhertappen mit allen Risiken und Fehlversuchen und daher erlaube ich mir vorab die Frage, ob ihr eine bessere Lösung für mein Problem kennt. Letztlich geht es mir darum, dass zwei Objekte (vom Typ Spielfeld (= Model) und View) möglichst nur lose gekoppelt sind und sich gegenseitig mit Events über Aktionen oder Statusänderungen informieren. Die obigen Verknüpfungen von Event und Handler erfolgen in einem dritten Objekt names 'Spiel' ( = Controller).

EDIT: Wer sich den Code genauer ansehen will: ist jetzt beigefügt, funzt aber noch nicht richtig.

Nochmal EDIT: Eine Idee ist mir jetzt selbst gekommen. Ich kann natürlich auch händisch das Observerpattern implementieren und dabei alle Klienten in einer Liste halten, an die man sich als Beobachter anmelden und abmelden kann... oder?
Dateianhänge
MindedOut.zip
(5.55 KiB) 91-mal heruntergeladen
Zuletzt geändert von Marsmännchen am Sa 1. Okt 2016, 21:36, insgesamt 1-mal geändert.
Ich mag Pascal...

Thandor
Beiträge: 153
Registriert: Sa 30. Jan 2010, 18:17
OS, Lazarus, FPC: Windows 10 64Bit/ lazarus 3.0 mit FPC 3.2.2 (32Bit + 64bit)
CPU-Target: 64Bit
Wohnort: Berlin

Re: Mulitcast Events

Beitrag von Thandor »

Marsmännchen hat geschrieben:Nochmal EDIT: Eine Idee ist mir jetzt selbst gekommen. Ich kann natürlich auch händisch das Observerpattern implementieren und dabei alle Klienten in einer Liste halten, an die man sich als Beobachter anmelden und abmelden kann... oder?


Genau dass wollte ich dir gerade vorschlagen. Also einen Handler, der alle Anderen Handler nacheinander Aufruft.

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Mulitcast Events

Beitrag von m.fuchs »

Gibt es nicht sogar eine Observer-Implementation in der RTL?


Hab bisher noch nicht damit gearbeitet, aber wenn ich das richtig verstehe sparst du damit ein bisschen Aufwand für die eigene Implementierung.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Marsmännchen
Beiträge: 294
Registriert: So 4. Mai 2014, 21:32
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10
CPU-Target: 64bit
Wohnort: Oranienburg

Re: Mulitcast Events

Beitrag von Marsmännchen »

Danke für den Hinweis. Ich probier das mal aus...

... jetzt hab ich es mir mal angesehen. Ist ja ganz sinnvoll aufgebaut. Allerdings habe ich ein Problem mit dem Protokoll, welches für die Information der Observer verwendet wird (siehe http://www.freepascal.org/docs-html/rtl/classes/ifpobserved.fponotifyobservers.html). Wie übergebe ich den Daten per Pointer? Bisher übergebe ich bei den Events entweder einen Record oder ein Enum. Müsste ich dafür jetzt verschiedene ValueObjekte entwerfen? In der Doku zu FPONotifyObservers steht:
What Data is, depends on the implementor of the interface.
. Wenn Data ein Pointer ist, wie kann ich dann festlegen, welche Daten übergeben werden?
Hmmm, ich wollte ja auch mit unterschiedlichen Events arbeiten. Vermutlich passt das Interface der RTL nicht so recht für meine Zwecke... aber trotzdem Danke!
Ich mag Pascal...

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Mulitcast Events

Beitrag von Socke »

Marsmännchen hat geschrieben:Wie übergebe ich den Daten per Pointer? Bisher übergebe ich bei den Events entweder einen Record oder ein Enum. Müsste ich dafür jetzt verschiedene ValueObjekte entwerfen? In der Doku zu FPONotifyObservers steht:
What Data is, depends on the implementor of the interface.
. Wenn Data ein Pointer ist, wie kann ich dann festlegen, welche Daten übergeben werden?
Hmmm, ich wollte ja auch mit unterschiedlichen Events arbeiten. Vermutlich passt das Interface der RTL nicht so recht für meine Zwecke... aber trotzdem Danke!

Ein Pointer zeigt auf einen bestimmten Speicherbereich. Das kann ein Record, ein Objekt oder ein Enum sein - hängt also von deinem Programm ab.

Im folgenden Beispiel wird ein Zeiger auf einen Record übergeben. In diesem Record können dann weitere Daten, die zur Bearbeitung des Ereignisses benötigt werden, enthalten sein.

Code: Alles auswählen

program Project1;
 
{$mode objfpc}
 
uses
  Classes; 
 
type
  TCustomEvent = (ceOne, ceTwo);
  TEventRecord = record
    EventType: TCustomEvent;
    UserData: TObject;
  end;
  PEventRecord = ^TEventRecord;
 
  TEventObserved = class(TPersistent)
  public
    procedure DoIt;
  end;
 
  TOneEventObserver = class(TObject, IFPObserver)
    procedure FPOObservedChanged(ASender: TObject;
      Operation: TFPObservedOperation; Data: Pointer);
  end;
  TTwoEventObserver = class(TObject, IFPObserver)
    procedure FPOObservedChanged(ASender: TObject;
      Operation: TFPObservedOperation; Data: Pointer);
  end;
 
 
{ TTwoEventObserver }
 
procedure TTwoEventObserver.FPOObservedChanged(ASender: TObject;
  Operation: TFPObservedOperation; Data: Pointer);
var
  EventRecord: PEventRecord absolute Data;
begin
  if Operation = ooCustom then
  begin
    if EventRecord^.EventType = ceTwo then
      WriteLn('Two: ', EventRecord^.UserData.ClassName);
  end;
end;
 
{ TOneEventObserver }
 
procedure TOneEventObserver.FPOObservedChanged(ASender: TObject;
  Operation: TFPObservedOperation; Data: Pointer);
var
  EventRecord: PEventRecord absolute Data;
begin
  if Operation = ooCustom then
  begin
    if EventRecord^.EventType = ceOne then
      WriteLn('One: ', EventRecord^.UserData.ClassName);
  end;
end;
 
{ TEventObserved }
 
procedure TEventObserved.DoIt;
var
  // der übergebene Record ist eine lokale Variable auf dem Stack
  // daher kann man sich jegliche Speicherverwaltung sparen
  EventRecord: TEventRecord;
begin
  // Event 1
  EventRecord.EventType := ceOne;
  EventRecord.UserData := Self;
  FPONotifyObservers(Self, ooCustom, @EventRecord);
  // Event 2
  EventRecord.EventType := ceTwo;                 
  FPONotifyObservers(Self, ooCustom, @EventRecord);
end;
 
var
  Observerd: TEventObserved;
  OneObserver: TOneEventObserver;
  TwoObserver: TTwoEventObserver;
begin
  Observerd := TEventObserved.Create;
  OneObserver := TOneEventObserver.Create;
  TwoObserver := TTwoEventObserver.Create;
  Observerd.FPOAttachObserver(OneObserver);
  Observerd.FPOAttachObserver(TwoObserver);
 
  // Event auslösen
  Observerd.DoIt;
 
  Observerd.Destroy;
  OneObserver.Destroy;
  TwoObserver.Destroy;
  ReadLn;
end.

Wenn du viele verschiedene Nachrichten senden möchtest, die jeweils nur von wenigen Objekten bearbeitet werden, ist dieser Ansatz nicht wirklich effizient.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Marsmännchen
Beiträge: 294
Registriert: So 4. Mai 2014, 21:32
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10
CPU-Target: 64bit
Wohnort: Oranienburg

Re: Mulitcast Events

Beitrag von Marsmännchen »

Hi,

ich habs eben mal durchlaufen lassen: total cool :shock: Ich muss zugeben, ich habe eine ganze Weile gebraucht, bis ich die Funktionsweise verstanden habe. Das macht aber nix, daran wächst man :wink:
Socke hat geschrieben:Wenn du viele verschiedene Nachrichten senden möchtest, die jeweils nur von wenigen Objekten bearbeitet werden, ist dieser Ansatz nicht wirklich effizient.

Ich denke, dass könnte bei meinem aktuellen Projekt genau der Punkt sein: ich habe nur zwei Objekte, die sich gegenseitig über Statusänderungen unterrichten (also genau zwei verschiedene Events). Ein Drittes Objekt lauscht auch mit, ob ein bestimmter Zustand auftritt. Dafür ist die RTL-Variante eine Menge Overhead. Vielleicht probier ich ja mal - rein zu Lernzwecken - beide Varianten aus (ich hab ja keinen Zeitdruck bei meiner Coderei), also sowohl das Interface hier, als auch die selbstgemachte Zu-Fuß-Variante (ich frage mich, ob ein selbstgestricktes Observerpattern wirklich weniger Aufwand ist :? ).

Kann mir jemand of Topic noch eine Verständnisfrage beantworten? Mir war das Schlüsselwort absolute bisher unbekannt:

Code: Alles auswählen

EventRecord: PEventRecord absolute Data;
Ich hab schon nachgesehen, was es bedeutet, man lässt damit zwei Variablen auf den gleichen Speicherbereich zeigen. Mir ist nur nicht der Sinn in unserem Beispiel klar. Geht es darum, über diese Anweisung den Data-Pointer des Observer-Interfaces zu typisieren und dann eben nicht mit diesem allgemeinen Data-Poiner, sondern dem TEventPointer typsicher weiter zu arbeiten? Oder bin ich da auf dem falschen Pfad?
Ich mag Pascal...

Marsmännchen
Beiträge: 294
Registriert: So 4. Mai 2014, 21:32
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10
CPU-Target: 64bit
Wohnort: Oranienburg

Re: [Gelöst]Mulitcast Events

Beitrag von Marsmännchen »

Habe jetzt die Observer-Implementation der RTL in meinem Projekt umgesetzt. Wenn man sich dran gewöhnt hat, ist es gar nicht mal so schlimm 8) .

Ganz großen Dank nochmal an m.fuchs für den Hinweis und besonders auch an Socke für das Klasse-Beispiel! Das hat mir enorm den Weg geebnet.
Ich mag Pascal...

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Mulitcast Events

Beitrag von Socke »

Marsmännchen hat geschrieben:Kann mir jemand of Topic noch eine Verständnisfrage beantworten? Mir war das Schlüsselwort absolute bisher unbekannt:

Code: Alles auswählen

EventRecord: PEventRecord absolute Data;
Ich hab schon nachgesehen, was es bedeutet, man lässt damit zwei Variablen auf den gleichen Speicherbereich zeigen. Mir ist nur nicht der Sinn in unserem Beispiel klar. Geht es darum, über diese Anweisung den Data-Pointer des Observer-Interfaces zu typisieren und dann eben nicht mit diesem allgemeinen Data-Poiner, sondern dem TEventPointer typsicher weiter zu arbeiten? Oder bin ich da auf dem falschen Pfad?

Das ist richtig. Der Typ Pointer zeigt nur auf einen Speicherberich, trifft aber keine Aussage darüber, welche Daten dort gespeichert sind. Wenn man mit diesen Daten arbeiten möchte, muss man aber definieren, wie diese Daten strukturiert und wie groß der jewelige Speicherbereich ist. Dies nennt man Typisierung.
Durch das Schlüsselwort absolute wird festgelgt: an der Speicherposition des untypisierten Zeigers (Pointer) ist ein typisierter Zeiger vom Typ PEventRecord; dieser Zeiger zeigt also auf einen Record vom Typ TEventRecord.
Alternativ kann man die Typumwandlung (Typecast) auch direkt an jedem Aufruf vornehmen:

Code: Alles auswählen

procedure TTwoEventObserver.FPOObservedChanged(ASender: TObject;
  Operation: TFPObservedOperation; Data: Pointer);
begin
  if Operation = ooCustom then
  begin
    if PEventRecord(EventRecord)^.EventType = ceTwo then
      WriteLn('Two: ', PEventRecord(EventRecord)^.UserData.ClassName);
  end;
end;
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Antworten