Skip to content
Aprius edited this page Sep 1, 2024 · 13 revisions

image

Notes: Data use Unity.Serialization to serialize data

It allows you to store data in byte[] form in blocks called profiles. Usage is quite simple and similar to PlayerPrefs

Each profile is a Dictionary with the key as a string and the value as a byte[].

The default profile will be 0 if you want to Load or Save in another profile you will need to call the ChangeProfile function.

using Pancake;

Data.ChangeProfile(1);

The Load function will load all the data of the current profile into memory. When the game starts it will be called automatically

using Pancake;

Data.Load();

The Save function will save all changes of the current profile to a file. It will be called automatically when you leave the game.

using Pancake;

Data.Save();

When you switch profiles, it will save the data of the current profile and then load the profile you need.

After you have successfully loaded the profile you can Load part data from the profile by key in memory using the following two methods

using Pancake;

PlayerData player = Data.Load<PlayerData>("hero");
Data.Load<PlayerData>("hero", out PlayerData player);

Call Save if you want to add new or overwrite data to the profile in memory

using Pancake;

PlayerData player = new PlayerData();
Data.Save<PlayerData>(player);

To check if the profile has a key, you can use the HasKey function

using Pancake;

bool exist = Data.HasKey("hero");

To delete the key from the profile you can use the function DeleteKey

using Pancake;

Data.DeleteKey("hero");

To delete the entire key, call DeleteAll

using Pancake;

Data.DeleteAll();

Sample

public class PlayerData : UnitData
{
    public int health;
    public int damage;
}


public class UnitData
{
    public int id;
    public string name;
}
var player = new PlayerData()
{
    id = 1, name = "Anya", health = 100, damage = 25
};

Data.Save("all-player", player);

Versioned data

In case your data class changes over each version such as adding or removing data, you need to create a binary adapter class to implement serialization/deserialization for that class. You can follow the following example for easier visualization.

using Unity.Collections.LowLevel.Unsafe.NotBurstCompatible;

[Serializable]
public class Enemy // Old data class
{
    public string name;
    public float damage;
    public int health;
}

public class EnemyBinaryAdapter : VersionedBinaryAdapterBase, IBinaryAdapter<Enemy>
{
    private const byte CURRENT_ADAPTER_VERSION = 0;

    public EnemyBinaryAdapter(byte adapterVersion)
        : base(adapterVersion)
    {
    }

    public unsafe void Serialize(in BinarySerializationContext<Enemy> context, Enemy value)
    {
        var writer = context.Writer;
        WriteAdapterVersion(writer);
        writer->AddNBC(value.name);
        writer->Add(value.damage);
        writer->Add(value.health);
    }

    public unsafe Enemy Deserialize(in BinaryDeserializationContext<Enemy> context)
    {
        var reader = context.Reader;
        byte serializedVersion = ReadAdapterVersion(reader);

        if (serializedVersion == CURRENT_ADAPTER_VERSION)
        {
            reader->ReadNextNBC(out string name);
            float damage = reader->ReadNext<float>();
            int hp = reader->ReadNext<int>();
            return new Enemy {name = name, damage = damage, health = hp};
        }

        throw new SerializationVersionException(GetVersionExceptionMessage(serializedVersion));
    }
}

In the next version the damage attribute is removed and the description attribute is added.

using Unity.Collections.LowLevel.Unsafe.NotBurstCompatible;

[Serializable]
public class Enemy
{
    public string name;
    // public float damage;
    public int health;
    public string description;
}

public class EnemyBinaryAdapter : VersionedBinaryAdapterBase, IBinaryAdapter<Enemy>
{
    private const byte CURRENT_ADAPTER_VERSION = 1;
    private const byte OLD_ADAPTER_VERSION = 0;

    public EnemyBinaryAdapter(byte adapterVersion)
        : base(adapterVersion)
    {
    }

    public unsafe void Serialize(in BinarySerializationContext<Enemy> context, Enemy value)
    {
        var writer = context.Writer;
        WriteAdapterVersion(writer);
        writer->AddNBC(value.name);
        writer->Add(value.health);
        writer->AddNBC(value.description);
    }

    public unsafe Enemy Deserialize(in BinaryDeserializationContext<Enemy> context)
    {
        var reader = context.Reader;
        byte serializedVersion = ReadAdapterVersion(reader);

        if (serializedVersion == OLD_ADAPTER_VERSION)
        {
            reader->ReadNextNBC(out string name);
            reader->ReadNext<float>(); // skip bytes for: damage 
            int hp = reader->ReadNext<int>();
            return new Enemy {name = name, health = hp};
        }

        if (serializedVersion == CURRENT_ADAPTER_VERSION)
        {
            reader->ReadNextNBC(out string name);
            int hp = reader->ReadNext<int>();
            reader->ReadNextNBC(out string description);
            return new Enemy {name = name, health = hp, description = description};
        }

        throw new SerializationVersionException(GetVersionExceptionMessage(serializedVersion));
    }
}

When you deploy a binary adapter you need to register it to the serialize/deserialize process via the command Data.SetAdapters

Data.SetAdapters(new EnemyBinaryAdapter(1));

Quick comparison on EditorMode using TestFramework

Call Data.Load<> 10000 times

  • Use serialize Newtonsoft.Json editormode_old

  • Use Unity Serialization Binary editormode_new

Below is a performance comparison table

Call Data.Load<> 1000 times

Load<int> NewtonSoft (json) Unity.Serialization (binary)
UnityEditor (window 11 core i7 10700) 195ms 2ms
LDPlayer 9 (4 core ARM64) 348ms 10ms
Samsung S10 (Android 12 ARM64) 216ms 5ms
Iphone 12 ProMax (iOS 16.3 ARM64) 72ms 1ms

Load<string> NewtonSoft (json) Unity.Serialization (binary)
UnityEditor (window 11 core i7 10700) 402ms 1ms
LDPlayer 9 (4 core ARM64) 791ms 16ms
Samsung S10 (Android 12 ARM64) 519ms 8ms
Iphone 12 ProMax (iOS 16.3 ARM64) 173ms 1ms

Load<float> NewtonSoft (json) Unity.Serialization (binary)
UnityEditor (window 11 core i7 10700) 211ms 1ms
LDPlayer 9 (4 core ARM64) 308ms 9ms
Samsung S10 (Android 12 ARM64) 183ms 5ms
Iphone 12 ProMax (iOS 16.3 ARM64) 53ms 1ms

Load<DateTime> NewtonSoft (json) Unity.Serialization (binary)
UnityEditor (window 11 core i7 10700) 348ms 13ms
LDPlayer 9 (4 core ARM64) 492ms 48ms
Samsung S10 (Android 12 ARM64) 314ms 14ms
Iphone 12 ProMax (iOS 16.3 ARM64) 95ms 4ms

Load<Vector2> NewtonSoft (json) Unity.Serialization (binary)
UnityEditor (window 11 core i7 10700) 766ms 3ms
LDPlayer 9 (4 core ARM64) 2246ms 35ms
Samsung S10 (Android 12 ARM64) 1379ms 21ms
Iphone 12 ProMax (iOS 16.3 ARM64) 309ms 7ms

Load<Vector3> NewtonSoft (json) Unity.Serialization (binary)
UnityEditor (window 11 core i7 10700) 791ms 3ms
LDPlayer 9 (4 core ARM64) 2727ms 36ms
Samsung S10 (Android 12 ARM64) 1578ms 19ms
Iphone 12 ProMax (iOS 16.3 ARM64) 357ms 9ms
Clone this wiki locally