Copying objects

BlitzMax Forums/BlitzMax Beginners Area/Copying objects

Ant(Posted 2006) [#1]
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:



...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


Dreamora(Posted 2006) [#2]
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)


Ant(Posted 2006) [#3]
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.


Dreamora(Posted 2006) [#4]
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


Ant(Posted 2006) [#5]
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.


tonyg(Posted 2006) [#6]
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.


BlackSp1der(Posted 2006) [#7]
I don't have problems using byte ptr to copy.




BlackSp1der(Posted 2006) [#8]
Ohh. memcopy is a better choice


tonyg(Posted 2006) [#9]
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.


Jay Kyburz(Posted 2006) [#10]
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.


Dreamora(Posted 2006) [#11]
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 ...


Ant(Posted 2006) [#12]
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?


Dreamora(Posted 2006) [#13]
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.


tonyg(Posted 2006) [#14]
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...

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


Dreamora(Posted 2006) [#15]
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.


tonyg(Posted 2006) [#16]
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...

<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.


Dreamora(Posted 2006) [#17]
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.


BlackSp1der(Posted 2006) [#18]
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)



Dreamora(Posted 2006) [#19]
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.


tonyg(Posted 2006) [#20]
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).


Dreamora(Posted 2006) [#21]
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.


tonyg(Posted 2006) [#22]
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?


Dreamora(Posted 2006) [#23]
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