Copy Object to a New Object

BlitzMax Forums/BlitzMax Beginners Area/Copy Object to a New Object

Hi All,

Is there a command to copy one object to another object?

If I do this:

Type TFoo
  Field test$
End Type

Local foo:TFoo = New TFoo
foo.test = "FOO"

Local bar:TFoo = foo

bar.test = "BAR"

Print foo.test
Print bar.test


Both prints out "BAR"... as it is coping by reference. I want both objects to be a different instance of each other, whilst keeping all the data.

Please help.

Cheers!

You have to do the copy yourself.

Make a new object and copy over all of the fields.

There may be a way using reflection to get an object to copy itself, but I have not played with it yet.

Thanks TaskMaster...

Looking at my code again, and I'm now not sure what the problem is, I thought it was this.

Ive got a set of buildings each with a set of slots, my problem is that if I click on one building it sets the slot of each building with that type.

Eg 3 buildings, 2 houses and 1 bar. If I click the first house, the slots in both houses are used...!?!? I just want the building I clicked on!

Here is a simplified version of my code:

SuperStrict

Graphics 800,600,0

Type TSlot
	Field x%,y%
	Field empty%
	Field slotList:TSlotList
End Type

Type TSlotList Extends TList
	Method AddSlot:TSlot(x:Int, y:Int)
		Local s:TSlot = New TSlot
		s.x = x
		s.y = y
		s.empty = True
		s.slotList = Self
		AddLast(s)
		Return s
	End Method
	
	Method findFreeSlot:TSlot()
		For Local s:TSlot = EachIn Self
			If s.empty Then Return s
		Next
		' reset slots if all are full
		For Local s:TSlot = EachIn Self
			 s.empty = True
		Next
		Return Null
	End Method
End Type

Type TBuildingInfo
	Field name$
	Field slots:TSlotList
	Field buildingInfoList:TBuildingInfoList 
End Type

Type TBuildingInfoList Extends TList
	Method AddNew:TBuildingInfo(name:String)
		Local b:TBuildingInfo = New TBuildingInfo
		b.name = name		
		b.slots = New TSlotList
		
		b.buildingInfoList = Self
		
		AddLast(b)
		Return b
	End Method
	
	Method Find:TBuildingInfo(name:String)
		For Local i:TBuildingInfo = EachIn Self
			If Upper(i.name) = Upper(name) Then Return i
		Next		
		Return Null
	End Method
End Type
	
Type TBuilding
	Field x%,y%
	Field h%,w%
	Field info:TBuildingInfo
	Field name$
	Field buildings:TBuildingList 'points to list it's part of
	
	Function Create:TBuilding(tx%, ty%,name$)
		Local b:TBuilding = New TBuilding
		b.x = tx
		b.y = ty
		b.h = 100
		b.w = 100
		b.name = name
		b.info = New TBuildingInfo
		b.info.slots = New TSlotList 
		Return b
	End Function
End Type

Type TBuildingList Extends TList
	Method AddNew:TBuilding(x%,y%,name$)
		Local b:TBuilding = TBuilding.Create(x, y,name)
		b.buildings = Self 'point to this list		
		AddLast(b)
		Return b
	End Method
	
	Method Draw()
		For Local b:TBuilding = EachIn Self
			SetColor 255,255,255
			DrawRect b.x,b.y,b.h,b.w
			SetColor 255,0,0
			DrawText b.name, b.x,b.y
			For Local s:TSlot = EachIn b.info.slots
				If s.empty SetColor 0,255,0 Else SetColor 255,0,0
				DrawOval b.x+s.x , b.y+s.y , 10 , 10
			Next
		Next
		SetColor 255,255,255		
	End Method
	
	Method find:TBuilding(name$)
		For Local b:TBuilding = EachIn Self
			If Upper(b.name) = Upper(name) Return b
		Next
		Return Null
	End Method
End Type

' set up objects

