Thursday, October 7, 2010

Observing sprites

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