October 28, 2020, 10:50:26 PM

Author Topic: [bmx] (De)serialization llibrary by Jim Teeuwen [ 1+ years ago ]  (Read 1681 times)

Offline BlitzBot

  • Jr. Member
  • **
  • Posts: 1
Title : (De)serialization llibrary
Author : Jim Teeuwen
Posted : 1+ years ago

Description : 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.


Code :
Code: BlitzMax
  1. '// IFormatter.bmx
  2. Rem
  3.         The Interface that all Formatters implement.
  4.         Makes for a somewhat more OOP-compliant approach.
  5. End Rem
  6. Type IFormatter Abstract
  7.  
  8.         Method Serialize(fs:TStream, obj:Object) Abstract
  9.         Method Deserialize:Object(fs:TStream) Abstract
  10.  
  11. End Type
  12.  
  13.  
  14.  
  15. '// BinaryFormatter.bmx
  16. Type BinaryFormatter Extends IFormatter
  17.  
  18.         Const TYPETKN_UNDEFINED:Byte = 0;
  19.         Const TYPETKN_BYTE:Byte = 1;
  20.         Const TYPETKN_SHORT:Byte = 2;
  21.         Const TYPETKN_INT:Byte = 3;
  22.         Const TYPETKN_LONG:Byte = 4;
  23.         Const TYPETKN_FLOAT:Byte = 5;
  24.         Const TYPETKN_DOUBLE:Byte = 6;
  25.         Const TYPETKN_OBJECT:Byte = 7;
  26.         Const TYPETKN_STRING:Byte = 8;
  27.         Const TYPETKN_ARRAY:Byte = 9;
  28.  
  29.         Method Deserialize:Object(fs:TStream)
  30.                 Return Self.RecursiveDeserialize(fs);
  31.         End Method
  32.         Method Serialize(fs:TStream, obj:Object)
  33.                 Self.RecursiveSerialize(fs, obj);
  34.         End Method
  35.         Method RecursiveDeserialize:Object(fs:TStream)
  36.                 Local strlen:Int = fs.ReadInt();
  37.                 Local name:String = fs.ReadString(strlen);
  38.                 Local typeid:TTypeId = TTypeId.ForName(name);
  39.                 Local obj:Object = typeid.NewObject();
  40.                 Local token:Byte = GetTypeToken(typeid);
  41.  
  42.                 Select (token)
  43.                         Case TYPETKN_UNDEFINED;
  44.                                 For Local fld:TField = EachIn typeid.EnumFields()
  45.                                         token = GetTypeToken(fld.TypeId());
  46.                                         strlen = fs.ReadInt();
  47.                                         name = fs.ReadString(strlen);
  48.  
  49.                                         Select (token)
  50.                                                 Case TYPETKN_BYTE;
  51.                                                         fld.SetInt(obj, fs.ReadByte());
  52.                                
  53.                                                 Case TYPETKN_SHORT;
  54.                                                         fld.SetInt(obj, fs.ReadShort());
  55.                                
  56.                                                 Case TYPETKN_INT;
  57.                                                         fld.SetInt(obj, fs.ReadInt());
  58.                                
  59.                                                 Case TYPETKN_LONG;
  60.                                                         fld.SetLong(obj, fs.ReadLong());
  61.                                
  62.                                                 Case TYPETKN_FLOAT;
  63.                                                         fld.SetFloat(obj, fs.ReadFloat());
  64.                                
  65.                                                 Case TYPETKN_DOUBLE;
  66.                                                         fld.SetDouble(obj, fs.ReadDouble());
  67.                                
  68.                                                 Case TYPETKN_STRING;
  69.                                                         strlen = fs.ReadInt();
  70.                                                         fld.SetString(obj, fs.ReadString(strlen));
  71.  
  72.                                                 Case TYPETKN_ARRAY;
  73.                                                         ReadArray(fs, fld.Get(obj));
  74.  
  75.                                                 Default  '// Undefined, object
  76.                                                         fld.Set(obj, RecursiveDeserialize(fs));
  77.                                
  78.                                         End Select
  79.                                 Next
  80.                 End Select
  81.  
  82.                 Return obj;
  83.         End Method
  84.         Method RecursiveSerialize(fs:TStream, obj:Object)
  85.                 Local typeid:TTypeId = TTypeId.ForObject(obj);
  86.                 Local token:Byte = GetTypeToken(typeid);
  87.  
  88.                 fs.WriteInt( typeid.Name().Length );
  89.                 fs.WriteString( typeid.Name() );
  90.                
  91.                 Select (token)
  92.                         Case TYPETKN_UNDEFINED;
  93.                                 For Local fld:TField = EachIn typeid.EnumFields()
  94.                                         token = GetTypeToken(fld.TypeId());
  95.                                         fs.WriteInt(fld.Name().Length);
  96.                                         fs.WriteString(fld.Name());
  97.                                                
  98.                                         Select (token)
  99.                                                 Case TYPETKN_BYTE;
  100.                                                         fs.WriteByte( Byte(fld.GetInt(obj)) );
  101.                                
  102.                                                 Case TYPETKN_SHORT;
  103.                                                         fs.WriteShort( Short(fld.GetInt(obj)) );
  104.                                
  105.                                                 Case TYPETKN_INT;
  106.                                                         fs.WriteInt( fld.GetInt(obj) );
  107.                                
  108.                                                 Case TYPETKN_LONG;
  109.                                                         fs.WriteLong( fld.GetLong(obj) );
  110.                                
  111.                                                 Case TYPETKN_FLOAT;
  112.                                                         fs.WriteFloat( fld.GetFloat(obj) );
  113.                                
  114.                                                 Case TYPETKN_DOUBLE;
  115.                                                         fs.WriteDouble( fld.GetDouble(obj) );
  116.                                
  117.                                                 Case TYPETKN_STRING;
  118.                                                         fs.WriteInt( fld.GetString(obj).Length );
  119.                                                         fs.WriteString( fld.GetString(obj) );
  120.                                
  121.                                                 Case TYPETKN_ARRAY;
  122.                                                         WriteArray(fs, fld.Get(obj));
  123.  
  124.                                                 Default  '// Undefined, object
  125.                                                         RecursiveSerialize(fs, fld.Get(obj));
  126.                                
  127.                                         End Select
  128.                                 Next
  129.                         End Select
  130.                
  131.         End Method
  132.         Method WriteArray(fs:TStream, arr:Object)
  133.                 Local typeid:TTypeId = TTypeId.ForObject(arr);
  134.                 Local alen:Int = typeid.ArrayLength(arr) ;
  135.                 fs.WriteInt(alen);
  136.                 For Local n:Int = 0 To alen -1
  137.                         Local o:Object = typeid.GetArrayElement(arr, n);
  138.                         Local token:Byte = GetTypeToken(typeid.ElementType());
  139.                         fs.WriteByte(token);
  140.                         If( token = TYPETKN_UNDEFINED ) Then
  141.                                 RecursiveSerialize(fs, o);
  142.                         Else If( token = TYPETKN_ARRAY ) Then
  143.                                 WriteArray(fs, o);
  144.                         Else
  145.                                 fs.WriteInt(o.ToString().Length);
  146.                                 fs.WriteString(o.ToString());
  147.                         End If
  148.                 Next
  149.         End Method
  150.         Method ReadArray(fs:TStream, arr:Object)
  151.                 Local typeid:TTypeId = TTypeId.ForObject(arr);
  152.                 Local alen:Int = fs.ReadInt();
  153.                 For Local n:Int = 0 To alen -1
  154.                         Local token:Byte = fs.ReadByte();
  155.                         Local o:Object = typeid.GetArrayElement(arr, n);
  156.                         If( token = TYPETKN_UNDEFINED ) Then
  157.                                 typeid.SetArrayElement(arr, n, RecursiveDeserialize(fs));
  158.                         Else If( token = TYPETKN_ARRAY ) Then
  159.                                 ReadArray(fs, o);
  160.                         Else
  161.                                 Local strlen:Int = fs.ReadInt();
  162.                                 Local val:String = fs.ReadString(strlen);
  163.                                 typeid.SetArrayElement(arr, n, val);
  164.                         End If
  165.                 Next
  166.         End Method
  167.         Method GetTypeToken:Byte(tid:TTypeId)
  168.                 Select (tid)
  169.                         Case ByteTypeId; Return TYPETKN_BYTE;
  170.                         Case ShortTypeId; Return TYPETKN_SHORT;
  171.                         Case IntTypeId; Return TYPETKN_INT;
  172.                         Case LongTypeId; Return TYPETKN_LONG;
  173.                         Case FloatTypeId; Return TYPETKN_FLOAT;
  174.                         Case DoubleTypeId; Return TYPETKN_DOUBLE;
  175.                         Case ObjectTypeId; Return TYPETKN_OBJECT;
  176.                         Case StringTypeId; Return TYPETKN_STRING;
  177.                         Case ArrayTypeId; Return TYPETKN_ARRAY;
  178.                         Default;
  179.                                 If(tid.ExtendsType( ArrayTypeId )) Then
  180.                                         Return TYPETKN_ARRAY;
  181.                                 End If
  182.                                 Return TYPETKN_UNDEFINED;
  183.                 End Select
  184.         End Method
  185.  
  186. End Type
  187.  
  188.  
  189.  
  190.  
  191. '// AsciiFormatter.bmx
  192. Type AsciiFormatter Extends IFormatter
  193.  
  194.         Const TYPETKN_UNDEFINED:Byte = 0;
  195.         Const TYPETKN_BYTE:Byte = 1;
  196.         Const TYPETKN_SHORT:Byte = 2;
  197.         Const TYPETKN_INT:Byte = 3;
  198.         Const TYPETKN_LONG:Byte = 4;
  199.         Const TYPETKN_FLOAT:Byte = 5;
  200.         Const TYPETKN_DOUBLE:Byte = 6;
  201.         Const TYPETKN_OBJECT:Byte = 7;
  202.         Const TYPETKN_STRING:Byte = 8;
  203.         Const TYPETKN_ARRAY:Byte = 9;
  204.  
  205.         Field Indent:Int;
  206.  
  207.         Method Deserialize:Object(fs:TStream)
  208.                 Return Self.RecursiveDeserialize( fs );
  209.         End Method
  210.         Method Serialize(fs:TStream, obj:Object)
  211.                 Indent = 0;
  212.                 Self.RecursiveSerialize(fs, obj);
  213.         End Method
  214.         Method RecursiveDeserialize:Object( fs:TStream )
  215.                 Local typeid:TTypeId = TTypeId.ForName( fs.ReadLine().Trim() );
  216.                 Local obj:Object = typeid.NewObject();
  217.                 Local token:Byte = GetTypeToken(typeid);
  218.  
  219.                 Select (token)
  220.                         Case TYPETKN_UNDEFINED;
  221.                                 For Local fld:TField = EachIn typeid.EnumFields()
  222.                                         token = GetTypeToken( fld.TypeId() );
  223.  
  224.                                         Select (token)
  225.                                                 Case TYPETKN_ARRAY;
  226.                                                         ReadArray( fs, fld.Get(obj) );
  227.  
  228.                                                 Case TYPETKN_UNDEFINED, TYPETKN_OBJECT;
  229.                                                         fld.Set( obj, RecursiveDeserialize( fs ) );
  230.  
  231.                                                 Default;
  232.                                                         fld.Set( obj, GetVal(fs)[1] );
  233.                                
  234.                                                 End Select
  235.                                 Next
  236.                 End Select
  237.                 Return obj;
  238.         End Method
  239.         Method RecursiveSerialize(fs:TStream, obj:Object)
  240.                 Local typeid:TTypeId = TTypeId.ForObject(obj);
  241.                 Local token:Byte = GetTypeToken(typeid);
  242.  
  243.                 Write( fs, typeid.Name() );
  244.                 Indent :+ 1;
  245.        
  246.                 Select (token)
  247.                         Case TYPETKN_UNDEFINED;
  248.                                 For Local fld:TField = EachIn typeid.EnumFields()
  249.                                         token = GetTypeToken(fld.TypeId());
  250.        
  251.                                         Select (token)
  252.                                                 Case TYPETKN_ARRAY;
  253.                                                         WriteArray(fs, fld.Get(obj), fld.Name());
  254.  
  255.                                                 Case TYPETKN_UNDEFINED,  TYPETKN_OBJECT;
  256.                                                         RecursiveSerialize(fs, fld.Get(obj));
  257.                                
  258.                                                 Default;
  259.                                                         Write(fs, fld.Name() + "=" + fld.GetString(obj));
  260.  
  261.                                         End Select
  262.                                 Next
  263.                 End Select
  264.  
  265.                 Indent :- 1;
  266.  
  267.         End Method
  268.         Method WriteArray( fs:TStream, arr:Object, name:String)
  269.                 Local typeid:TTypeId = TTypeId.ForObject(arr);
  270.                 Local alen:Int = typeid.ArrayLength(arr) ;
  271.                 Write(fs, name + " Size=" + alen);
  272.                 Indent :+ 1;
  273.                 For Local n:Int = 0 To alen -1
  274.                         Local o:Object = typeid.GetArrayElement(arr, n);
  275.                         Local token:Byte = GetTypeToken(typeid.ElementType());
  276.                         If( token = TYPETKN_UNDEFINED Or  token = TYPETKN_OBJECT) Then
  277.                                 RecursiveSerialize(fs, o);
  278.                         Else If( token = TYPETKN_ARRAY ) Then
  279.                                 WriteArray(fs, o, name);
  280.                         Else
  281.                                 Write(fs, typeid.ElementType().Name() + "=" + o.ToString());
  282.                         End If
  283.                 Next
  284.                 Indent :- 1;
  285.         End Method
  286.         Method ReadArray( fs:TStream, arr:Object)
  287.                 Local typeid:TTypeId = TTypeId.ForObject(arr);
  288.                 Local alen:Int = Int(GetVal(fs)[1]);
  289.                 Local token:Byte = GetTypeToken( typeid.ElementType() );
  290.                 For Local n:Int = 0 To alen -1
  291.                         Local o:Object = typeid.GetArrayElement(arr, n);
  292.                         If( token = TYPETKN_UNDEFINED ) Then
  293.                                 typeid.SetArrayElement(arr, n, RecursiveDeserialize( fs ));
  294.                         Else If( token = TYPETKN_ARRAY ) Then
  295.                                 ReadArray(fs, o);
  296.                         Else
  297.                                 typeid.SetArrayElement(arr, n, GetVal(fs)[1]);
  298.                         End If
  299.                 Next
  300.         End Method
  301.         Method GetTypeToken:Byte(tid:TTypeId)
  302.                 Select (tid)
  303.                         Case ByteTypeId; Return TYPETKN_BYTE;
  304.                         Case ShortTypeId; Return TYPETKN_SHORT;
  305.                         Case IntTypeId; Return TYPETKN_INT;
  306.                         Case LongTypeId; Return TYPETKN_LONG;
  307.                         Case FloatTypeId; Return TYPETKN_FLOAT;
  308.                         Case DoubleTypeId; Return TYPETKN_DOUBLE;
  309.                         Case ObjectTypeId; Return TYPETKN_OBJECT;
  310.                         Case StringTypeId; Return TYPETKN_STRING;
  311.                         Case ArrayTypeId; Return TYPETKN_ARRAY;
  312.                         Default;
  313.                                 If(tid.ExtendsType( ArrayTypeId )) Then
  314.                                         Return TYPETKN_ARRAY;
  315.                                 End If
  316.                                 Return TYPETKN_UNDEFINED;
  317.                 End Select
  318.         End Method
  319.         Method GetVal:String[](fs:TStream)
  320.                 Local line:String = fs.ReadLine();
  321.                 Local ret:String[] = [line[..line.Find("=")], line[line.Find("=") + 1..]];
  322.                
  323.                 For Local n:Int = 0 To ret.Length -1
  324.                         ret[n] = ret[n].Trim();
  325.                 Next
  326.  
  327.                 Return ret;
  328.         End Method
  329.         Method Write(fs:TStream, val:String)
  330.                 Local padding:String ="";
  331.                 For Local n:Int = 0 To Indent - 1
  332.                         padding :+ "  ";
  333.                 Next
  334.                 fs.WriteLine(padding + val);
  335.         End Method
  336.  
  337. End Type
  338.  
  339.  
  340.  
  341. '// DEMO.bmx
  342. SuperStrict
  343. Framework brl.basic
  344. Import brl.reflection
  345.  
  346. Include "IFormatter.bmx"
  347. Include "BinaryFormatter.bmx"
  348. Include "AsciiFormatter.bmx"
  349.  
  350. '// Some basic Game objects
  351. Type Player
  352.         Field Score:Int;
  353.         Field Name:String;
  354.         Field Location:Float[];
  355.  
  356.         Method New()
  357.                 Score = 0;
  358.                 Name = "Unnamed Player";
  359.                 Location = New Float[2];
  360.         End Method
  361.  
  362.         Method ToString:String()
  363.                 Return (Name + " (" + Score + ")");
  364.         End Method
  365. End Type
  366.  
  367. Type Game
  368.         Field Players:Player[];
  369.         Field RandSeed:Int;
  370.         Field Level:Int;
  371.  
  372.         Method New()
  373.                 Level = 1;
  374.                 RandSeed = MilliSecs();
  375.                 SeedRnd(RandSeed);
  376.  
  377.                 '// All array objects MUST be initialized for the binaryformatter to work properly!
  378.                 '// The reason for this is that the routine that puts the values back in these lists
  379.                 '// expects the objects to be valid instances.
  380.                 Players = New Player[2];
  381.                 Players[0] = New Player;
  382.                 Players[1] = New Player;
  383.         End Method
  384.  
  385.         Method ToString:String()
  386.                 Return ("Level: " + Level + ", " + Players[0].ToString() + ", " + Players[1].ToString() );
  387.         End Method
  388.  
  389. End Type
  390.  
  391.  
  392. '// Define our objects : (Un)comment here to use a different formatter.
  393. Local bf:IFormatter = New BinaryFormatter;
  394. 'Local bf:IFormatter = New AsciiFormatter;
  395.  
  396. Local file:TStream = Null;
  397. Local savefile:String = "game.sav";
  398. Local g:Game = New Game;
  399.  
  400. '// Setup some basic game parameters with initial values to simulate a game in progress.
  401. g.Level = 10;
  402. g.Players[0].Name ="Bob";
  403. g.Players[0].Score = Rand(0, 1000);
  404. g.Players[0].Location[0] = Rand(0, 500);
  405. g.Players[0].Location[1] = Rand(0, 500);
  406. g.Players[1].Name ="Mike";
  407. g.Players[1].Score = Rand(0, 1000);
  408. g.Players[1].Location[0] = Rand(0, 500);
  409. g.Players[1].Location[1] = Rand(0, 500);
  410.  
  411.  
  412. '// Show the Initial output
  413. Print("--- PRESAVE TEST ----------------------------------------------");
  414. Print(g.ToString());
  415. Print("x: " + g.Players[1].Location[0] + " y:" + g.Players[1].Location[1] );
  416.  
  417.  
  418. '// Save the entire game to a file in it's current state.
  419. Try
  420.         file = WriteFile(savefile);
  421.         bf.Serialize( file, g );
  422. Catch e:Object
  423.         Print(e.ToString());
  424. End Try
  425.  
  426. If(file <> Null) Then
  427.         file.Close();
  428. End If
  429.  
  430.  
  431. '// clear game so we can load it up fresh from a file.
  432. g = Null;
  433.  
  434. Try
  435.         file = ReadFile(savefile);
  436.         g = Game(bf.Deserialize( file ));
  437. Catch e:Object
  438.         Print(e.ToString());
  439. End Try
  440.  
  441. If(file <> Null) Then
  442.         file.Close();
  443. End If
  444.  
  445. '// bog out when something went wrong.
  446. If( g = Null ) Then End;
  447.  
  448. '// Show the new output
  449. '// If all went well, this should be identical to the presave output.
  450. Print("--- POSTSAVE TEST ----------------------------------------------");
  451. Print(g.ToString());
  452. Print("x: " + g.Players[1].Location[0] + " y:" + g.Players[1].Location[1] );
  453.  
  454. End;


Comments : none...

 

SimplePortal 2.3.6 © 2008-2014, SimplePortal