Serialization

Sometimes it's handy to be able to quickly convert data into a string using the setPref() and read it back using the getPref() commands. For instance, this is a great way to quickly save your game state.

Unfortunately, not all data goes back and forth that easily. For instance, if you have a list of strings, and take the value() of the string() of the list, you'll only sometimes get back the original list. If one of those strings has a RETURN in it, for instance, the reassembly of the list will fail.

This is where serialization comes in. Serialization is simply a method of changing binary data into a text-file-storable string. Many other languages have this built in by default, but Lingo, unfortunately, does not. So it's time to roll our own solution.

Below is a simple serialization movie script that will serialize and unserialize most content types for you. It will handle the following data types:

  • Booleans
  • Symbols
  • Integers
  • Floats
  • Strings
  • Points
  • Vectors
  • Rects
  • Lists
  • Property Lists
  • "Serializable" Objects

There are a few gotchas, here:

  • For integers and floats, it is up to you to set an appropriate float precision before serializing.
  • Numbers that end up in exponent notation will not be parsed properly.
  • Returns and other unprintable characters will remain in strings, so this may not be appropriate if you need everything in a single line. (If this is the case, you can write a routine to encode the unprintable characters.)
  • "Serializable" objects are objects which have saveInfo() and loadInfo() handlers. The saveInfo() command returns any data type defined above that can be used later to restore the properties of the object. The loadInfo() command accepts that data type back and restores the properties of the object. For instance, a simple object might return TRUE from a call to saveInfo(), and if loadInfo(TRUE) is called, it would restore the object to its default state. (Note that there are trickier ways to do this that don't require these methods, such as accessing properties by number, but it's better to explicitly define what properties are to be stored, especially since our serializer doesn't cover all possible data types, such as sprites, quads, members, etc.
  • "Serializable" objects just use new() in this incarnation (you will see why in later articles), but if you run into trouble with that, simply use the rawnew() command to bypass the "normal" new() behavior.
  • In case it isn't obvious, the scripts for "Serializable" objects must be available when you make your unserialize() call, and they cannot have been renamed in the interim. Otherwise, the engine wouldn't be able to create them. (If you need to be able to recreate objects without the scripts, you can write code to store the scriptText, too, but as that's not always available, you'll have to provide it explicitly.)
  • Objects can refer to other objects in the saveInfo() result, but those objects cannot refer back. You cannot save a "web" or dual-linked-list of objects, for example. If you need to do this, simply store the objects in the web in a cache somewhere, generate all the objects, and then hook them up in a second pass. (Again, you will see why, in a later article, we don't bother with this.)
  • And, of course, there are many data types not supported here, such as sprites, members, castlibs, bitmaps, quads, xtras, 3d nodes, etc. Typically, this isn't so bad, since you only want to store your model - the high-level computer view of the game. Most of the stuff this serializer doesn't support is display-level stuff or things that can be referenced by name.

To use this code, simply serialize() some data object, like a property list, and save it to a text file. To restore it, just unserialize() the returned text. In particular, unserialize(serialize(something)) should give you back something if the above rules are followed.

Here's the actual code to cut-and-paste. (I've tried to write it for clarity, not speed, so feel free to optimize.)

Made on Mac