Copying objects

BlitzMax Forums/BlitzMax Beginners Area/Copying objects

Ugghhh......after a mammoth bug hunting session I've found my problem, but I need a more elegant solution.

I have a type called TBlock, now within TBlock is another type called TRenderObject and within TRenderObject are around 30 fields to do with animation and drawing, kind of like this:

Type TBlock
Field renderObject :TRenderObject
etc
endtype

Type TRenderObject
Field identifier :String
Field x :Float
Field y :Float
Field image :TImage
Field priority :Byte
Field objectType :Short
Field drawFlag :Byte
etc.....
endtype

Now I have a method which copies a TBlock Object which is the following:

Method Copy:TBlock()

Local block	:	TBlock	
block= New TBlock	
block.renderObject	= renderObject 
block.number 		= number
block.xCell			= xCell
block.yCell			= yCell
block.state			= state
block.colour			= colour

Return block
EndMethod


...and is called like this:

BlockCopy:TBlock = block.Copy()

...and works great, except that it doesnt copy the contents of the renderObject field.

Now I can sort this by using a similar technique of assigning fields as I have done in the above example, but as TRenderObject has so many fields I wondered if there was a more elegent solution rather than going through each field (as I have done in TBlock.copy() and assigning them manually...?

Sorry if this explanation seems a little disjointed, blurry eyes and blurry mind.
Thanks for any help

The best way is to do it in OO style:

Add the method copy to all of your objects and call it for all child object on copying :-) (although I would have called it clone or deepclone as you clone the linked part as well. copy would normally not copy the child data but reference it only as you do in the above code)

I guess this is the way I thought, I'm just concerned that if I do this and later update the type, I'll forget to add any additional fields I add to the copy! Would there be any way to check this? I dont know if this would work, but maybe I could do a sizeof TRenderObject and assert if that is different size to the copy I make - then I would know that I am missing fields in my copy method....I'll give this a try.

The only way is always updating the copy method after modifiying fields. Sizeof would be a possibility, but only as long as you don't have lists or the like in

Hmmm...it doesnt have lists, but there is a problem. Iam adding an additional float field to the type, but not the copy method. (so there is a field which is not getting copied across). But when I look at the size of the TRenderObject (with the extra field) and an instance of it created using the copy method, they are the same....adding the extra field doesnt seem to have done any good - its reporting back as being of equal size.

I'm guessing an object isn't created in contiguous memory so you can't do a memcopy?
Type ttest
  Field x
End Type
my:ttest = New ttest
my.x = 5
mynew:ttest=New ttest
MemCopy(Byte Ptr(mynew),Byte Ptr(my),SizeOf my)
Print mynew.x


<edit> Seems OK...
Type ttest
  Field x
  Field y
  Field name:String
End Type
my:ttest = New ttest
my.x = 5
my.y = 51
my.name="Hello World!"
mynew:ttest=New ttest
MemCopy(Byte Ptr(mynew),Byte Ptr(my),SizeOf my)
Print mynew.x + " " + mynew.y + " " + mynew.name

but can we be sure?
Could it mess up inherited types?
Type base
  Field g
  Field h
End Type
Type ttest Extends base
  Field x
  Field y
  Field name:String
End Type
my:ttest = New ttest
my.x = 5
my.y = 51
my.g = 12
my.name="Hello World!"
mynew:ttest=New ttest
MemCopy(Byte Ptr(mynew),Byte Ptr(my),SizeOf my)
mynew.h = 999
Print mynew.x + " " + mynew.y + " " + mynew.name + " " + mynew.g + " " + mynew.h

seems to work as well.

I don't have problems using byte ptr to copy.

Type TRenderObject
	Field identifier :String
	Field x :Float
	Field y :Float
	Field image :TImage
	Field priority :Byte
	Field objectType :Short 
	Field drawFlag :Byte 
EndType