Local infoList:TBuildingInfoList = New TBuildingInfoList 
Local info:TBuildingInfo = infoList.AddNew("HOUSE")
info.slots.AddSlot(-20,10)
info.slots.AddSlot(50,120)
info.slots.AddSlot(120,10)

info:TBuildingInfo = infoList.AddNew("BAR")
info.slots.AddSlot(-20,10)
info.slots.AddSlot(50,120)
info.slots.AddSlot(120,10)


Local buildings:TBuildingList = New TBuildingList
Local b:TBuilding
b = buildings.AddNew(100,100, "HOUSE")
Local houseInfo:TbuildingInfo = infoList.Find("HOUSE")
b.info = houseInfo

b = buildings.AddNew(300,300, "HOUSE")
b.info = houseInfo

b = buildings.AddNew(500,100, "BAR")
Local barInfo:TbuildingInfo = infoList.Find("BAR")
b.info = barInfo

Repeat
	If MouseHit(1)
		For Local b:TBuilding = EachIn buildings
			If RectsOverlap(MouseX(),MouseY(),1,1,b.x,b.y,b.w,b.h)
				Local freeSlot:TSlot = b.info.slots.findFreeSlot()
				If freeSlot <> Null
					freeSlot.empty = False
				End If
			EndIf
		Next		
	End If

	Cls
		buildings.Draw()
	Flip
Until KeyHit(KEY_ESCAPE)

Function RectsOverlap:Int(x0:Float, y0:Float, w0:Float, h0:Float, x2:Float, y2:Float, w2:Float, h2:Float)
	If x0 > (x2 + w2) Or (x0 + w0) < x2 Then Return False
	If y0 > (y2 + h2) Or (y0 + h0) < y2 Then Return False
	Return True
End Function


I thought of adding the following to TBuildingInfo:

	Method clone:TBuildingInfo()
		Local b:TBuildingInfo = New TBuildingInfo
		b.name = name
		b.slots = slots
		b.buildinginfoList = buildingInfoList
		Return b
	End Method


and assigning like this:

Local houseInfo:TbuildingInfo = infoList.Find("HOUSE")
b.info = houseInfo.clone()


But I still have the problem... any ideas?

Thanks!

Okay I think I found my problem... and it was that one object (TSlot) was still getting copied by reference...

I did this in TBuildingInfo:

	Method clone:TBuildingInfo()
		Local b:TBuildingInfo = New TBuildingInfo
		b.name = name
		b.slots = New TSlotList
		For Local s:TSlot = EachIn slots
			Local sl:TSlot = b.slots.clone(s, slots)
			b.slots.AddSlot(sl.x,sl.y)
		Next		
		b.buildinginfoList = buildingInfoList
		Return b
	End Method


And added clone to TSlotList:

	Method clone:TSlot(s:TSlot, sl:TSlotList )
		Local ss:TSlot = s.clone(sl)
		Return ss
	End Method


Finally added clone to TSlot:

	Method clone:TSlot(sl:TSlotList )
		Local s:TSlot = New TSlot
		s.x = x
		s.y = y
		s.empty = empty
		s.slotList = sl
		Return s
	End Method


The working code:

SuperStrict

Graphics 800,600,0

Type TSlot
	Field x%,y%
	Field empty%
	Field slotList:TSlotList
	
	Method clone:TSlot(sl:TSlotList )
		DebugLog "CLONE SLOT"
		Local s:TSlot = New TSlot
		s.x = x
		s.y = y
		s.empty = empty
		s.slotList = sl
		DebugLog s.x
		Return s
	End Method
End Type

Type TSlotList Extends TList
	Method AddSlot:TSlot(x:Int, y:Int)
		Local s:TSlot = New TSlot
		s.x = x
		s.y = y
		s.empty = True
		s.slotList = Self
		AddLast(s)
		Return s
	End Method
	
	Method findFreeSlot:TSlot()
		For Local s:TSlot = EachIn Self
			If s.empty Then Return s
		Next
		' reset slots if all are full
		For Local s:TSlot = EachIn Self
			 s.empty = True
		Next
		Return Null
	End Method
	
	Method clone:TSlot(s:TSlot, sl:TSlotList )
		Local ss:TSlot = s.clone(sl)
		Return ss
	End Method
