Save Load Docs

Overview

The Runtime Save & Load (RTSL) subsystem is required for saving and managing scenes, assets and projects at runtime and consists of three main parts:

  • Persistent Classes - this part allows you to choose what to serialize and generate persistent classes for serialization.
  • Asset Libraries - this part allows you to create and manage assets, as well as provide information to the RTSL to identify these assets.
  • Project - this part provides api to interact with RTSL.

Note

RTSL use protobuf.net for serialization.

Screenshot

Getting Started

After importing RTSL you will see the configuration window:

Screenshot

After clicking "Build All", several folders will be created under /Battlehub/RTSL_Data

Screenshot

Note

RTSLTypeModel.dll contains protobuf-net type model. Due to unity scripting restrictions, runtime type model need to be pre-complied before using at runtime.

  1. Create new scene
  2. Drag and Drop Assets/Battlehub/RTDemoGame/Prefabs/Game.prefab to hierarchy.

    Screenshot

  3. Click Tools->Runtime SaveLoad->Libraries->Collect Scene Dependencies

    Screenshot

  4. Create Game Object, then add Assets/Battlehub/RTSL/Interface/Project and Assets/Battlehub/RTSL/Interface/RTSLIgnore components.

    Screenshot

  5. Create RTSLTest script.

  6. Hit play.
  7. Save scene using 'M' key.
  8. Load scene using 'L' key.

using System.Collections;

using UnityEngine;
using UnityEngine.SceneManagement;

using Battlehub.RTCommon;
using Battlehub.RTSL.Interface;


public class RTSLTest : MonoBehaviour
{
    IProject m_project;

    IEnumerator Start()
    {
        m_project = IOC.Resolve<IProject>();

        yield return m_project.OpenProject("My Project");
        yield return m_project.CreateFolder("Scenes/Demo");           
    }

    IEnumerator SaveScene()
    {
        ProjectAsyncOperation ao = m_project.Save("Scenes/Demo/Scene", SceneManager.GetActiveScene());
        yield return ao;

        if(ao.Error.HasError)
        {
            Debug.LogError(ao.Error.ToString());
        }
    }

    IEnumerator LoadScene()
    {
        ProjectAsyncOperation ao = m_project.Load<Scene>("Scenes/Demo/Scene");
        yield return ao;

        if (ao.Error.HasError)
        {
            Debug.LogError(ao.Error.ToString());
        }
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.M))
        {
            StartCoroutine(SaveScene());
        }

        if (Input.GetKeyDown(KeyCode.L))
        {   
            if (m_project.Exist<Scene>("Scenes/Demo/Scene"))
            {
                StartCoroutine(LoadScene());
            }
        }
    }
}

Saved scene can be found in PersistentDataPath/My Project/Assets/Scenes/Demo folder.

Note

Only few persistent classes enabled and could be saved by default. Use Persistent Classes Editor Window to enable more.

Note

Demo scene can be found in Assets/Battlehub/RTSL/Demo folder.

Persistent Classes

Persistent Class is a class derived from Persistent Surrogate and having the [ProtoContract] attribute. The main purpose of the persistent classes is to give you full control over what and how to save, without having to use reflection and without writing a lot of code.

To open persistent classes editor, click Tools->Runtime SaveLoad->Persistent Classes->Edit.

Screenshot

Here is the persistent classes editor window:

Screenshot

This window allow you:

  • Search for types you want to save.
  • Select properties you want to save.
  • Generate C# code of persistent classes.

After clicking the "Create Persistent Classes" button the following will occur:

  1. Persistent classes will be created in Assets/Battlehub/RTSL_Data/Scripts/PersistentClasses.

    Screenshot

  2. The editor window state will be saved in Assets/Battlehub/RTSL_Data/Mappings/Editor.

    Screenshot

  3. Custom Implementation will be created in .../RTSL_Data/Scripts/CustomImplementation.

    Screenshot

Note

To ensure forward compatibility of saved files, never delete the ClassMappingStorage and SurrogatesMappingsStorage prefabs !!!

How To: Create Custom Persistent Class