Type TBlock
	Field renderObject :TRenderObject
	Field number:Int
	Field xCell:Int
	Field yCell:Int
	Field state:Int
	Field colour:Int

	Method Copy:TBlock()
		Local block	:	TBlock
		block= New TBlock	
		block.renderObject = New TRenderObject
		block.number 		= number
		block.xCell			= xCell
		block.yCell			= yCell
		block.state			= state
		block.colour			= colour
		
		Local source:Byte Ptr = renderObject
		Local target:Byte Ptr = block.renderObject
		For Local pos:Int=0 Until SizeOf renderObject
		target[pos]=source[pos]
		Next

		Return block
EndMethod

EndType

Local render:TRenderObject=New TRenderObject
render.identifier="render"
render.x=1111
render.y=1010
render.drawFlag=1


Local block1:TBlock=New TBlock
block1.number=5
block1.renderObject=render

Local block2:TBlock=block1.copy()
block2.renderObject.identifier="new render"
block2.renderObject.drawFlag=3

Print block1.number
Print block1.renderObject.identifier
Print block1.renderObject.x
Print block1.renderObject.y
Print block1.renderObject.drawFlag
Print "------------"
Print block2.number
Print block2.renderObject.identifier
Print block2.renderObject.x
Print block2.renderObject.y
Print block2.renderObject.drawFlag


Ohh. memcopy is a better choice

Quick check and other posts suggest the problem is with the garbage collector. Not sure why as we've done a 'new' so should be known by GC.
Test suggests listremove and null on mynew will release memory.

I usualy hate to get involved in language discussions, but some some kind of deepcopy function would come in real handy I think.

NewObject:tBlar = Copy OldObject.

Oh, and some buit in serialisaton for easy loading and saving.

Both of these things required to to step through each of your objects and account for every field. This becomes dificult to maintain and prone to bugs as you game grows.

The problem with sizeof btw is that your object actually has the same size. just because you didn't copy over a float value, it does not mean it is not there ... its only 0.

and memcopy is definitely no possibility or you will see more MAVs than you can defeat ... memcopy and the like is not GC handled nor are their result ...

No I did actually I added a float and assigned a value to it...yet its still coming out as the same size. I''ll look at it again...

[Edit]

Ah...ok sorry, see your point....so size of comparison is useless in this situation then?

Yes but the problem is that an instance of a class (object) will ALWAYS have the same size as the class. so sizeof is of no use. The moment you do "new childclass" it already has the size, no mather if you copy over the float value. Its space is reserved due to the type declaration.

Thats what I meant.

Why would the sizeof be a problem?
The sizeof the class is calculated from the sum of it's fields.
e.g.
Type ttest1
  Field x
End Type
Type ttest2
  Field x
  Field y:Float
  Field z:String
End Type
Print SizeOf ttest1 + " " + SizeOf ttest2
my:ttest1 = new ttest1
my.x=5
print sizeof my
This is reserving all the space we might need and the sizeof the object will never increase.
I have run a test and the 'mynew' is picked up by the GC...
Type base
  Field g
  Field h
End Type
Type ttest Extends base
  Global mylist:Tlist=CreateList()
  Field x
  Field y
  Field name:String
End Type
my:ttest = New ttest
my.x = 5
my.y = 51
my.g = 12
ListAddLast(ttest.mylist,my)
my.name="Original Object"
mynew:ttest=New ttest
MemCopy(Byte Ptr(mynew),Byte Ptr(my),SizeOf my)
mynew.h = 999
ListAddLast(ttest.mylist,mynew)
Print mynew.x + " " + mynew.y + " " + mynew.name + " " + mynew.g + " " + mynew.h
Print "sizeof mynew : " + SizeOf mynew
GCCollect()
Print "Before listremove " + GCMemAlloced()
ListRemove(ttest.mylist,mynew)
GCCollect()
Print "After list remove " + GCMemAlloced()
mynew=Null
GCCollect()
Print "After mynew=null " + GCMemAlloced()

I also have the feeling that something will go wrong at
some point but I can't see what.

Yeah the size of an object will never increase. But SizeOf is no solution to track if the copy method does not copy a field as the space for the field is reserved no mather if it is filled or not.
You somehow got quite off the topic somehow ;-)

