RunUO Community

This is a sample guest message. Register a free account today to become a member! Once signed in, you'll be able to participate on this site by adding your own topics and posts, as well as connect with other members through your own private inbox!

Understanding Serialization

Vorspire

Knight
Understanding Serialization;

When you write a binary file and you serialize the data, you are writing the data in a compatible format that can be easily stored and read back into memory.
Binary serialization writes data in a stream of bytes, usually the sequence must be read back into memory in the same order that the data was written.

Imagine a binary file with 3 integers (Int32) serialized, each integer is represented by 4 consecutive bytes, giving a total of 12 bytes - the file size will be 12 bytes.
Gien that the first 4 bytes represent the number 123, the second set of 4 bytes represents 456 and the third set represents 789, in order to deserialize them prperly, you have to read them back in that order, starting at the first byte (index of 0).
This is where GenericReader comes in, when it is fist created, the internal stream byte index is 0 by default, placing it's reader pointer at the first byte.
With this in mind and knowing that the file represents 3 integers, you read back the first 4 bytes by calling reader.ReadInt()
When the reader has read the first 4 bytes, it's internal stream byte index also increments by 4, putting the internal stream byte index at 4 ready to read more data.
You already know there are 8 bytes remaining and they represent 2 different integers, the next time you call reader.RadInt(), it will again read 4 bytes and increment the internal stream byte index.

So basically, every time you read some data, a secific mount of bytes is read and the index offset is incremented by that number of bytes, altering the position within the file at which the next piece of data will be read.

Now having explained this, it should be obvious that if your data is offset by even one single byte, any data after that offset will fail to be read properly and cause issues.

This is why your serialize and deserialize methods must match up.
To recap, for every writer.Write, you need a reader.Read and the reader.Read must match the length of data written by writer.Write.
reader.Read and writer.Write are the only method calls that will increment the internal stream's byte index.

----

In standard RunUO practice, Serialize and Deserialize methods usually look like this:
Code:
pubic override void Serialize( GenericWrter writer )
{
    base.Serialize( writer );
 
    wrier.Write( (int)2 ); //version
 
    //version 2
    writer.Write( (int)789 );
 
    //version 1
    writer.Write( (int)456 );
 
    //version 0
    writer.Write( (int)123 );
}
 
public override void Deserialize( GenericReader reader )
{
    int version = reader.ReadInt( );
 
    switch( version )
    {
        case 2:
        {
              int fist = reader.ReadInt( );
        } goto case 1;
        case 1:
        {
              int second = reader.ReadInt( );
        } goto case 0;
        case 0:
        {
              int third = reader.ReadInt( );
        } break;
    }
}

I generally find it easier to track the order of the data by including a switch in the serialize method too;

Code:
pubic override void Serialize( GenericWrter writer )
{
    base.Serialize( writer );
 
    int version = 2;
 
    wrier.Write( version ); //version
 
    switch( version )
    {
        case 2:
        {
              writer.Write( (int)789 );
        } goto case 1;
        case 1:
        {
              writer.Write( (int)456 );
        } goto case 0;
        case 0:
        {
              writer.Write( (int)123 );
        } break;
    }
}
 
public override void Deserialize( GenericReader reader )
{
    base.Deserialize( reader );
 
    int version = reader.ReadInt( );
 
    switch( version )
    {
        case 2:
        {
              int fist = reader.ReadInt( );
        } goto case 1;
        case 1:
        {
              int second = reader.ReadInt( );
        } goto case 0;
        case 0:
        {
              int third = reader.ReadInt( );
        } break;
    }
}

Common data-types and their lengths;

'byte' / 'bool' == 1 byte
'short' / 'char' == 2 bytes
'int' == 4 bytes
'long' / 'double' == 8 bytes
 
Top