End Type

Type TBuildingInfo
	Field name$
	Field slots:TSlotList
	Field buildingInfoList:TBuildingInfoList 
	
	Method clone:TBuildingInfo()
		Local b:TBuildingInfo = New TBuildingInfo
		b.name = name
		b.slots = New TSlotList
		For Local s:TSlot = EachIn slots
			Local sl:TSlot = b.slots.clone(s, slots)
			b.slots.AddSlot(sl.x,sl.y)
		Next		
		b.buildinginfoList = buildingInfoList
		Return b
	End Method

End Type

Type TBuildingInfoList Extends TList
	Method AddNew:TBuildingInfo(name:String)
		Local b:TBuildingInfo = New TBuildingInfo
		b.name = name		
		b.slots = New TSlotList
		
		b.buildingInfoList = Self
		
		AddLast(b)
		Return b
	End Method
	
	Method Find:TBuildingInfo(name:String)
		For Local i:TBuildingInfo = EachIn Self
			If Upper(i.name) = Upper(name) Then Return i
		Next		
		Return Null
	End Method
End Type
	
Type TBuilding
	Field x%,y%
	Field h%,w%
	Field info:TBuildingInfo
	Field name$
	Field buildings:TBuildingList 'points to list it's part of
	
	Function Create:TBuilding(tx%, ty%,name$)
		Local b:TBuilding = New TBuilding
		b.x = tx
		b.y = ty
		b.h = 100
		b.w = 100
		b.name = name
		b.info = New TBuildingInfo
		b.info.slots = New TSlotList 
		Return b
	End Function
End Type

Type TBuildingList Extends TList
	Method AddNew:TBuilding(x%,y%,name$)
		Local b:TBuilding = TBuilding.Create(x, y,name)
		b.buildings = Self 'point to this list		
		AddLast(b)
		Return b
	End Method
	
	Method Draw()
		For Local b:TBuilding = EachIn Self
			SetColor 255,255,255
			DrawRect b.x,b.y,b.h,b.w
			SetColor 255,0,0
			DrawText b.name, b.x,b.y
			DrawText b.info.name, b.x,b.y+10
			For Local s:TSlot = EachIn b.info.slots
				If s.empty SetColor 0,255,0 Else SetColor 255,0,0
				DrawOval b.x+s.x , b.y+s.y , 10 , 10
			Next
		Next
		SetColor 255,255,255		
	End Method
	
	Method find:TBuilding(name$)
		For Local b:TBuilding = EachIn Self
			If Upper(b.name) = Upper(name) Return b
		Next
		Return Null
	End Method
End Type

' set up objects

Local infoList:TBuildingInfoList = New TBuildingInfoList 
Local info:TBuildingInfo = infoList.AddNew("HOUSE")
info.slots.AddSlot(-20,10)
info.slots.AddSlot(50,120)
info.slots.AddSlot(120,10)

info:TBuildingInfo = infoList.AddNew("BAR")
info.slots.AddSlot(-20,10)
info.slots.AddSlot(50,120)
info.slots.AddSlot(120,10)


Local buildings:TBuildingList = New TBuildingList
Local b:TBuilding
b = buildings.AddNew(100,100, "HOUSE")
Local houseInfo:TbuildingInfo = infoList.Find("HOUSE")
b.info = houseinfo.clone()

b = buildings.AddNew(300,300, "HOUSE")
b.info = houseinfo.clone()
b.info.name = "H"

b = buildings.AddNew(500,100, "BAR")
Local barInfo:TbuildingInfo = infoList.Find("BAR")
b.info = barInfo.clone()

