Implementing a Type Field copy with Reflection?

BlitzMax Forums/BlitzMax Programming/Implementing a Type Field copy with Reflection?

In my Type I have a Method which copies all the fields of the type from the argument's same-type object

i.e.

Type MyType

  Field Field_a:Int
  Field Field_b:String

  Method Copy(_source:MyType)
    
    Field_a = _source.Field_a
    Field_b = _source.Field_b

  End Method
End Type

I'd like to use reflection here instead. My feeble start is below, but I'm a bit lost re Sets Gets and navigating Fields generally.

		Local self_id:TTypeId		=TTypeId.ForObject( Self)
		Local source_id:TTypeId 		= TTypeId.ForObject(_source)
		Local source_field_list:TList 	= source_id.Fields()
		Local source_field:TField 		= TField(source_field_list.First())
		
		For Local self_field:TField=EachIn self_id.EnumFields()
			self_field.Set Self, source_field.Get()
			source_field = OK, Now I admit I'm lost! These Sets and Gets confuse me...
		
		Next

Any help greatly appreciated!

Thanks.

-David

I can't help you with the reflection, but here's a handy general function for copying types which seems to work.

Type Tcake
	Field x,y
End Type

Function copytype(src:Object,dest:Object,thesize)
	MemCopy(Byte Ptr(dest),Byte Ptr(src),thesize)
End Function

my:Tcake=New Tcake
my.x=5
my.y=6

your:Tcake=New Tcake
copytype(my,your,SizeOf(Tcake))

DebugLog your.x
DebugLog your.y


nomen: congratulation to your active null reference creation.
Copy of memory does not change the reference count -> zack and bang if the original object ever goes out of scope



here is a function I wrote as proof of concept for the german boards:

SuperStrict

Type TCloneTest
   Field x:Int
   Field y:Int
End Type

Function DoClone:Object(obj:Object)
   Local typ:TTypeId    = TTypeId.ForObject(obj)
   Local clone:Object   = typ.newObject()
   
   For Local t:TField = EachIn typ.EnumFields()
      t.set(clone,t.get(obj))
   Next
   Return clone
End Function

Local original:TCloneTest = New TCloneTest
original.x = 10
original.y = 17


Local clone:TCloneTest = TCloneTest(DoClone(original))

Print "Clone: " + clone.x + ", " + clone.y



Copy of memory does not change the reference count -> zack and bang if the original object ever goes out of scope


Why should anything go bang? What does reference count have to do with anything? Why would you want to affect the reference count when creating a completely new object? Wouldn't that just stop the old object from ever being deleted? Why should an object going out of scope have any effect on the content of data you copied from it and is now residing in a completely different object?

Yeah but there is the little but massive problems that objects can hold references to other objects! And by memcopying them instead of assigning them, you totally break the GC (or in other terms: you would create weak references and therefor let the cloned object hold potential NULL references if the original object goes out of scope)
So everything you clone must be cloned by using this function hierarchically on all object references -> would need to be implemented in the bbObject class itself

Noel did that a few versions ago.
The GC dies with similar messages you get when you try to do callbacks to external libraries that are out of blitz for longer than the minimal timeslice of GC check.
Most likely because the clone can block the periodical GC checks

Ah, gotcha. I wasn't really thinking of objects holding other objects, since you can't really use that data for loading, saving etc anyway. You could clone it for immediate use though, so I see your point.

I've written a Clone function which may do what you want.

' Clones an object and returns the clone.
' 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=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
			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


nomen: congratulation to your active null reference creation
Hey, I never said it did a deep copy. Damn that infernal garbage collector!

Much thanks go to both Dreamora and Azathoth. Dreamora, I really liked the demonstrate-the-concept simplicity of your example. Helped me get my brain around it. Azathoth - talk about the swiss-army knife of clone functions! Amazing! Is this in the code archives? If not it probably should be : )

-David

I was going to put it in the archives but I was having problems getting arrays copied and thought I did something wrong, however it turns out its a bug and Mark says its fixed in the next release.

I'm interested in this too. Consider this.

Type TEmitter
   Field vPos:TVec3 = Vec3()
End Type

Type TVec3
   Field x:Float, y:Float, z:Float
End Type

Function Vec3:TVec3()
   Return New TVec3
End Function

Now if I wanted to create a fast copy of an Emitter which included all its fields that are types (TVec3), but I didn't want to go through each field vector and have to copy that too...what's the fastest way?

I know that doing a EmitterA = EmitterB is wrong because then EmitterA just becomes a pointer to EmitterB and isn't it's own Emitter per say. Maybe if there was a fast way to copy an entire type to another.But so you don't have to blah blah blah.

When I say fast, I mean something that could possibly be called each cycle with minimal tasking.

The fastest way is to hardcode the clone method simply.

the slower way is to use the above cloning method but as it is done through reflection, it will be slower.