deluvas
deluvas

Reputation: 379

Referencing a class inside another class by address

I have the following problem: I've got an entity class 'TEntity' and a mesh class 'TMesh' and TEntity needs to know when its element, (TMesh) is removed. Is there a possible working way I can call the TEntity method 'OnMeshRemove' from the TMesh destructor?

//uTEntity

interface

uses
  uTMesh;

type
  TEntity = class
    Mesh : TMesh;
    constructor Create(); overload;
    procedure OnMeshRemove();
  end;

implementation

constructor TEntity.Create();
begin
  Mesh := TMesh.Create();
  Mesh.EntityContainer := @self;
end;

procedure TEntity.OnMeshRemove();
begin
  //Do stuff
end;

end.

//uTMesh

interface

type
  TMesh = class
    EntityContainer : Pointer;
    destructor Remove();
  end;

implementation

uses
  uTEntity;

destructor TMesh.Remove();
var
  PEntity : ^TEntity;  
begin
  PEntity := EntityContainer;
  if Assigned( PEntity^ ) then
  begin
    PEntity^.OnMeshRemove();
  end;

  inherited Destroy();
end;

Example:

var
  Ent : TEntity;
begin

  Ent := TEntity.Create();
  Ent.Mesh.Remove();

  //I want Ent.OnMeshRemove to be called. In my example code, there is a pointer problem. I need to solve that. Thanks!

end;

PS: I don't want to have a TEntity procedure like TEntity.RemoveMesh();

Upvotes: 0

Views: 1074

Answers (3)

Jeroen Wiert Pluimers
Jeroen Wiert Pluimers

Reputation: 24483

Edit: back behind a PC.

The classic way of maintaining lists of predetermined objects of a certain class is using TCollection/TCollectionItem.

TCollection/TCollectionItem are heavily used in Delphi (see this list). They are lighter weight than TComponent (that automatically maintains Owner/Components/ComponentCount and has FreeNotification), as TCollectionItem and TCollection both descend from the TPersistent branch in stead of TComponent branch.
TCollection has a nice virtual Notify method:

procedure TCollection.Notify(Item: TCollectionItem; Action: TCollectionNotification);
begin
  case Action of
    cnAdded: Added(Item);
    cnDeleting: Deleting(Item);
  end;
end;

