TComponentEx implements IObserver and IObservable patterns, since TDummySprite inherites from the component, it carries the benefit. It is useful when you want to nofity to another observer a message, I use it in the Attach/Deattach methods this way:
procedure TSimpleSprite.SetAttachedSprite(const aValue: TSimpleSprite);
begin
if aValue = self then
raise Exception.Create('Self cannot be attached');
FAttachedSprite := aValue;
if FAttachedSprite <> nil then
begin
if aValue.IsDead then
Exit; //Sprite it's gonna be destroid in the next frame, therefore self cannot be longer notified by any other sprite
FAttachX := X - FAttachedSprite.X;
FAttachY := Y - FAttachedSprite.Y;
FAttachedSprite.AddObserver(self); //In order to avoid following a dead sprite, a notification is added to the attached sprite
end else
begin
FAttachX := 0;
FAttachY := 0;
end;
end;
procedure TSimpleSprite.DeattachSprite;
begin
if FAttachedSprite <> nil then
begin
FAttachedSprite.DeleteObserver(self);
FAttachedSprite := nil;
FAttachX := 0;
FAttachY := 0;
end;
end;
procedure TSimpleSprite.DeleteObserver(aObserver: IObserver);
begin
inherited;
if aObserver = FAttachedSprite as IObserver then
FAttachedSprite := nil;
end;
This way we always make sure that we don't follow an already freed sprite. The
interfaces look like this:
IObserver = interface
['{3E91264F-BBC0-44DF-8272-BD8EA9B5846C}']
procedure Update(aMessage: TMessage);
end;
IObservable = interface
['{A7C4D942-011B-4141-97A7-5D36C443355F}']
procedure AddObserver(aObserver: IObserver);
procedure DeleteObserver(aObserver: IObserver);
procedure ClearObservers;
procedure Notify(aMessage: TMessage);
end;
When a TComponentEx is destroid this code is launch:
destructor TComponentEx.Destroy;
var
m: TMessage;
begin
m := TMessage.MessageDestroy(self);
try
FObservable.Notify(m);
finally
m.Free;
end;
FObservable.Free;
inherited;
end;
The Notify() method:
procedure TObservable.Notify(aMessage: TMessage);
var
i: integer;
begin
//Notify a message to interested observers
for i := FNotify.Count - 1 downto 0 do
IObserver(FNotify[i]).Update(aMessage);
end;
the Update() method:
procedure TObservable.Update(aMessage: TMessage);
var
I: IObserver;
begin
if aMessage = nil then
Exit;
//An observer is informing me that is going to be freed
if aMessage.Id = MSG_DESTROY then
begin
if aMessage.Sender.GetInterface(IObserver, I) then
DeleteObserver(I);
end;
end;
(Update is a virtual method so its funcionality can be expanded easily)
Finally a TMessage:
const
MSG_DESTROY = 1;
MSG_CHANGED = 2;
MSG_RESTORE = 3;
type
TMessage = class
private
FId: integer;
FText: string;
FSender: TObject;
public
constructor Create(aSender: TObject; aId: integer; const aText: string); overload;
constructor Create(aSender: TObject; aId: integer); overload;
constructor Create(aSender: TObject; const aText: string); overload;
class function MessageDestroy(aSender: TObject): TMessage;
property Sender: TObject read FSender;
property Id: integer read FId;
property Text: string read FText;
end;
The Salamander project relies on this mechanism to find out that THero class has been freed (the "game" continue running but all entities ignore the hero ship). Somewhere in the code you can find:
procedure TDragon.SetGame(const aValue: TGame);
begin
inherited SetGame(avalue);
if GetGame.Hero <> nil then
GetGame.Hero.AddObserver(self);
end;
and
procedure TMyGame.DeleteObserver(aObserver: IObserver);
begin
inherited;
//Hero
if aObserver = FHero as IObserver then
begin
FHero := nil;
Exit;
end;
end;
Ciao
No comments:
Post a Comment