TransWikia.com

File saved to IndexedDB lost unless we change scenes

Game Development Asked on November 2, 2021

Our game includes an in-game level editor. When the user saves a custom level, the level is serialized and written to a file:

public void SaveFile<T>(T obj, string path) {
    if (!typeof(T).IsSerializable) {
        throw new ArgumentException("Tried to save non-serializable type " + typeof(T).Name);
    }

    FileStream fs = null;
    try {
        fs = new FileStream(path, FileMode.Create);
        DataContractJsonSerializer serializer = GetSerializer();
        serializer.WriteObject(fs, obj);
    } catch (Exception ex) {
        Debug.LogException(ex);
    } finally {
        if (fs != null) fs.Close();
    }
}

Serialized files can be loaded like this:

public T LoadFile<T>(string path) {
    if (!typeof(T).IsSerializable) {
        throw new ArgumentException("Tried to deserialize non-serializable type " + typeof(T).Name);
    }

    if (!File.Exists(path)) return default(T);

    FileStream fs = null;
    T result;
    try {
        fs = new FileStream(path, FileMode.Open);
        DataContractJsonSerializer serializer = GetSerializer();
        var result = (T)serializer.ReadObject(fs);
    } catch (Exception ex) {
        Debug.LogException(ex);
        throw ex;
    } finally {
        if (fs != null) fs.Close();
    }

    return result;
}

When displaying a "Load" menu, we enumerate the saved files like this:

public List<T> LoadFiles() {
    if (!Directory.Exists(path)) {
        Debug.Log("No save files found");
        return new List<T>();
    }

    var files = Directory.EnumerateFiles(path);
    Debug.Log("Found " + files.Count() + " files");

    List<T> result = new List<T>(files.Count());
    foreach (string file in files) {
        Debug.Log(file);
        T obj = LoadFile<T>(file);
        if (obj == null) Debug.Log("File at " + file + " loaded null");
        if (obj != null) result.Add(obj);
    }
    return result;
}

I have omitted a lot of code for brevity, but what is shown above should cover the important parts.

In the Editor, everything works perfectly. However, in WebGL builds (where files are saved to/loaded from the browser IndexedDB), there is a strange quirk: the file doesn’t remain in IndexedDB unless the user changes to a different scene after saving.

Scenario 1:

  1. In the level Editor, Bob presses "Save"
  2. The file is saved, and the browser console shows the log entry File saved to /idbfs/abc123/CustomLevels//FileName
  3. Bob refreshes the webpage
  4. Bob clicks "Load"
  5. The game searches IndexedDB but does not find the file saved in step 2

Scenario 2:

  1. In the level Editor, Alice presses "Save"
  2. The file is saved, and the browser console shows the log entry File saved to /idbfs/abc123/CustomLevels//FileName
  3. Alice returns to the main menu scene
  4. Alice refreshes the webpage
  5. Alice clicks "Load"
  6. The game searches IndexedDB and does find the file saved in step 2

The browser console log suggests that after Bob refreshes the page in Scenario 1, the file no longer exists. If this was the only scenario that Bob had saved, we see the message "No save files found". If Bob had previously saved other scenarios and used the workaround of returning to the main menu scene, those previously saved files will appear in the console and save list, but the most recent file is missing.

Why would the save file disappear from the IndexedDB if the user leaves the webpage without changing scenes, but remain in the IndexedDB if the user returns to the main menu scene before leaving the webpage?

One Answer

D'oh! This is covered in the WebGL troubleshooting documentation (which did not come up in my web search results for 'Unity IndexedDB').

Unity does not flush changes to IndexedDB immediately when you save a file. They don't explain when they do a flush, but clearly a scene change is one event that triggers a flush.

To immediately flush the changes, we have to run a little bit of JavaScript. Create a new .jslib file in Assets/Plugins/ and add the following code:

mergeInto(LibraryManager.library, {
    //flush our file changes to IndexedDB
    SyncDB: function () {
        FS.syncfs(false, function (err) {
           if (err) console.log("syncfs error: " + err);
        });
    }
});

Add a reference to the external JS function in our C# code:

    #if UNITY_WEBGL && !UNITY_EDITOR
        [DllImport("__Internal")]
        private static extern void SyncDB();
    #endif

Call the external function after saving:

    #if UNITY_WEBGL && !UNITY_EDITOR
        //flush our changes to IndexedDB
        SyncDB();
    #endif

Answered by Kevin on November 2, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP