Code archives/Miscellaneous/(De)serialization llibrary
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
This small library offers a more OOP oriented way to dynamically store and load object structures to/from a file. This can be used in many situations; for instance, to store a game state for save-game purposes, or to record a demo. I have had this code lying around for quite a while, but decided to post it here so it can be of some use to anyone who needs it. The heart is a small interface that is implemented by the actual serializers. Posted below are 2 implementations for a serializer. The first stores the data in binary format. The second stores it as plain-text (ascii). There is a third that stores the data as an XML tree, but this one requires an external XML module and is therefor not included here. Going through the code for the first 2 serializers, should make it easy enough to adapt it to any other format you wish to use. Using it is piss-easy. Just create your game related types however you see fit. When you want to store an object in a file, do this: local mygame:Game = new Game ... Local bf:IFormatter = New BinaryFormatter; local file:TStream = WriteFile( "mysavegame.xxx" ); bf.Serialize( file, mygame ); CloseFile(file); ... And to Load the object from a file, do this: Local bf:IFormatter = New BinaryFormatter; local file:TStream = ReadFile( "mysavegame.xxx" ); mygame = Game( bf.Deserialize( file ) ); CloseFile(file); Demo code is included at the very bottom of the code listing. Note: It should be noted that this library is by no means feature complete. It will handle basic data types and custom types with basic data type fields just fine. Even arrays of any of the above as well as nested object trees. It does however not deal with TList and TMap objects. You will have to add these yourself. | |||||
'// IFormatter.bmx Rem The Interface that all Formatters implement. Makes for a somewhat more OOP-compliant approach. End Rem Type IFormatter Abstract Method Serialize(fs:TStream, obj:Object) Abstract Method Deserialize:Object(fs:TStream) Abstract End Type '// BinaryFormatter.bmx Type BinaryFormatter Extends IFormatter Const TYPETKN_UNDEFINED:Byte = 0; Const TYPETKN_BYTE:Byte = 1; Const TYPETKN_SHORT:Byte = 2; Const TYPETKN_INT:Byte = 3; Const TYPETKN_LONG:Byte = 4; Const TYPETKN_FLOAT:Byte = 5; Const TYPETKN_DOUBLE:Byte = 6; Const TYPETKN_OBJECT:Byte = 7; Const TYPETKN_STRING:Byte = 8; Const TYPETKN_ARRAY:Byte = 9; Method Deserialize:Object(fs:TStream) Return Self.RecursiveDeserialize(fs); End Method Method Serialize(fs:TStream, obj:Object) Self.RecursiveSerialize(fs, obj); End Method Method RecursiveDeserialize:Object(fs:TStream) Local strlen:Int = fs.ReadInt(); Local name:String = fs.ReadString(strlen); Local typeid:TTypeId = TTypeId.ForName(name); Local obj:Object = typeid.NewObject(); Local token:Byte = GetTypeToken(typeid); Select (token) Case TYPETKN_UNDEFINED; For Local fld:TField = EachIn typeid.EnumFields() token = GetTypeToken(fld.TypeId()); strlen = fs.ReadInt(); name = fs.ReadString(strlen); Select (token) Case TYPETKN_BYTE; fld.SetInt(obj, fs.ReadByte()); Case TYPETKN_SHORT; fld.SetInt(obj, fs.ReadShort()); Case TYPETKN_INT; fld.SetInt(obj, fs.ReadInt()); Case TYPETKN_LONG; fld.SetLong(obj, fs.ReadLong()); Case TYPETKN_FLOAT; fld.SetFloat(obj, fs.ReadFloat()); Case TYPETKN_DOUBLE; fld.SetDouble(obj, fs.ReadDouble()); Case TYPETKN_STRING; strlen = fs.ReadInt(); fld.SetString(obj, fs.ReadString(strlen)); Case TYPETKN_ARRAY; ReadArray(fs, fld.Get(obj)); Default '// Undefined, object fld.Set(obj, RecursiveDeserialize(fs)); End Select Next End Select Return obj; End Method Method RecursiveSerialize(fs:TStream, obj:Object) Local typeid:TTypeId = TTypeId.ForObject(obj); Local token:Byte = GetTypeToken(typeid); fs.WriteInt( typeid.Name().Length ); fs.WriteString( typeid.Name() ); Select (token) Case TYPETKN_UNDEFINED; For Local fld:TField = EachIn typeid.EnumFields() token = GetTypeToken(fld.TypeId()); fs.WriteInt(fld.Name().Length); fs.WriteString(fld.Name()); Select (token) Case TYPETKN_BYTE; fs.WriteByte( Byte(fld.GetInt(obj)) ); Case TYPETKN_SHORT; fs.WriteShort( Short(fld.GetInt(obj)) ); Case TYPETKN_INT; fs.WriteInt( fld.GetInt(obj) ); Case TYPETKN_LONG; fs.WriteLong( fld.GetLong(obj) ); Case TYPETKN_FLOAT; fs.WriteFloat( fld.GetFloat(obj) ); Case TYPETKN_DOUBLE; fs.WriteDouble( fld.GetDouble(obj) ); Case TYPETKN_STRING; fs.WriteInt( fld.GetString(obj).Length ); fs.WriteString( fld.GetString(obj) ); Case TYPETKN_ARRAY; WriteArray(fs, fld.Get(obj)); Default '// Undefined, object RecursiveSerialize(fs, fld.Get(obj)); End Select Next End Select End Method Method WriteArray(fs:TStream, arr:Object) Local typeid:TTypeId = TTypeId.ForObject(arr); Local alen:Int = typeid.ArrayLength(arr) ; fs.WriteInt(alen); For Local n:Int = 0 To alen -1 Local o:Object = typeid.GetArrayElement(arr, n); Local token:Byte = GetTypeToken(typeid.ElementType()); fs.WriteByte(token); If( token = TYPETKN_UNDEFINED ) Then RecursiveSerialize(fs, o); Else If( token = TYPETKN_ARRAY ) Then WriteArray(fs, o); Else fs.WriteInt(o.ToString().Length); fs.WriteString(o.ToString()); End If Next End Method Method ReadArray(fs:TStream, arr:Object) Local typeid:TTypeId = TTypeId.ForObject(arr); Local alen:Int = fs.ReadInt(); For Local n:Int = 0 To alen -1 Local token:Byte = fs.ReadByte(); Local o:Object = typeid.GetArrayElement(arr, n); If( token = TYPETKN_UNDEFINED ) Then typeid.SetArrayElement(arr, n, RecursiveDeserialize(fs)); Else If( token = TYPETKN_ARRAY ) Then ReadArray(fs, o); Else Local strlen:Int = fs.ReadInt(); Local val:String = fs.ReadString(strlen); typeid.SetArrayElement(arr, n, val); End If Next End Method Method GetTypeToken:Byte(tid:TTypeId) Select (tid) Case ByteTypeId; Return TYPETKN_BYTE; Case ShortTypeId; Return TYPETKN_SHORT; Case IntTypeId; Return TYPETKN_INT; Case LongTypeId; Return TYPETKN_LONG; Case FloatTypeId; Return TYPETKN_FLOAT; Case DoubleTypeId; Return TYPETKN_DOUBLE; Case ObjectTypeId; Return TYPETKN_OBJECT; Case StringTypeId; Return TYPETKN_STRING; Case ArrayTypeId; Return TYPETKN_ARRAY; Default; If(tid.ExtendsType( ArrayTypeId )) Then Return TYPETKN_ARRAY; End If Return TYPETKN_UNDEFINED; End Select End Method End Type '// AsciiFormatter.bmx Type AsciiFormatter Extends IFormatter Const TYPETKN_UNDEFINED:Byte = 0; Const TYPETKN_BYTE:Byte = 1; Const TYPETKN_SHORT:Byte = 2; Const TYPETKN_INT:Byte = 3; Const TYPETKN_LONG:Byte = 4; Const TYPETKN_FLOAT:Byte = 5; Const TYPETKN_DOUBLE:Byte = 6; Const TYPETKN_OBJECT:Byte = 7; Const TYPETKN_STRING:Byte = 8; Const TYPETKN_ARRAY:Byte = 9; Field Indent:Int; Method Deserialize:Object(fs:TStream) Return Self.RecursiveDeserialize( fs ); End Method Method Serialize(fs:TStream, obj:Object) Indent = 0; Self.RecursiveSerialize(fs, obj); End Method Method RecursiveDeserialize:Object( fs:TStream ) Local typeid:TTypeId = TTypeId.ForName( fs.ReadLine().Trim() ); Local obj:Object = typeid.NewObject(); Local token:Byte = GetTypeToken(typeid); Select (token) Case TYPETKN_UNDEFINED; For Local fld:TField = EachIn typeid.EnumFields() token = GetTypeToken( fld.TypeId() ); Select (token) Case TYPETKN_ARRAY; ReadArray( fs, fld.Get(obj) ); Case TYPETKN_UNDEFINED, TYPETKN_OBJECT; fld.Set( obj, RecursiveDeserialize( fs ) ); Default; fld.Set( obj, GetVal(fs)[1] ); End Select Next End Select Return obj; End Method Method RecursiveSerialize(fs:TStream, obj:Object) Local typeid:TTypeId = TTypeId.ForObject(obj); Local token:Byte = GetTypeToken(typeid); Write( fs, typeid.Name() ); Indent :+ 1; Select (token) Case TYPETKN_UNDEFINED; For Local fld:TField = EachIn typeid.EnumFields() token = GetTypeToken(fld.TypeId()); Select (token) Case TYPETKN_ARRAY; WriteArray(fs, fld.Get(obj), fld.Name()); Case TYPETKN_UNDEFINED, TYPETKN_OBJECT; RecursiveSerialize(fs, fld.Get(obj)); Default; Write(fs, fld.Name() + "=" + fld.GetString(obj)); End Select Next End Select Indent :- 1; End Method Method WriteArray( fs:TStream, arr:Object, name:String) Local typeid:TTypeId = TTypeId.ForObject(arr); Local alen:Int = typeid.ArrayLength(arr) ; Write(fs, name + " Size=" + alen); Indent :+ 1; For Local n:Int = 0 To alen -1 Local o:Object = typeid.GetArrayElement(arr, n); Local token:Byte = GetTypeToken(typeid.ElementType()); If( token = TYPETKN_UNDEFINED Or token = TYPETKN_OBJECT) Then RecursiveSerialize(fs, o); Else If( token = TYPETKN_ARRAY ) Then WriteArray(fs, o, name); Else Write(fs, typeid.ElementType().Name() + "=" + o.ToString()); End If Next Indent :- 1; End Method Method ReadArray( fs:TStream, arr:Object) Local typeid:TTypeId = TTypeId.ForObject(arr); Local alen:Int = Int(GetVal(fs)[1]); Local token:Byte = GetTypeToken( typeid.ElementType() ); For Local n:Int = 0 To alen -1 Local o:Object = typeid.GetArrayElement(arr, n); If( token = TYPETKN_UNDEFINED ) Then typeid.SetArrayElement(arr, n, RecursiveDeserialize( fs )); Else If( token = TYPETKN_ARRAY ) Then ReadArray(fs, o); Else typeid.SetArrayElement(arr, n, GetVal(fs)[1]); End If Next End Method Method GetTypeToken:Byte(tid:TTypeId) Select (tid) Case ByteTypeId; Return TYPETKN_BYTE; Case ShortTypeId; Return TYPETKN_SHORT; Case IntTypeId; Return TYPETKN_INT; Case LongTypeId; Return TYPETKN_LONG; Case FloatTypeId; Return TYPETKN_FLOAT; Case DoubleTypeId; Return TYPETKN_DOUBLE; Case ObjectTypeId; Return TYPETKN_OBJECT; Case StringTypeId; Return TYPETKN_STRING; Case ArrayTypeId; Return TYPETKN_ARRAY; Default; If(tid.ExtendsType( ArrayTypeId )) Then Return TYPETKN_ARRAY; End If Return TYPETKN_UNDEFINED; End Select End Method Method GetVal:String[](fs:TStream) Local line:String = fs.ReadLine(); Local ret:String[] = [line[..line.Find("=")], line[line.Find("=") + 1..]]; For Local n:Int = 0 To ret.Length -1 ret[n] = ret[n].Trim(); Next Return ret; End Method Method Write(fs:TStream, val:String) Local padding:String =""; For Local n:Int = 0 To Indent - 1 padding :+ " "; Next fs.WriteLine(padding + val); End Method End Type '// DEMO.bmx SuperStrict Framework brl.basic Import brl.reflection Include "IFormatter.bmx" Include "BinaryFormatter.bmx" Include "AsciiFormatter.bmx" '// Some basic Game objects Type Player Field Score:Int; Field Name:String; Field Location:Float[]; Method New() Score = 0; Name = "Unnamed Player"; Location = New Float[2]; End Method Method ToString:String() Return (Name + " (" + Score + ")"); End Method End Type Type Game Field Players:Player[]; Field RandSeed:Int; Field Level:Int; Method New() Level = 1; RandSeed = MilliSecs(); SeedRnd(RandSeed); '// All array objects MUST be initialized for the binaryformatter to work properly! '// The reason for this is that the routine that puts the values back in these lists '// expects the objects to be valid instances. Players = New Player[2]; Players[0] = New Player; Players[1] = New Player; End Method Method ToString:String() Return ("Level: " + Level + ", " + Players[0].ToString() + ", " + Players[1].ToString() ); End Method End Type '// Define our objects : (Un)comment here to use a different formatter. Local bf:IFormatter = New BinaryFormatter; 'Local bf:IFormatter = New AsciiFormatter; Local file:TStream = Null; Local savefile:String = "game.sav"; Local g:Game = New Game; '// Setup some basic game parameters with initial values to simulate a game in progress. g.Level = 10; g.Players[0].Name ="Bob"; g.Players[0].Score = Rand(0, 1000); g.Players[0].Location[0] = Rand(0, 500); g.Players[0].Location[1] = Rand(0, 500); g.Players[1].Name ="Mike"; g.Players[1].Score = Rand(0, 1000); g.Players[1].Location[0] = Rand(0, 500); g.Players[1].Location[1] = Rand(0, 500); '// Show the Initial output Print("--- PRESAVE TEST ----------------------------------------------"); Print(g.ToString()); Print("x: " + g.Players[1].Location[0] + " y:" + g.Players[1].Location[1] ); '// Save the entire game to a file in it's current state. Try file = WriteFile(savefile); bf.Serialize( file, g ); Catch e:Object Print(e.ToString()); End Try If(file <> Null) Then file.Close(); End If '// clear game so we can load it up fresh from a file. g = Null; Try file = ReadFile(savefile); g = Game(bf.Deserialize( file )); Catch e:Object Print(e.ToString()); End Try If(file <> Null) Then file.Close(); End If '// bog out when something went wrong. If( g = Null ) Then End; '// Show the new output '// If all went well, this should be identical to the presave output. Print("--- POSTSAVE TEST ----------------------------------------------"); Print(g.ToString()); Print("x: " + g.Players[1].Location[0] + " y:" + g.Players[1].Location[1] ); End; |
Comments
None.
Code Archives Forum