From Delphi 2009, you have generics, so then it can pay off big time to use TList (in your cast TList<TEntity>, as it contains this very nice Notify method and OnNotify event:

procedure TList<T>.Notify(const Item: T; Action: TCollectionNotification);
begin
  if Assigned(FOnNotify) then
    FOnNotify(Self, Item, Action);
end;

These two solutions work well if your TMesh is indeed a collection/list of TEntity.
If it TMesh is a non-list graph of TEntity, then it is better to descend both from TComponent like vcldeveloperlink text explained in his answer.

Andy Bulka has a nice blog post on the various ways of using lists and collections, including a well balanced view of TCollection/TCollectionItem usage.

--jeroen

Old answer (great iPad auto-complete bugs fixed):

Sorry for the short answer, as I am on the road only carrying a mobile device.

It looks like your mesh is a container for entities.
If so, then you should look into TCollection and TCollectionItem.
Derive your mesh from the former and your entity from the latter.

The delphi vcl source code contain many examples of these: fields/field or actions/action are good starters.

Upvotes: 2

APZ28
APZ28

Reputation: 1007

All objects in Delphi are pointer type so no need to deference it. Here is a bit more simpler

type
  TEntity = class
  public
    Mesh: TMesh;
    constructor Create;
    destructor Destroy; override;
  end;

implementation

constructor TEntity.Create;
begin
  inherited Create;
  Mesh := TMesh.Create;
  Mesh.EntityContainer := Self;
end;

procedure TEntity.Destroy;
begin
  if Mesh <> nil then
  begin
    Mesh.EntityContainer := nil;
    FreeAndNil(Mesh); 
  end;
  inherited Destroy;
end;

//***************************************************

type
  TMesh = class
    EntityContainer: TObject;
    destructor Destroy; override;
  end;

implementation

uses
  uTEntity;

destructor TMesh.Destroy;
begin
  if (EntityContainer <> nil) and (TEntity(EntityContainer).Mesh = Self) then
    TEntity(EntityContainer).Mesh := nil;
  EntityContainer := nil;
  inherited Destroy;
end;

Upvotes: 3

vcldeveloper
vcldeveloper

Reputation: 7489

Your TEntity instance should register itself with TMesh instance so that when TMesh instance is being freed, it will modify the TEntity instance about it.

If your classes are derived from TComponent class, then this mechanism is already implemented for you; each TComponent instance has a method called FreeNotification and a method called Notification. Any TComponent instance can register itself with the other component calling its FreeNotification method and passing itself as the parameter. Whenever a component is being destroyed, it will check the list of components registered for its free notification, and will invoke Notification method of each registered component. This way, the register component will be notified whenever the other component is about to be destroyed.

If one TComponent instance is the owner of the other (In your case, TEntity can be the owner of TMesh instance), then it will be notified automatically whenever TMesh instance is removed. All you need to do is to override its Notification method and do whatever you want to do inside that method.

If you do not want to derive your classes from TComponent class or for any reason do not want to use Delphi's implementation, you can implement the same behavior in your own classes. You need an internal list in your TMesh class which holds a list of classes which should be notified. You also need a method to register a class with your TMesh class, and eventually you need a method in your TEntity class which should be called by TMesh whenever it is being freed.

Here is a simple source code just for demonstrating the general idea. Take note that this sample code is not thread-safe, and might lack some other checks. I just wrote it fast to show you how to implement such an idea:

unit Unit1;

interface

uses
  Classes, Generics.Collections;

type
  TBaseClass = class
  private
    FNotificationList : TList<TBaseClass>;
  protected
    procedure Notify(AClass: TBaseClass); virtual;
  public
    procedure RegisterForNotification(AClass: TBaseClass);
    procedure UnregisterNotification(AClass: TBaseClass);
    constructor Create;
    destructor Destroy; override;
  end;

  TMesh = class(TBaseClass)

  end;

  TEntity = class(TBaseClass)
  private
    FMesh : TMesh;
    FOnMeshRemoved : TNotifyEvent;
    procedure SetMesh(Value: TMesh);
  protected
    procedure Notify(AClass: TBaseClass); override;
    procedure DoMeshRemoved; virtual;
  public
    constructor Create;
    destructor Destroy; override;
    property Mesh : TMesh read FMesh write SetMesh;
    property OnMeshRemoved : TNotifyEvent read FOnMeshRemoved write FOnMeshRemoved;
  end;


implementation

{ TBaseClass }

constructor TBaseClass.Create;
begin
  inherited;
  FNotificationList := TList<TBaseClass>.Create;
end;

destructor TBaseClass.Destroy;
var
  AClass: TBaseClass;
begin
  if Assigned(FNotificationList) then
  begin
    if (FNotificationList.Count > 0) then
      for AClass in FNotificationList do
        AClass.Notify(Self);

    FNotificationList.Free;
    FNotificationList := nil;
  end;

  inherited;
end;

procedure TBaseClass.Notify(AClass: TBaseClass);
begin
end;

procedure TBaseClass.RegisterForNotification(AClass: TBaseClass);
begin
  if not Assigned(AClass) then
    Exit;
  if FNotificationList.IndexOf(AClass) < 0 then
    FNotificationList.Add(AClass);
end;

procedure TBaseClass.UnregisterNotification(AClass: TBaseClass);
begin
  if not Assigned(AClass) then
    Exit;
  if FNotificationList.IndexOf(AClass) >= 0 then
    FNotificationList.Remove(AClass);
end;

{ TEntity }

constructor TEntity.Create;
begin
  inherited;

end;

destructor TEntity.Destroy;
begin
  if Assigned(FMesh) then
    FMesh.UnregisterNotification(Self);

  inherited;
end;

procedure TEntity.DoMeshRemoved;
begin
  if Assigned(FOnMeshRemoved) then
    FOnMeshRemoved(Self);
end;

procedure TEntity.Notify(AClass: TBaseClass);
begin
  inherited;
  FMesh := nil;
  DoMeshRemoved;
end;

procedure TEntity.SetMesh(Value: TMesh);
begin
  if Assigned(FMesh) then
  begin
    FMesh.UnregisterNotification(Self);
    FMesh := nil;
  end;

  if Assigned(Value) then
  begin
    FMesh := Value;
    FMesh.RegisterForNotification(Self);
  end;
end;

end.

In this code, both TEntity and TMesh are derived from TBaseClass which provides notification mechanism. TEntity does not create any instance of TMesh initially, but you can assign a created TMesh instance to its Mesh property. Doing so will make TEntity to assign that value to its FMesh field, and call its RegisterForNotification class so that it can be notified if the mesh is being destroyed.

When the mesh is being destroyed, it iterates over all the objects registered themselves with it, and invokes their Notify method. Here it would be Notify method of TEntity class. Inside Notify method of TEntity, it first makes its FMesh field nil, because that object is being destroyed and there is no need to keep a reference of a dead object. It then calls DoMeshRemove method which is an event-invoker for OnMeshRemove event.

Upvotes: 3

Related Questions