Code archives/File Utilities/Object Loader
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
| Allows you to read or write an arbitrary object to a stream (eg. a file or over the network). Uses the reflection module to get runtime information about type structure (fields). May be of use when implementing saved games or multiplayer over the network. For an example program and a module version, please download the zip file from here: http://jan.varho.org/blog/programming/blitz/blitzmax-object-loader-module/ Bug reports (and patches) appreciated! UPDATE: Added checks for saving TList and TMap objects properly. | |||||
' Loads and saves any objects to a stream using the reflection module.
' Recursive - any and all objects referenced are also saved!
' Use the metadata key "no_save" to mark fields not to be saved.
SuperStrict
Import BRL.Reflection
'Saves an object to the stream specified
Function SaveObject(o:Object, s:TStream, tid:TTypeId = Null)
'Null?
If o
s.WriteByte True
Else
s.WriteByte False
Return
End If
'Get type Id
If tid = ObjectTypeId Then tid = TTypeId.ForObject(o)
If Not tid Then tid = TTypeId.ForObject(o)
'Save type name
s.WriteInt tid.Name().length
s.WriteString tid.Name()
'DebugLog "Saving "+tid.Name()
'Primitive type?
Select tid
'Integers
Case ByteTypeId
s.WriteByte Byte(String(o) )
Return
Case ShortTypeId
s.WriteShort Short(String(o) )
Return
Case IntTypeId
s.WriteInt Int(String(o) )
Return
Case LongTypeId
s.WriteLong Long(String(o) )
Return
'Floating Points
Case FloatTypeId
s.WriteFloat Float(String(o) )
Return
Case DoubleTypeId
s.WriteDouble Double(String(o) )
Return
'Strings
Case StringTypeId
s.WriteInt String(o).length
s.WriteString String(o)
Return
End Select
'Array?
If tid.Name().EndsWith("[]")
'Save length
s.WriteInt tid.ArrayLength(o)
'Element type
Local etid:TTypeId = TTypeId.ForName(tid.Name()[..tid.Name().length - 2])
'Save elements
For Local i:Int = 0 To tid.ArrayLength(o)-1
SaveObject tid.GetArrayElement(o, i), s, etid
Next
Return
End If
'Map?
If tid.Name()="TMap"
Local m:TMap = TMap(o)
'Save length
Local l:Int = 0
For Local key:Object = EachIn m.Keys()
l:+1
Next
s.WriteInt l
'Save key-value pairs
For Local node:TNode = EachIn m
SaveObject node.Key(), s
SaveObject node.Value(), s
Next
Return
End If
'List?
If tid.Name()="TList"
Local l:TList = TList(o)
'Save length
s.WriteInt l.Count()
'Save contents
For Local obj:Object = EachIn l
SaveObject obj, s
Next
Return
End If
'Save fields
For Local f:TField = EachIn tid.EnumFields()
If f.MetaData("no_save") Then Continue
SaveObject f.Get(o), s, f.TypeId()
Next
End Function
'Reads an object to the stream specified
Function LoadObject:Object(s:TStream)
'Null?
Select s.ReadByte()
Case False
Return Null
Case True
Default
RuntimeError "Not an object"
End Select
'Get type Id
Local tid:TTypeId = TTypeId.ForName(s.ReadString(s.ReadInt() ) )
'DebugLog "Loading "+tid.Name()
'Primitive type?
Select tid
'Integers
Case ByteTypeId
Return String.FromInt(s.ReadByte())
Case ShortTypeId
Return String.FromInt(s.ReadShort())
Case IntTypeId
Return String.FromInt(s.ReadInt())
Case LongTypeId
Return String.FromLong(s.ReadLong() )
'Floating point numbers
Case FloatTypeId
Return String.FromFloat(s.ReadFloat())
Case DoubleTypeId
Return String.FromDouble(s.ReadDouble() )
'Strings
Case StringTypeId
Return s.ReadString(s.ReadInt())
End Select
Local o:Object
'Array?
If tid.ElementType()
'Get length
Local l:Int = s.ReadInt()
'Create array
o = tid.NewArray(l)
'Load elements
For Local i:Int = 0 To l - 1
Local obj:Object = LoadObject(s)
If obj Then tid.SetArrayElement o, i, obj
Next
Return o
End If
'Map?
If tid.Name()="TMap"
Local m:TMap = New TMap
'Get length
Local l:Int = s.ReadInt()
'Load key-value pairs
For Local i:Int = 0 To l - 1
Local key:Object = LoadObject(s)
Local value:Object = LoadObject(s)
m.Insert key, value
Next
Return m
End If
'List?
If tid.Name()="TList"
Local l:TList = New TList
'Get length
Local length:Int = s.ReadInt()
'Load key-value pairs
For Local i:Int = 0 To length - 1
l.AddLast LoadObject(s)
Next
Return l
End If
'Create the object
o = tid.NewObject()
'Load fields
For Local f:TField = EachIn tid.EnumFields()
If f.MetaData("no_save") Then Continue
Local obj:Object = LoadObject(s)
If obj Then f.Set o, obj
Next
Return o
End Function |
Comments
| ||
| Hey Otus, Thanks for the code! It works great for me, with one exception - multidimensional arrays. I'm currently bombing out on both custom type 2d arrays, and regular 2d int arrays... not sure what the deal is yet, but wanted to post in case you want to look at it. |
| ||
| I don't think reflection supports multidimensional arrays. Could be wrong though. Have you tried arrays of arrays? That's usually the answer to multidim. array problems. |
| ||
| Good call - it doesn't. Looks like reflection doesn't return a multidimensional array as a TField. In my case, I wrapped my calls for saving out the 2d arrays with a function that took the 2d array, mapped it onto a 1d array, and then did the reverse when loading it. My arrays are all equal in their x and y dimensions, so that went pretty easily. I imagine that if people need this for non-square arrays, you could imbed data into the stream with that info. |
| ||
| When trying to compile the module version of this I get an error. Building test_module Compiling:objload.bmx Compile Error: Function can not return a value Which is in this line but I don't understand what is going on, help? 'Compares two fields Function CompareFieldNames(o1:Object, o2:Object) Return TField(o1).Name().Compare(TField(o2).Name() ) ' ? End Function |
| ||
markcw: Sorry, it should be Function CompareFieldNames:Int(o1:Object, o2:Object) No idea how that slipped through... :p |
| ||
| Oh great, thanks Otus. I was able to get it compiling by rem'ing them out and replacing the lines where they are called with the code here. Also, the update for TMap and TList is not in the module code. Must have slipped through eh? :) |
Code Archives Forum