PDA

View Full Version : Advanced Delphi OOP



Relfos
16-07-2006, 07:13 PM
Hi there! I've created a octree class, which is going to be used in my 3d engine. Because I need to reuse it in various places, and the octree nodes can store different things, I've designed a generic class that implements every thing a octree need, addObject, deleteObject, etc.

Now, the problem is, when a octree needs to generate a new node, it calls a method CreateChild. The first version of this function was like this.


Function TOctreeNode.CreateChild(Box:TBoundingBox):TOctreeN ode;
Begin
Result:=TOctreeNode.Create(Box);
End;

However, since I want a generic octree, if I create another derived class, called TSceneOctreeNode, which I instantiated like this:
Octree:=TSceneOctreeNode.Create(WorldBBox);
You probably already have found the problem, when this octree needs to generate a new node, it creates a new TOctreeNode and not a TSceneTreeNode.
After investigating class fields I found something called ClassType and ClassName. ClassName holds the name of the class of the object, and I tough ClassType could be used to instantiate a new node.
So this is the new code:


Function TOctreeNode.CreateChild(Box:TBoundingBox):TOctreeN ode;
Begin
Result:=TOctreeNode(Self.ClassType).Create(Box);
End;

This should work in theory, but the program crashes after this, inside the constructor. Any expert can help me?

AthenaOfDelphi
16-07-2006, 08:28 PM
Hi Relfos,

You need to adopt (or at least I think you do) similar methods to TCollection.

When you create an instance of TCollection, you'd do something like this:-



myCollection:=TCollection.create(TMyCollectionItem Class);


TMyCollectionItemClass is normally descended from TCollectionItem. Once you've done this, you just use the generic 'add' method of the collection to create a new node, like this:-



var
myNewItem : TMyCollectionItemClass;
begin
myNewItem:=TMyCollectionitemClass(myCollection.add );
end;


You can implement this yourself using class references (I think thats the term). The key stages are as follows.

Define your base octree item class



TOctreeNode = class(TObject)
....
end;


Then define the type that provides (I think) the class reference you will use in the octree object.



TOctreeNodeClass = class of TOctreeNode;


Now define your octree object.



TOctree = class(TObject)
protected
fNodeClass : TOctreeNodeClass;
...
public
constructor create(nodeClass:TOctreeNodeClass);

function add:TOctreeNode;
end;


In the constructor, keep a record of the class reference you've passed to it.



constructor TOctree.create(nodeClass:TOctreeNodeClass);
begin
inherited;

fNodeClass:=nodeClass;
end;


The add method then looks something like this.



function TOctree.add:TOctreeNode;
begin
result:=fNodeClass.create;
end;


This example may not hold up with exact syntax etc. and obviously you will have to store the new nodes internally etc. but I hope it gives you the idea of how this kind of thing is handled in collections (and I hope its what you wanted).

Once you've got the base classes working, complete with the base properties you require, then whenever you need to store something different, you just create a new class descended from TOctreeNode. It does have the overhead of typecasting, but I think it will serve your purposes.

Relfos
16-07-2006, 08:56 PM
Thank you for that solution, its probably the best and cleaner method, the only difference is I need to pass the class to the constructor, no problem.
However, I discovered some strange way to make my method work.


Function LOctreeNode.CreateChild(Box:LBoundingBox):LOctreeN ode;
Begin
Result:=LOctreeNode(Self.ClassType.Create);
Result.Create(Box);
End;


Its seems strange, but if I use the default constructor inherited from TObject it doesn't crash. After creating it I call the normal constructor for initializing the object as I need. I really don't understand why its works, but I'm using it for now.
I don't know if using the ClassType property brings some extra overhead, since I need a good performance in this class, I'll test if and if necessary rewrite the code using your solution :wink:

TheDon
30-08-2006, 09:49 PM
Hi Relfos!

Let's take a look into the problem of creating your desired object (with a call to the right constructor). You want to create an object of a self defined class, and the class is specified during runtime (and not during compiletime).

