Search Unity

WebGL save / load issue

Discussion in 'Web' started by drmop, Aug 24, 2017.

  1. drmop

    drmop

    Joined:
    Aug 26, 2014
    Posts:
    21
    I have a serlialisable class that I use to persist my data between sessions which works fine on mobile / desktop but does not work when with WebGL (Unity 2017.1)

    I'm using BinaryFormatter to format the data then serialise it with a FileStream.

    If I refresh the browser and load the data back in the data is not there, in fact the file isn't even there.

    I've dug around for days and found a couple of threads that mention calling a bit of Javascript to force the file to sync FS.fsync().but surely that would be taken care of on the Unity side? In fact when I call that Javascript I am given the following warning:

    "warning: 2 FS.syncfs operations in flight at once, probably just doing extra work".

    Is file persistence using File IO broken in WebGL or just not supported?

    Forgot to mention, my game auto saves every 15 seconds.
     
    Last edited: Aug 24, 2017
  2. kou_yeung

    kou_yeung

    Joined:
    Mar 24, 2016
    Posts:
    28
    You can try to use PlayerPrefs to save and load data at indexedDB.
     
  3. drmop

    drmop

    Joined:
    Aug 26, 2014
    Posts:
    21
    Thanks for the reply but my data is quite complex, using PlayerPrefs would be difficult
     
  4. kou_yeung

    kou_yeung

    Joined:
    Mar 24, 2016
    Posts:
    28
    Last edited: Aug 25, 2017
    james_feed and UlalaTopola like this.
  5. ansrsource

    ansrsource

    Joined:
    Jul 9, 2018
    Posts:
    1
    Im working on the same method, and using Application.persistantDataPath , but it keeps on erasing data once the webpage is closed.
    Any ideas? the data is quite complex to save in a player prefab. the other thing i can think of is save the data as a json string, and load from a json string using playerprefabs, though i wonder if the string would go out of limits.
     
  6. kou-yeung

    kou-yeung

    Joined:
    Sep 5, 2016
    Posts:
    30
  7. astracat111

    astracat111

    Joined:
    Sep 21, 2016
    Posts:
    725
    I'm confused about this. Don't you use www when using webgl to load files?

    I'm having this trouble as well, I've been using the following code to load an xml file:

    Code (CSharp):
    1. XmlSerializer xmlSerializer = new XmlSerializer(typeof(MG_GameData));
    2.         FileStream fileStream = new FileStream (MG_Data.DataPath + "/" + "MG_Data.data", FileMode.Open);
    3.         MG_Data.mg_GameData = xmlSerializer.Deserialize (fileStream) as MG_GameData;
    4.         fileStream.Close ();
    I'm not sure how I would convert this...I already use a www class to load audio files...
     
  8. kou-yeung

    kou-yeung

    Joined:
    Sep 5, 2016
    Posts:
    30
    No. you cann't load files from local storage.

    here have two situation.

    first one. Load a XML file from resource server and deserialize it to use.
    sample .
    Code (CSharp):
    1.     /// <summary>
    2.     /// Load obj from Resource Server
    3.     /// </summary>
    4.     void LoadFromWWW<T>(string url, Action<T> cb)
    5.     {
    6.         // redirect to coroutine
    7.         StartCoroutine(InternalLoadFromWWW(url, cb));
    8.     }
    9.  
    10.     IEnumerator InternalLoadFromWWW<T>(string url, Action<T> cb)
    11.     {
    12.         // download xml data from resource server.
    13.         var www = new WWW(url);
    14.         yield return www;
    15.  
    16.         if (string.IsNullOrEmpty(www.error))
    17.         {
    18.             var xml = www.text;
    19.             using (var sr = new StringReader(xml))
    20.             {
    21.                 using (var xw = XmlReader.Create(sr))
    22.                 {
    23.                     var serializer = new XmlSerializer(typeof(T));
    24.                     cb((T)serializer.Deserialize(xw));
    25.                 }
    26.             }
    27.         }
    28.         else
    29.         {
    30.             if (cb != null) cb(default(T));
    31.         }
    32.     }
    the next situation. Serialize a class and Save it. ( eg. user configs )
    Code (CSharp):
    1.  
    2.     /// <summary>
    3.     /// Save obj to PlayerPrefs ( in WebGL it will be save to IndexedDB
    4.     /// </summary>
    5.     void Save<T>(string filename, T obj)
    6.     {
    7.         using (var sw = new StringWriter())
    8.         {
    9.             using (var xw = XmlWriter.Create(sw))
    10.             {
    11.                 var serializer = new XmlSerializer(typeof(T));
    12.                 serializer.Serialize(xw, obj);
    13.             }
    14.             PlayerPrefs.SetString(filename, sw.ToString());
    15.             PlayerPrefs.Save();
    16.         }
    17.     }
    18.     /// <summary>
    19.     /// Load obj from PlayerPrefs ( in WebGL it will be save to IndexedDB
    20.     /// </summary>
    21.     T Load<T>(string filename)
    22.     {
    23.         if (!PlayerPrefs.HasKey(filename)) return default(T);
    24.  
    25.         var xml = PlayerPrefs.GetString(filename);
    26.         using (var sr = new StringReader(xml))
    27.         {
    28.             using (var xw = XmlReader.Create(sr))
    29.             {
    30.                 var serializer = new XmlSerializer(typeof(T));
    31.                 return (T)serializer.Deserialize(xw);
    32.             }
    33.         }
    34.     }
    35.  
     
  9. astracat111

    astracat111

    Joined:
    Sep 21, 2016
    Posts:
    725
    @kou-yeung Great stuff, and another note is that you absolutely can't use the following:

    Code (CSharp):
    1. while (!www.isDone)
    2. {
    3. }
    It states in www networking in unity's manual that you can't use this as webgl is single threaded and it ends up locking up the browser by unfocusing....it...I guess? It seems to do that with me anyway....

    The problem I'm now having with this is that I never use IEnumerators (I use state machines instead), and I'm reading in some places that IEnumerators are even having some trouble with browsers (maybe this has since been solved though).

    If I use a state machine there are a host of scripts that then become dependent on seeing where that first scripts state machine is in the scene and it creates a whole lick of a mess and the code becomes more and more overly convoluted.

    I used this for xml serialization (the memorystream part...):

    Code (CSharp):
    1. public WWW www;
    2.  
    3.     private void GotoNextLoadState()
    4.     {
    5.         loadState += 1;
    6.     }
    7.  
    8.     public void Update()
    9.     {
    10.  
    11.         switch (loadState)
    12.         {
    13.             case LoadState.Start:
    14.                 Debug.Log("On Start");
    15.                 SetDataPath_FromDataPathType();
    16.                 CreateMGGameData();
    17.                 CreateWWW();
    18.                 GotoNextLoadState();
    19.                 break;
    20.             case LoadState.Downloading:
    21.                 Debug.Log("On Downloading");
    22.                 FinishedDownloading_SoGotoNextState();
    23.                 break;
    24.             case LoadState.Load:
    25.                 Debug.Log("On Load");
    26.                 CreateStream_AndLoadIntoMGGameData();
    27.                 GotoNextLoadState();
    28.                 break;
    29.             case LoadState.Complete:
    30.                 Debug.Log("On Complete");
    31.                 Debug.Log("we can now start up mg");
    32.                 MG.MG_O.GetComponent<MG>().mg_State = MG.MG_State.Start;
    33.                 break;
    34.         }
    35.  
    36.     }
    37.  
    38.  
    39.     private void CreateStream_AndLoadIntoMGGameData()
    40.     {
    41.         Stream stream = new MemoryStream(www.bytes);
    42.         XmlSerializer xmlSerializer = new XmlSerializer(typeof(MG_GameData));
    43.         FileStream fileStream = new FileStream(MG_Data.DataPath + "MG_Data.data", FileMode.Open);
    44.         MG_Data.mg_GameData = xmlSerializer.Deserialize(fileStream) as MG_GameData;
    45.         fileStream.Close();
    46.     }
    47.  
    48.     private void CreateWWW()
    49.     {
    50.         www = new WWW(MG_Data.DataPath + "/" + "MG_Data.data");
    51.     }
    52.     private void SetDataPath_FromDataPathType()
    53.     {
    54.         if (dataPathType == DataPathType.StreamingAssetsPath)
    55.         {
    56.             DataPath = Application.streamingAssetsPath + "/Data/";
    57.         }
    58.         else if (dataPathType == DataPathType.PersistentDataPath)
    59.         {
    60.             DataPath = Application.persistentDataPath;
    61.             StreamingAssets_MoveTo_PersistentDatapath();
    62.         }
    63.     }
    64.     private void CreateMGGameData()
    65.     {
    66.         if (mg_GameData == null)
    67.         {
    68.             mg_GameData = new MG_GameData();
    69.         }
    70.     }
    71.     private void FinishedDownloading_SoGotoNextState()
    72.     {
    73.         if (www.isDone)
    74.         {
    75.             GotoNextLoadState();
    76.         }
    77.     }
     
  10. kou-yeung

    kou-yeung

    Joined:
    Sep 5, 2016
    Posts:
    30
    Last edited: Oct 29, 2018
  11. levi-almanzar-aofl

    levi-almanzar-aofl

    Joined:
    Dec 1, 2016
    Posts:
    1
    Saving a file in WebGL will only save the file to a temp location that gets wiped when leaving or reloading the page. But here is one solution, though not ideal.

    Set a value in PlayerPrefs just so that we dirty it. Then call PlayerPrefs.Save(). It will write the updated PlayerPrefs and, at the same time, call the necessary javasript under the hood in order to write these temp save files to the IndexedDB, which will make these files accessable even after reloading the page.

    Code (CSharp):
    1. private void SaveData(string fileName, string content)
    2. {
    3.      string fullPath = System.IO.Path.Combine(Application.persistentDataPath, fileName);
    4.      System.IO.File.WriteAllText(fullPath, content);
    5.      PlayerPrefs.SetString("forceSave", string.Empty);
    6.      PlayerPrefs.Save();
    7. }
     
    Last edited: Mar 5, 2021
  12. Twelv445

    Twelv445

    Joined:
    Feb 22, 2013
    Posts:
    32
    This works GREAT! Thank you. :)
     
  13. ahmetteksas

    ahmetteksas

    Joined:
    Aug 19, 2021
    Posts:
    7
    you are a real hero man !! works impressively