Skip to main content

Serialization

You will often want to save and load data. Such as when implementing save/load mechanic in your game or when working with network.

Meep offers 2 main serialization mechanisms:

  • JSON
  • Binary

JSON

Meep implements JSON serialization via standard convention method called toJSON and fromJSON respectively. This is done mainly for convenience and for the sake of compatibility with external libraries/tools.

Example
import {Transform} from '@woosh/meep-engine/src/engine/ecs/transform/Transform'

const transform = new Transform();

// serialize
const json = transform.toJSON();

// deserialize
transform.fromJSON(json);

You will find that most of the components and core data classes that Meep ships with come with these methods already implemented.

Binary

Meep has a custom serialization framework, designed for speed and special use cases that appear in complex applications.

Why not JSON

JSON is a very convenient format, so why not JSON?

  • It's slow to parse. Because it's text, and even as text it's pretty slow because of its hierarchical structure, meaning you have to parse read and tokenize the entire piece of text representing an object before you can even start parsing, which takes a lot of memory and required intermediate representation to be held.
  • JSON is huge. Consider storing a number Math.PI, of course it's an irrational number, but in JavaScript we can represent it with 64 bit floating point number, and converting it to string we get 3.141592653589793, we could represent it directly as binary and use 64 bits (4 bytes), but as JSON we would need to store at least 17 characters to represent this same number, or 17 bytes at the very least, that's x4.25 times worse than raw binary.

The short answer is - binary is much faster and smaller. This is why we store geometry data as binary, see formats like GLTF and FBX, why we store images, audio and just about anything else with decent amount of information as binary.

Structure

  • BinaryBuffer this is an adapter which deals with encoding/decoding numbers and strings for you.

  • BinarySerializationAdapter this is an interface for encoding/decoding complete objects. For example, if you have a component in your game for Health - you might write something like:

    Example
    class Health{
    current: number = 9
    maximum: number = 10
    }

    class HealthSerializationAdapter extends BinarySerializationAdapter{

    klass = Health
    version = 0

    serialize(buffer: BinaryBuffer, health: Health):void {
    buffer.writeUint32(health.current);
    buffer.writeUint32(health.maximum);
    }

    deserialize(buffer: BinaryBuffer, health: Health):void {
    health.current = buffer.readUint32();
    health.maximum = buffer.readUint32();
    }

    }
  • BinaryClassUpgrader this is a support class that lets you seamlessly read data that was written in an older data format. This is incredibly useful as games change, and we modify what we store and how we do it, BinaryClassUpgrader allows us to still read user's stored data without having to tell the user "Old save files are not supported" or having to restrict ourselves and how modify the data at all.

Here's how all that works

note

Anecdotally, when we were working on our game Might is Right we ran into a problem with JSON that our save files were becoming 20+ Mb in size or larger. And were taking ~2seconds to create. Similarly, parsing those files was taking even longer. With binary format we went down to 60ms, which is 33 times faster. Better yet, it creates no garbage in memory.

Performance as a feature

Because our binary serialization framework produces very file sizes and works incredibly fast - it enables a few features:

  • You can store entire game world, without having to choose which parts of the data should be treated as "dynamic". Just serialize the entire world.
  • Automatics saves can be done in real-time without having to pause the game for the player