If the compiler evaluates the method call during the compilation the same method always get called regardless of the object's type. I will try to explain this with a small code example:



type
TMyObject1 = class(TObject)
public
procedure Foo;
end;

TMyObject2 = class(TObject)
public
procedure Foo;
end;

procedure DoACall(Object : TMyObject1);
begin
// in this line below you call ALWAYS TMyObject1.Foo
// because the compiler think that the method need not
// be evaluated during runtime
Object.Foo;
end;

var
a : TMyObject1;
b : TMyObject2;
begin
a := TMyObject1.Create;
b := TMyObject2.Create;

DoACall(a);
DoACall(TMyObject1(b));
end;


The compiler nearly always evaluate method calls during compilation, except if you define a method as virtual or dynamic. If you define some methods with the keyword virtual the compiler insert a special code instead of the direct call to the method. This special code "tries to find a method with that name in the object and call this method" (it is a very simple explanation of what really happens) and it is executed every time your programm reaches this place.

The same thing is valid for constructors. With your appraches in your first post you always create a TOctreeNode (baseclass) item and never call the right constructor.

To solve your problem, just make your constructor in the TOctree base class virtual. If needed (for extra object field initialization, ...) you have to override the constructors in subclasses of the TOctree class. But you MUST NOT change the signation (parameters or result) of a virtual/override mehtod. If the signation is changed, the method is a different one and has nothing to do with the base method.



type
TMyOctreeNode = class
public
constructor Create(Box : TBoundingBox);virtual;
function CreateChild(Box : TBoundingBox);
end;

TMyDescentOctreeNode = class(TMyOctreeNode)
private
aField : Integer;
public
// constructor if needed
construcotr Create(Box : TBoundingBox);override;
end;

//....

constructor TMyOctreeNode.Create(Box : TBoundingBox);
begin
inherited Create();

//do something with box
end;

function TMyOctreeNode.CreateChild(Box : TBoundingBox) : TMyOctreeNode;
begin
// It is important to call the create from the .ClassType!
result := Self.ClassType.Create(Box);
end;

constructor TMyDescentOctreeNode.Create(Box : TBoundingBox);
begin
inherited Create(Box);

aField := 1;

//do something with other fields
end;


Another way is to leave the constructor the way it is and make the CreateChild virtual and define a new CreateChild for each descendant of the TOctree class.
But I would prefere the first version, because it is easy to forget to add the CreateChild method in a new descendant.

HTH

Relfos
31-08-2006, 04:26 PM
Thanks for the help, altough I've already hacked a solution.
Defining the CreateChild as virtual is also a good idea, but like you said, its easy to forget to declare it for each new class.

However, there is a problem in you code.
This line won't compile, because ClassType is a TClass, and not a TOctreeNode, so the constructor dont expects any parameters.

result := Self.ClassType.Create(Box);
I've tried before casting it to a OctreeNode

result := TOctreeNode(Self.ClassType).Create(Box);
The problem is, this compiles, but crashes when this line is called.
And I've declared the constructor Virtual, so in theory this should work. Maybe a Delphi bug, I'm using version 7.

This here my hack is used. Since Delphi allows constructors to be called as normal procedures after the object has been created, I call both constructors.

result:=TOctreeNode(Self.ClassType.Create);
result.Create(Box);

TheDon
31-08-2006, 05:14 PM
Hi!

Sorry for the invalid code, but it was written totaly out of the brain, without help of the compiler.

The followoing code



result := Self.ClassType.Create(Box);


cannot be compiled because ClassType is TClass and TClass is "class of TObject" and TObject.Create do not have any parameters.

You have to create a TOctreeClass as a "class of TOctree" with the following code:



type
TOctreeClass = class of TOCtree;


and then you need to call it that way



result := TOctreeClass(Self.ClassType).Create(Box);


HTH

Relfos
31-08-2006, 05:27 PM
Thanks, that really worked. Now I understand why that typecast crashed the program, I was confunding TClass with TObject :oops: