WWW.XYZW.DE The homepage of Dierk "Chaos" Ohlerich  
Home Releases Codes
Types FPU intrinsics Input Lag Iterate & Delete Load & Save Memory Leaks About Exceptions AGP Writes

Loading and saving files is a lot of work. If you write a tool, you have to stream in and out every single variable you created in your document. My first implementations of serializing classes were very bloated. My current method is very simple but efficient and easy to use. I just use *data++ for reading and writing everything.

I like simple code. The method presented here is as simple as you can possibly get. The point is that it actually works, and that I use it in large applications without problems.

Image I have some structures you want to save:

/****************************************************************************/

struct BlaOp            // one operator for an intro
{
  sInt x,y,w;           // position in the page
  sChar Name[32];       // name of the operator
  sInt ClassId;         // class of the operator
  sU32 Data[64];        // payload of the operator
}

struct BlaDoc           // the document class
{
  sInt Count;           // count of operators
  BlaOp *Pages[256];   // list of operators
};

/****************************************************************************/

Now I want to write a code that reads and writes the document. I assume that I have already implemented sReadString() and sWriteString().

/****************************************************************************/

sInt BlaOp::Write(sU32 *&data)
{

  *data++ = 1;          // write version
  *data++ = x;
  *data++ = y;
  *data++ = z;
  *data++ = ClassId;
  *data++ = 0;          // some dummy data for easy extension
  *data++ = 0;
  *data++ = 0;
  sWriteString(data,Name);
  sCopyMem(data,Data,64*4); data+=64;
  return sTRUE;
}

sInt BlaOp::Read(sU32 *&data)
{
  sInt version = *data++;
  if(version<1 || version>1) return sFALSE;
  x = *data++;
  y = *data++;
  z = *data++;
  ClassId = *data++;
  data+=3;
  sReadString(data,Name,sizeof(Name));
  sCopyMem(Data,data,64*4); data+=64;
  return sTRUE;
}

sInt BlaDoc::Write(sU32 *&data)
{
  *data++ = 1;
  *data++ = Count;
  *data++ = 0;
  *data++ = 0;
  for(sInt i=0;i<Count;i++)
    if(!Ops[i]->Write(data))
      return sFALSE;
  return sTRUE;
}

sInt BlaDoc::Read(sU32 *&data)
{
  sInt version = *data++;
  if(version<1 || version>1) return sFALSE;
  Count = *data++;
  data+=2;
  for(sInt i=0;i<Count && ok;i++)
  {
    Ops[i] = new BlaOp;
    if(!Ops[i]->Read(data))
      return sFALSE;
  }
  return sTRUE;
}

/****************************************************************************/

Ok, you get the idea. This is straight forward. But it has serious problems, but we can get around these problems.

  1. All data is written as 32 bits

    This wastes a lot of space, but I found out that it is just not worth saving these extra bits. Note, we are not writing data into a 64k intro, we are writing data for the tool.
     
  2. When we write, we need to allocate a huge amount of memory, and if the saved data exceeds this memory, the application will crash.

    Usually I allocate 64MBytes and that should be enough. I make shure that the application crashes with a reasonable message when I run out of memory so that I know what happened. I had lots of fear about this problem, but in practice this simply is not an issue. The number of crashes because of this implementation is very small, less than five since fr-08. The number of crashes I had in the past because of buggy load/save code was much larger.
     
  3. You have to duplicate the members code in Read() and Write()

    Yes, but it is important to keep this style, you should never use memcpy(). This ensures that you can still read old files when you changed the struct.

Ok, that "out of memory" crash stinks. But there is a way around it: In the beginning of each Write() function, call sWritePatch(data). This function will check that the pointer is not too near to the end of the buffer. If it is, it may flush the buffer and change the data pointer to the new start of the buffer. This assumes that no Write() function writes more than a certain amount of data (lets say, 64KBytes). If a function write more than that, like a bitmap or vertex data, you can use a modified memcpy() function that calls sWritePatch() from time to time.