On the what: The problem is, that if your object you copy has references to objects and not only numerics, it will fail. Because memcopy a reference does not raise the reference count. So if all "regular created references" (using new) are away, the objects your copy references to will simply disappear. And it will be nearly impossible to specify this bug if you are not aware of that risk.

MemCopy thought might work for Shallowcopy on numeric only types. But I would not use it just for inconsistency reasons and the risk that the GC might change on handling byte ptr etc.

if the copy method does not copy a field as the space for the field is reserved no mather if it is filled or not.


Sorry Dreamora. I am sure it's right I just don't understand what you're saying.
If the object isn't held in a single contiguous area of memory there'll be a problem which was one of my earlier questions.
I think I see what you mean about referencing other objects. If, for example, my:TTest has a field otherobject:TTest2 then it's possible that object might be deleted as it does not know my:ttest2 is referencing it.
OK. I think that's worth a quick test.
<edit> The result...
Type ttest
  Global mylist:Tlist=CreateList()
  Field x
  Field y
  Field name:String
  Field test2:TTest2
  Function create:ttest()
		my:ttest = New ttest
		my.x = 5
		my.y = 51
		my.test2 = New ttest2
		my.test2.defined = "TTEST2"
		my.name="Original Object"
		ListAddLast(mylist,my)
		Return my
   End Function
   Method remove()
		ListRemove mylist,Self
		test2=Null
   End Method
End Type
Type ttest2 
  Field defined:String
End Type
my:ttest=ttest.create()
mynew:ttest=New ttest
MemCopy(Byte Ptr(mynew),Byte Ptr(my),SizeOf my)
GCCollect()
Print "Memcheck1 : " + GCMemAlloced()
my.remove()
my=Null
'GCCollect()
Print "Memcheck2 : " + GCMemAlloced()
ListAddLast(ttest.mylist,mynew)
Print mynew.x + " " + mynew.y + " " + mynew.name + " " +  mynew.test2.defined
'Print my.x + " " + my.y + " " + my.name + " " +  my.test2.defined
'GCCollect()
Print "Memcheck3 : " + GCMemAlloced()
Print mynew.x + " " + mynew.y + " " + mynew.name + " " + mynew.test2.defined

<edit> Added extra gccollect and it does fail for exactly the reason Dreamora says. No reference for ttest2 so it was deleted although mynew needed it.

The objects are in a continous area of memory. Sorry missed that question.
A BM object is a C++ object with 2 hidden functions (constructor and destructor)

so for copy of only numeric objects like TVector or TMatrix, the memcopy method should work.

in that case we need to do 2 copies
MemCopy(Byte Ptr(mynew),Byte Ptr(my),SizeOf my)
MemCopy(Byte Ptr(mynew.test2),Byte Ptr(my.test2),SizeOf my.test2)


This will totally break with the GarbageCollector. BM is not meant for manual memory operations. They are only provided so you can use C / C++ imports which need continous memory blocks for data you send. For example for arrays and the like.

The structure after the memcopy was fine (even the object reference) but, if the object pointed to by my.test2 was deleted (and was the last *known* reference) mynew.test2 would point to a non-existent object.
Does anybody know how the list of 'referenced objects' is maintained? Is it possible to manually add a reference from mynew.test2 to the object and increment it's counter? (I'm guessing it is a simple counter).

Check struct BBObject in the brl.blitz blitz_object.h .
It has an int that counts the refs.

So at best you would do a clone functionality on that level by adding it to that file or a similar way.

You would be able to do from BM as the struct is a "super object" to the actual BBObject class implementation which results in :Object within BM.

I guess there's no way of checking which fields are object references and creating a dummy version to manually increment the counter. On deleting the clone also delete the dummy object?

Not really.
Especially as out of OO sight, the implementation of the Copy / Clone method and recursively calling it is the correct way to go. Especially in a managed environment that needs to be used properly or can cause real problems.

The "what if I miss to add a new field to copy/clone method" might happen but it is debugged extremely fast if it happens. so no real problem