About Monkey 2 › Forums › Monkey 2 Development › Structs and reflection
Tagged: reflection
This topic contains 11 replies, has 3 voices, and was last updated by
Mark Sibly
1 year, 5 months ago.
-
AuthorPosts
-
September 20, 2017 at 8:58 pm #10652
Is there any chance structs will be supported by the reflection system in the future? (i.e. no technical reason why it can’t happen?)
I’m working on a basic general purpose serialization that uses reflection to load and save all of an object’s “settable” properties, but I got stuck figuring out the type of properties that contain structs.
Monkey123456789101112131415161718192021222324#Import "<std>"#Import "<mojo>"#Import "<reflection>"Using std..Using mojo..Function Main()Local test01 := "Test!"Local test02 := 1000.0Local test03 := New Bool[]( True, False, True )Local test04 := New Image( 128, 128 )Local test05 := New Stack<Int>Local test06 := New Vec3f( 0.5 )Local test07 := New Color( 0.1, 0.5, 1.0, 1.0 )Print Typeof( test01 )Print Typeof( test02 )Print Typeof( test03 )Print Typeof( test04 )Print Typeof( test05 )Print Typeof( test06 )Print Typeof( test07 )EndI don’t need this right now, so I can put this aside until there’s a solution, but I need to know if it’s not really doable so I can try to find a different solution on my own side.
Thanks!
September 20, 2017 at 11:02 pm #10656Just had a look at this, and the big problem is that structs are always passed ‘by value’, which means its impossible to modify them ‘by reference’, eg:
Monkey1234567891011121314Struct CField x:IntEndFunction SetX( c:C,i:Int )c.x=iEndFunction Main()Local c:=New CSetX( c,100 )Print c.x 'alas wont print 100!EndThis fails to SetX because SetX() receives a copy of ‘c’, not a reference to the same ‘c’.
Reflection has a similar problem – when you pass the ‘instance’ object to ‘Set’ or ‘Invoke’, you’re passing a copy of the struct value, so any modifications made by Set or Invoke are being made to a copy that will just be thrown away.
So I’ll keep looking, but right now the answer is no.
September 21, 2017 at 12:41 am #10657I’ve actually had some success with this and it may yet happen!
The trick is to treat the Variant as a ‘box’ for struct values. The struct value still gets copied when you set or get it, but the Set() and Invoke() methods can directly access the value in the variant/box.
So as long as you keep passing the same variant to decl.Set() etc, you’ll be updating the same struct value.
September 21, 2017 at 2:39 am #10658Very nice!
September 22, 2017 at 8:43 pm #10716So close! This code almost works with the latest dev branch. The goal is to print out all settable properties, recursively.
Only when we uncomment the “BrandNewName” property, which returns a struct, it fails. Can’t see why, since the Color property also returns a struct, yet that one works perfectly.Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113#Import "<std>"#Import "<mojo>"#Import "<reflection>"Using std..Using mojo..Class JsonObject ExtensionMethod Serialize( key:String, v:Variant )Select v.Type.NameCase "Double"SetNumber( key, Cast<Double>( v ) )Case "Float"SetNumber( key, Cast<Float>( v ) )Case "String"SetString( key, Cast<String>( v ) )Case "Bool"SetBool( key, Cast<Bool>( v ) )DefaultLocal newObj:= New JsonObjectnewObj.SetString( "Class", v.Type.Name )For Local decl:DeclInfo = Eachin v.Type.GetDecls()If decl.Kind = "Property" And decl.SettablenewObj.Serialize( decl.Name, decl.Get( v ) )EndNextSetObject( key, newObj.ToObject() )EndEndEndFunction Main()Local json := New JsonObject()json.Serialize( "text","Just a little text" )json.Serialize( "number", 100.0 )json.Serialize( "isItTrue?", False )json.Serialize( "Knight1", New KnightWhoSaysNi )json.Serialize( "Knight2", New KnightWhoSaysNi )json.Serialize( "KnightStruct", New NonsenseStruct )Print json.ToJson()End'***************** Test classes and structs *****************Class KnightWhoSaysNiPrivateField _name := "Ni!"Field _value:Float = 1000.0Field _schroob:= New SchruberryField _nonsense:= New NonsenseStructField _color := New Color( 0, 0, 1, 1 )PublicProperty Value:Float()Return _valueSetter( v:Float )_value = vEndProperty Name:String()Return _nameSetter( n:String )_name = nEndProperty Request:Schruberry()Return _schroobSetter( s:Schruberry )_schroob = sEnd'Uncomment these lines to get a scary error!' Property BrandNewName:NonsenseStruct()' Return _nonsense' Setter( n:String )' _nonsense.Words = n' EndProperty Color:Color()Return _colorSetter( c:Color )_color = cEndEndClass SchruberryProperty Random:Float()Return Rnd()Setter( bogus:Float )'One that looks nice. And not too expensive.EndEndStruct NonsenseStructPrivateField _words:= "Ekke Ekke Ekke Ekke Ptang Zoo Boing!"PublicProperty Words:String()Return _wordsSetter( w:String )_words = wEndEndThis will spit out the following string:
Monkey12345678910111213141516171819202122232425262728293031323334353637383940414243{"Knight1":{"Class":"default.KnightWhoSaysNi","Color":{"A":1,"B":1,"Class":"std.graphics.Color","G":0,"R":0},"Name":"Ni!","Request":{"Class":"default.Schruberry","Random":0.41015625},"Value":1000},"Knight2":{"Class":"default.KnightWhoSaysNi","Color":{"A":1,"B":1,"Class":"std.graphics.Color","G":0,"R":0},"Name":"Ni!","Request":{"Class":"default.Schruberry","Random":0.59253692626953125},"Value":1000},"KnightStruct":{"Class":"default.NonsenseStruct","Words":"Ekke Ekke Ekke Ekke Ptang Zoo Boing!"},"isItTrue?":true,"number":100,"text":"Just a little text"}Sorry for all the Monty Python references.
September 22, 2017 at 8:52 pm #10717Found it. The property that wasn’t working returns a struct “NonsenseStruct”, but it sets a String property in that struct, instead of setting the struct itself.
Changing it to this works:Monkey1234567Property BrandNewName:NonsenseStruct()Return _nonsenseSetter( n:NonsenseStruct )_nonsense = nEndSeptember 23, 2017 at 6:56 pm #10750Nice work, very bleeding edge! Will fix the property type bug ASAP.
I’ve successfully had generic struct instances reflected here (eg: Vec3f, Mat4f etc) but it needs a bit more work and I think a REFLECTION_FILTER equivalent also needs to be added before any of this is truly useful.
September 24, 2017 at 7:30 am #10786Thanks Mark!
Things are working nicely, but I’ve ran into a problem that may be more related to how inheritance works than to reflection… in the following code, I can’t serialize the objects coming from a stack with the correct extended type. They end up all being serialized as “BaseClass”, even though one of them is “ExtendedClass”.
Is there a way to cast them as i iterate through the stack?
Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293#Import "<std>"#Import "<reflection>"Using std..'************************ json extension '************************Class JsonObject ExtensionMethod Serialize( key:String, v:Variant )Select v.Type.NameCase "Double"SetNumber( key, Cast<Double>( v ) )Case "Float"SetNumber( key, Cast<Float>( v ) )Case "String"SetString( key, Cast<String>( v ) )Case "Bool"SetBool( key, Cast<Bool>( v ) )DefaultLocal newObj:= New JsonObjectnewObj.SetString( "Class", v.Type.Name )For Local decl:DeclInfo = Eachin v.Type.GetDecls()If ( decl.Kind = "Property" And decl.Settable )' Or ( decl.Kind = "Field" )newObj.Serialize( decl.Name, decl.Get( v ) )EndNextSetObject( key, newObj.ToObject() )EndEndEnd'************************ main function '************************Function Main()Local test1 := New BaseClass( "test1" )'This object will be reported as "BaseClass", instead of "ExtendedClass"Local test2 := New ExtendedClass( "test2", "Ni!" )Local json := New JsonObject()For Local t := Eachin BaseClass.All()json.Serialize( t.Name, t )Next'However this one, not coming from the stack, works!json.Serialize( "test3", New ExtendedClass( "test3", "NiNiNi!" ) )Print json.ToJson()End'************************ test classes '************************Class BaseClassProperty Name:String()Return _nameEndMethod New( name:String )_name = name_words= "Ekke Ekke Ekke Ekke Ptang Zoo Boing!"_all.Push( Self )EndFunction All:BaseClass[]()Return _all.ToArray()EndProtectedField _name:StringField _words:StringGlobal _all:= New Stack<BaseClass>EndClass ExtendedClass Extends BaseClassProperty Words:String()Return _wordsSetter( w:String )_words = wEndMethod New( name:String, words:String )Super.New( name )_words = wordsEndEndSeptember 25, 2017 at 11:11 pm #10807This is because Variant.Type only returns the static type of the variant. You’ll need to use InstanceType on the object contained in the variant instead of v.Type, eg:
Local type:=Cast<Object>( v ).InstanceType
…safer…
Monkey12345678Function DynamicType:Type( v:Variant )If v.Type.Kind="Class" or v.Type.Kind="Interface"Local inst:=Cast<Object>( v )If inst Return inst.InstanceTypeEndifReturn v.TypeEndI now think InstanceType should probably be renamed to DynamicType, and possibly the above DynamicType code added to Variant too which can do it more efficiently.
Anyway, quick fix for the above: change the Default block to:
Monkey12345678910Local newObj:= New JsonObjectLocal type:=Cast<Object>( v ).InstanceTypenewObj.SetString( "Class", type.Name )For Local decl:DeclInfo = Eachin type.GetDecls()If ( decl.Kind = "Property" And decl.Settable )' Or ( decl.Kind = "Field" )newObj.Serialize( decl.Name, decl.Get( v ) )EndNextSetObject( key, newObj.ToObject() )September 26, 2017 at 4:59 am #10810Sweet! Works now.
I remember something about serialization not recognizing generics, and I definitely see it not working.It also fails on Enum types, apparently. The way I’m gonna work around it is having properties that return arrays or other values, instead of generic classes and structs, so that instead of:
Monkey12345Property Position:Vec3<Float>()Return _positionSetter( p:Vec3<Float> )_position = pEndI’ll do:
Monkey12345Property Position:Float[]()Return New Float[]( _position.X, _position.Y, _position.Z )Setter( p:Float[] )_position = New Vec3f( p[0], p[1], p[2] )EndOr I can create ToArray() and FromArray() method extensions to classes and structs I know I’ll use anyway, like Vec3f, etc.
Gonna work on loading now. Will post Serialize/Deserialize code to archives once it works well (I already have it handling arrays, yay!).
Monkey2 is a lot of fun. Cheers!
October 31, 2017 at 2:12 am #11372and I think a REFLECTION_FILTER equivalent also needs to be added before any of this is truly useful.
I’m waiting for such filter!:)
I want to have minimal reflection where each class/interface would have its own instance for Typeof – to have comparison of them, and type should have at least Name property.
October 31, 2017 at 5:46 am #11375I’m waiting for such filter!:)
Soon – hopefully by v1.1.09!
-
AuthorPosts
You must be logged in to reply to this topic.