In some cases, it is not possible to get all the data to be saved using public properties and fields. For example, data can only be obtained using the methods. If you have to write your own code to save and load data, follow these steps:

  1. Open Persistent Classes Editor.
  2. Find and select required type.
  3. Select "Custom Implementation"

    Screenshot

  4. Click Create Persistent Classes button.

  5. Click Edit Custom Implementation button.

    Screenshot

  6. You should see following:

#if !RTSL_MAINTENANCE
using Battlehub.RTSL;
namespace UnityEngine.Battlehub.SL2
{
    [CustomImplementation]
    public partial class PersistentJoint
    {
        /*
        public override void ReadFrom(object obj)
        {
            base.ReadFrom(obj);
        }

        public override object WriteTo(object obj)
        {
            return base.WriteTo(obj);
        }

        public override void GetDeps(GetDepsContext context)
        {
            base.GetDeps(context);
        }

        public override void GetDepsFrom(object obj, GetDepsFromContext context)
        {
            base.GetDepsFrom(obj, context);
        }
        */
    }
}
#endif

There are four methods that can be implemented:

  • void ReadFrom(object obj) - invoked before serialization. Read the data from obj and save it to the fields of the persistent object.
  • object WriteTo(object obj) - invoked after deserialization. Write stored data to obj.
  • void GetDeps(GetDepsContext context) - return identifiers of dependencies.
  • void GetDepsFrom(object obj, GetDepsFromContext context) - get dependencies from obj.

Implementation of PersistentJoint may look like this:


#if !RTSL_MAINTENANCE
using Battlehub.RTSL;
namespace UnityEngine.Battlehub.SL2
{
    [CustomImplementation]
    public partial class PersistentJoint
    {
        [ProtoBuf.ProtoMember(1)]
        private long m_connectedBody;

        [ProtoBuf.ProtoMember(2)]
        private PersistentVector3 m_anchor;

        //..... more fields

        public override void ReadFrom(object obj)
        {
            base.ReadFrom(obj);

            Joint joint = (Joint)obj;
            if(joint == null)
            {
                return;
            }

            m_connectedBody = ToID(joint.connectedBody);
            m_anchor = joint.anchor;

            //... read other fields
        }

        public override object WriteTo(object obj)
        {
            obj = base.WriteTo(obj);

            Joint joint = (Joint)obj;
            if (joint == null)
            {
                return obj;
            }

            joint.connectedBody = FromID<Rigidbody>(m_connectedBody);
            joint.anchor = m_anchor;

            //... write other fields

            return joint;
        }

        public override void GetDeps(GetDepsContext context)
        {
            base.GetDeps(context);

            AddDep(m_connectedBody, context);

            //... get other dependencies
        }

        public override void GetDepsFrom(object obj, GetDepsFromContext context)
        {
            base.GetDepsFrom(obj, context);

            Joint joint = (Joint)obj;
            if (joint == null)
            {
                return;
            }

            AddDep(joint.connectedBody, context);

            //... get other dependencies
        }
    }
}
#endif


Note

Click Tools-> Runtime SaveLoad-> Build All each time you have finished making changes to persistent classes and are ready to build the application.

Asset Library

Game objects in any scene refer assets such as materials, meshes, textures, etc. Identifiers of these assets obtained using GetInstanceID() method do not remain constant and can change.
In contrast, Asset libraries are used to store unique asset identifiers that never change.

There are two special asset libraries:

  • Built-in asset library - contains Unity build-in assets.
  • Scene asset library - contains assets referenced by scene Game Objects.

The former library created automatically by clicking Tools->Runtime SaveLoad->Libraries->Update Built-In Asset Library
The latter created by opening scene and clicking Tools->Runtime SaveLoad->Libraries->Collect Scene Dependencies

Screenshot

Note

Scene asset libraries are referenced using the scene name. This means that scenes must have unique names.

Note

Click Tools->Runtime SaveLoad->Libraries->Collect Scene Dependencies each time you have finished making changes to scene and are ready to run the application.

How To: Create Asset Library

If the built-in asset library and scene dependency library are not sufficient, and the new resources must be Resource.Loaded at runtime, you can create a new asset library by clicking
Create-> Runtime Asset Library in the context menu.

Screenshot

Note

An asset library must be created inside the Resources folder.

Use drag & drop to add assets to asset library

Screenshot

Note

One asset library can contain no more than 65535 assets.