Repeat
	If MouseHit(1)
		For Local b:TBuilding = EachIn buildings
			If RectsOverlap(MouseX(),MouseY(),1,1,b.x,b.y,b.w,b.h)
				Local freeSlot:TSlot = b.info.slots.findFreeSlot()
				If freeSlot <> Null
					freeSlot.empty = False
				End If
			EndIf
		Next		
	End If

	Cls
		buildings.Draw()
	Flip
Until KeyHit(KEY_ESCAPE)

Function RectsOverlap:Int(x0:Float, y0:Float, w0:Float, h0:Float, x2:Float, y2:Float, w2:Float, h2:Float)
	If x0 > (x2 + w2) Or (x0 + w0) < x2 Then Return False
	If y0 > (y2 + h2) Or (y0 + h0) < y2 Then Return False
	Return True
End Function


This is all when and good for objects with a small number of fields, but my proper TBuildingInfo has 40 fields ATM and will be more soon!

Is there a smaller/simpler way to do this?

I'm back again!

Found this post http://www.blitzbasic.com/Community/posts.php?topic=73573#822043 by Azathoth and it looks like it might work...

I guess this is how you use the metadata tags(?):

Type TBuildingInfo
	Field name$ {Clone}
	Field slots:TSlotList{Clone}
	Field buildingInfoList:TBuildingInfoList {Clone}
End Type


And assign them by:
b = buildings.AddNew(100,100, "HOUSE")
Local houseInfo:TbuildingInfo = infoList.Find("HOUSE")
b.info = TBuildingInfo(CloneObject(houseInfo))


This looks like it works for the name$ field, but still slots:TSlotList is still by reference... any ideas?

Cheers

I think there might be a way to memcopy the objects directly, but I'm not sure how to do this.

I think there might be a way to memcopy the objects directly

Not advisable, me thinks.

You have to create a new slot list, and re-add each object in the list (if you want the same objects in the list). If you want the objects in the list to also be copies, you will have to copy them as well.

Yes. There are 2 ways you can think of copying :

A basic copy, which in effect creates a new objects and populates it with the objects the original contains. Those objects will be the same instances.

A deep copy, which will create new copies of ALL objects. If you have a TList field, it would create a new TList, and new instances of all objects inside that list.

The second form is obviously a lot more intensive, since it potentially has a lot of work to do.
It all depends how much you need copied? Just the object you are dealing with, or all the objects it uses?

My persistence module can do "proper" deep copying, albeit in a roundabout way - it converts the object-structure into XML, which you can then de-serialise to get a whole new set of objects. (i.e. it's designed for snap-shots of your objects for persisting on Disk, rather than on-the-fly copying).

Thanks for the replies guys.

@Czar Flavius - Ive read that doing the memcopy can really mess up the GC, especially if you have got objects within objects.

@TaskMaster, looks like your right... Azathoth CloneObject function doesnt like to copy the TSlotList field (I kept getting null pointers!? when trying to {Clone} it)...so here is what Ive come up with:

TBuildingInfo (notice the NoClone on the lists)

Type TBuildingInfo
	Field name$ {Clone}
	Field slots:TSlotList{NoClone}
	Field buildingInfoList:TBuildingInfoList {NoClone}
	
	Method clone:TBuildingInfo()
		Local b:TBuildingInfo =  TBuildingInfo (CloneObject(Self))
		b.slots = New TSlotList
		For Local s:TSlot = EachIn Self.slots
			Local ss:TSlot  = s.clone(slots)
			b.slots.AddSlot(ss.x,ss.y)
		Next
		b.buildingInfoList = Self.buildingInfoList
		Return b
	End Method
End Type


And to create the clone:

Local buildings:TBuildingList = New TBuildingList ' create the global building list
Local b:TBuilding
b = buildings.AddNew(100,100, "HOUSE") ' create a new building and add it to the list
Local houseInfo:TbuildingInfo = infoList.Find("HOUSE") 'get the data for the building
b.info = houseinfo.clone() ' create a new copy of the data and assign it to the new building
b.info.name = "1st House" ' check to see if the clone has worked


Here is the full code:

SuperStrict

Graphics 800,600,0

Type TSlot
	Field x%,y%
	Field empty%
	Field slotList:TSlotList
	
	Method clone:TSlot(sl:TSlotList )		
		Local s:TSlot = New TSlot
		s.x = x
		s.y = y
		s.empty = empty
		s.slotList = sl
		DebugLog s.x
		Return s
	End Method
End Type

Type TSlotList Extends TList
	Method AddSlot:TSlot(x:Int, y:Int)
		Local s:TSlot = New TSlot
		s.x = x
		s.y = y
		s.empty = True
		s.slotList = Self
		AddLast(s)
		Return s
	End Method
	
	Method findFreeSlot:TSlot()
		For Local s:TSlot = EachIn Self
			If s.empty Then Return s
		Next
		' reset slots if all are full
		For Local s:TSlot = EachIn Self
			 s.empty = True
		Next
		Return Null
	End Method
End Type

Type TBuildingInfo
	Field name$ {Clone}
	Field slots:TSlotList{NoClone}
	Field buildingInfoList:TBuildingInfoList {NoClone}
	
	Method clone:TBuildingInfo()
		Local b:TBuildingInfo =  TBuildingInfo (CloneObject(Self))
		b.slots = New TSlotList
		For Local s:TSlot = EachIn Self.slots
			Local ss:TSlot  = s.clone(slots)
			b.slots.AddSlot(ss.x,ss.y)
		Next
		b.buildingInfoList = Self.buildingInfoList
		Return b
	End Method
End Type

Type TBuildingInfoList Extends TList
	Method AddNew:TBuildingInfo(name:String)
		Local b:TBuildingInfo = New TBuildingInfo
		b.name = name		
		b.slots = New TSlotList
		
		b.buildingInfoList = Self
		
		AddLast(b)
		Return b
	End Method
	
	Method Find:TBuildingInfo(name:String)
		For Local i:TBuildingInfo = EachIn Self
			If Upper(i.name) = Upper(name) Then Return i
		Next		
		Return Null
	End Method
End Type
	
Type TBuilding
	Field x%,y%
	Field h%,w%
	Field info:TBuildingInfo
	Field name$
	Field buildings:TBuildingList 'points to list it's part of
	
	Function Create:TBuilding(tx%, ty%,name$)
		Local b:TBuilding = New TBuilding
		b.x = tx
		b.y = ty
		b.h = 100
		b.w = 100
		b.name = name
		b.info = New TBuildingInfo
		b.info.slots = New TSlotList 
		Return b
	End Function
End Type

Type TBuildingList Extends TList
	Method AddNew:TBuilding(x%,y%,name$)
		Local b:TBuilding = TBuilding.Create(x, y,name)
		b.buildings = Self 'point to this list		
		AddLast(b)
		Return b
	End Method
	
	Method Draw()
		For Local b:TBuilding = EachIn Self
			SetColor 255,255,255
			DrawRect b.x,b.y,b.h,b.w
			SetColor 255,0,0
			DrawText b.name, b.x,b.y
			DrawText b.info.name, b.x,b.y+10
			For Local s:TSlot = EachIn b.info.slots
				If s.empty SetColor 0,255,0 Else SetColor 255,0,0
				DrawOval b.x+s.x , b.y+s.y , 10 , 10
			Next
		Next
		SetColor 255,255,255		
	End Method
	
	Method find:TBuilding(name$)
		For Local b:TBuilding = EachIn Self
			If Upper(b.name) = Upper(name) Return b
		Next
		Return Null
	End Method
End Type

' set up objects

Local infoList:TBuildingInfoList = New TBuildingInfoList 
Local info:TBuildingInfo = infoList.AddNew("HOUSE")
info.slots.AddSlot(-20,10)
info.slots.AddSlot(50,120)
info.slots.AddSlot(120,10)

info:TBuildingInfo = infoList.AddNew("BAR")
info.slots.AddSlot(-20,10)
info.slots.AddSlot(50,120)
info.slots.AddSlot(120,10)


Local buildings:TBuildingList = New TBuildingList
Local b:TBuilding
b = buildings.AddNew(100,100, "HOUSE")
Local houseInfo:TbuildingInfo = infoList.Find("HOUSE")
b.info = houseinfo.clone()
b.info.name = "1st House"

b = buildings.AddNew(300,300, "HOUSE")
b.info = houseinfo.clone()
b.info.name = "2nd House"

b = buildings.AddNew(500,100, "BAR")
Local barInfo:TbuildingInfo = infoList.Find("BAR")
b.info = barInfo.clone()
b.info.name = "The Bar"


Repeat
	If MouseHit(1)
		For Local b:TBuilding = EachIn buildings
			If RectsOverlap(MouseX(),MouseY(),1,1,b.x,b.y,b.w,b.h)
				Local freeSlot:TSlot = b.info.slots.findFreeSlot()
				If freeSlot <> Null
					freeSlot.empty = False
				End If
			EndIf
		Next		
	End If

	Cls
		buildings.Draw()
	Flip
Until KeyHit(KEY_ESCAPE)

Function RectsOverlap:Int(x0:Float, y0:Float, w0:Float, h0:Float, x2:Float, y2:Float, w2:Float, h2:Float)
	If x0 > (x2 + w2) Or (x0 + w0) < x2 Then Return False
	If y0 > (y2 + h2) Or (y0 + h0) < y2 Then Return False
	Return True
End Function

' Clones an object and returns the clone. - by Azathoth
' Any fields that references an object only gets the reference copied unless MetaData contains {Clone}
' which then a copy is also made of the object referenced.
' {NoClone} prevents the field being copied.
Function CloneObject:Object(obj:Object)
	Local cobj:Object
	
	If obj=Null Then Return Null
	
	Local objId:TTypeId=TTypeId.ForObject(obj)
	
	If objId.ExtendsType(StringTypeId)
		Return String(obj)
	EndIf
	
	If objId.ExtendsType(ArrayTypeId)
		If objId.ArrayLength(obj)>0
			cobj=objId.NewArray(objId.ArrayLength(obj))
			
			If cobj
				For Local i:Int = 0 Until objId.ArrayLength(obj)
					If objId.ElementType().ExtendsType(ArrayTypeId) Or objId.ElementType().ExtendsType(StringTypeId) ..
						Or objId.ElementType().ExtendsType(ObjectTypeId)
						objId.SetArrayElement(cobj,i,CloneObject(objId.GetArrayElement(obj,i)))
					Else
						objId.SetArrayElement(cobj,i,objId.GetArrayElement(obj,i))
					EndIf
				Next
			EndIf
		EndIf
		
		Return cobj
	EndIf
	
	cobj=New obj
	
	For Local fld:TField=EachIn objId.EnumFields()
		Local fldId:TTypeId=fld.TypeId()
		If fld.Get(obj)<>Null And fld.MetaData("NoClone")=Null
			DebugLog fld.name()+"  "+fld.MetaData()+"  " fld.get(obj)
			If Not fld.MetaData("Clone")=Null
				fld.Set(cobj,CloneObject(fld.Get(obj)))
			Else
				fld.Set(cobj,fld.Get(obj))
			EndIf
		EndIf
	Next
	
	Return cobj
	
EndFunction


@Brucey, thanks for the info - Ill have a look at your persistence module - sounds interesting...

@TaskMaster, looks like your right... Azathoth CloneObject function doesnt like to copy the TSlotList field (I kept getting null pointers!? when trying to {Clone} it)
I think that has to do with {Clone} only affecting that specific field and not TSlotList's fields (it extends TList which doesn't contain the {Clone} metadata).

Edit: This was to prevent lock ups when trying to clone cyclic references.