If you change one or more prefabs, you will be asked to synchronize the assets library. Do it by clicking Synchronize button.

Screenshot

Project Item & Asset Item

Project Items are used to create tree structures representing the project tree.

    [ProtoContract]
    [ProtoInclude(1, typeof(AssetItem))]
    public class ProjectItem
    {
        [ProtoMember(2)]
        public long ItemID;

        public string Name;
        public string Ext;

        public ProjectItem Parent;
        public List<ProjectItem> Children;

        // ...

Asset Items are meta representations of assets in a project. They are stored in *.rtmeta files.


    [ProtoContract]
    public class AssetItem : ProjectItem
    {
        public event EventHandler PreviewDataChanged;

        [ProtoMember(1)]
        public Guid TypeGuid;

        [ProtoMember(2)]
        public PrefabPart[] Parts;

        [ProtoMember(3)]
        public long[] Dependencies;

        private Preview m_preview;
        public Preview Preview
        {
            get { return m_preview; }
            set
            {
                if (m_preview != value)
                {
                    m_preview = value;
                    if (PreviewDataChanged != null)
                    {
                        PreviewDataChanged(this, EventArgs.Empty);
                    }
                }
            }
        }

        public override bool IsFolder
        {
            get { return false; }
        }

    }

Project

IProject is the main interface of the RTSL library. Here is how to access it:


using Battlehub.RTCommon;
using Battlehub.RTSL.Interface;

void Awake()
{
    IProject project = IOC.Resolve<IProject>();
}


Open project:


using System.Collections;
using Battlehub.RTCommon;
using Battlehub.RTSL.Interface;

IEnumerator Start()
{
    IProject project = IOC.Resolve<IProject>();
    yield return project.OpenProject("My Project");
}


Close project:

m_project.CloseProject();


Delete project:

yield return project.DeleteProject("My Project");


Create folder:

yield return project.CreateFolder("My Scenes");   


Delete folder:

yield return project.DeleteFolder("My Scenes");


Save scene:


using System.Collections;

using UnityEngine;
using UnityEngine.SceneManagement;

using Battlehub.RTCommon;
using Battlehub.RTSL.Interface;

IEnumerator Start()
{
    //...

    ProjectAsyncOperation ao = project.Save("My Scenes/Scene 1", SceneManager.GetActiveScene());
    yield return ao;

    if (ao.Error.HasError)
    {
        Debug.LogError(ao.Error.ToString());
    }
}


Load scene:


using System.Collections;

using UnityEngine;
using UnityEngine.SceneManagement;

using Battlehub.RTCommon;
using Battlehub.RTSL.Interface;

IEnumerator Start()
{
    //...

    ProjectAsyncOperation ao = project.Load<Scene>("My Scenes/Scene 1");
    yield return ao;

    if (ao.Error.HasError)
    {
        Debug.LogError(ao.Error.ToString());
    }
}


Delete scene:

yield return project.Delete<Scene>("My Scenes/Scene 1");


Find objects of type:

foreach(string scene in project.Find<Scene>("Scene 1"))
{
    Debug.Log(scene);
}


Create Prefab:

GameObject primitive = GameObject.CreatePrimitive(PrimitiveType.Capsule);
yield return project.Save("Capsule", primitive);
Destroy(primitive);


Load and instantiate Prefab:

ProjectAsyncOperation<Object[]> ao = project.Load<GameObject>("Capsule");
yield return ao;

if(!ao.Error.HasError)
{
    Instantiate(ao.Result[0]);
}


Import all assets from asset bundle:


//get names of asset bundles from Assets/StreamingAssets folder.
ProjectAsyncOperation<string[]> ao = project.GetAssetBundles();
yield return ao;

//load ImportItems from first asset bundle
ProjectAsyncOperation<ProjectItem> loadAo = project.LoadImportItems(ao.Result[0], false);
yield return loadAo;


if (!loadAo.Error.HasError)
{
    //create previews here...
    //then unload asset bundle assets
    project.UnloadImportItems(loadAo.Result);

    //import all
    yield return project.Import(loadAo.Result.Flatten(true).OfType<ImportItem>().ToArray());
}

//log all asset items in project
foreach(string path in project.Find<object>(string.Empty, true))
{
    Debug.Log(path);
}