Local Save Selection in Unity WebGL with Easy Save 3

Discussion and help for Easy Save 3, The Complete Save Game & Data Serializer System for the Unity Engine
User avatar
Joel
Moodkie Staff
Posts: 5081
Joined: Wed Nov 07, 2012 10:32 pm

Re: Local Save Selection in Unity WebGL with Easy Save 3

Post by Joel »

Hi there,
Could you please clarify what you mean by "the speed of your storage hardware"
By your storage hardware I mean the speed of the hard drive in your computer.
We’re having some difficulty understanding where the data is stored in WebGL
From the Where Data is Stored section of the Getting Started guide:
By default save data is stored in Unity’s Application.persistentDataPath, the locations of which you can find in their documentation. You can open this path in the Editor by going to Tools > Easy Save 3 > Open Persistent Data Path.

The exception to this is WebGL, which stores data to PlayerPrefs, which in turn stores data to IndexedDB.
As such, you can find the storage limitations of WebGL on Unity's PlayerPrefs page:
https://docs.unity3d.com/6000.0/Documen ... Prefs.html

Generally if you want to save more data than WebGL's limit you would need to upload to and download from a server. We have our own functionality for using your own server here or you can integrate with another provider using the instructions here.

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
dev_PmaInc
Posts: 20
Joined: Thu Nov 30, 2023 3:20 pm

Re: Local Save Selection in Unity WebGL with Easy Save 3

Post by dev_PmaInc »

Thanks for your response!

We’ve tried using a server to store the data, but even when doing so, we still encounter a PlayerPrefsException with the following error:
Error in <> at line: PlayerPrefsException: Could not store preference value
Additionally, we attempted using the compressed version, but the application crashes before the save file has time to be compressed.

I've attached both save files: one from the Editor and one from the WebGL version.

Any insights would be greatly appreciated!

Dropbox Links for the Saves : https://www.dropbox.com/scl/fo/ntn53an7 ... ww3m7&dl=0
User avatar
Joel
Moodkie Staff
Posts: 5081
Joined: Wed Nov 07, 2012 10:32 pm

Re: Local Save Selection in Unity WebGL with Easy Save 3

Post by Joel »

Hi there,

This usually indicates that you're still saving locally, as this exception is thrown when you exceed the allowed local storage. Just to double-check, your storage location is set to Cache and you're not calling ES3.StoreCachedFile()?

If so, please could you replicate this in a new project with a simple scene and send it to me using the form at moodkie.com/repro.

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
dev_PmaInc
Posts: 20
Joined: Thu Nov 30, 2023 3:20 pm

Re: Local Save Selection in Unity WebGL with Easy Save 3

Post by dev_PmaInc »

Hi Joel,

Thank you for your response. I apologize for insisting, but we're in a rush and could really use some help to get this working. Despite our best efforts, we're struggling to properly use ES3. I believe our issue is that we're still going through the cache, which is getting overloaded because we already have the raw image stored in the file.
Here are our main questions:

How can we save data using WebGL without going through the cache (saving directly on server)?
If we must use the cache, what is the difference between "Cache" and "ES3.StoreCachedFile()"? Based on your last message, it seems we should be using "Cache", is that correct?
When we try to use compressed saving (ES3.Save("myTransform", this.transform, settings);), we can no longer open the saved file using the same load method that works fine in the editor with the uncompressed format.
Error in <3/Scripts/Readers/ES3JSONReader.cs> at line 39:

FormatException: Cannot load from file because the data is not JSON or is encrypted. If the save data is encrypted, please ensure that encryption is enabled when loading and that the same password is used.

In the settings, is there something else we need to configure? So far, we are only using:
ES3.SaveRaw(ByteResult, settings); and settings = new ES3Settings(ES3.Location.Cache);


We truly appreciate your help. We'll submit the form you sent as soon as possible, but in the meantime, if you could help us untangle this, it would be a lifesaver.

Thanks again!

here the SaveManager

Code: Select all

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SaveManager : MonoBehaviour
{
    string filename = "filename123456789";
    private ES3Settings settings;

    // Start is called before the first frame update
    void Awake()
    {
        settings = new ES3Settings(ES3.Location.Cache);
    }

    public void SaveBoutonClicked(bool showLoadingPopup = true)
    {
        StartCoroutine(SaveProcess(showLoadingPopup));
    }

    public IEnumerator SaveProcess(bool showLoadingPopup)
    {
        // Save all your data
        yield return StartCoroutine(SaveScriptMain());
        yield return StartCoroutine(SavePlan());

        // Upload the saved file to the cloud.
        CloudManager cloudMng = ScriptManager.GetScript_CloudManager();
        cloudMng.path = filename;
        //Debug.Log("cloudMng.path : " + cloudMng.path);
        yield return StartCoroutine(cloudMng.UploadFile());

        if (loadingPopup != null)
            loadingPopup.GetComponent<Popup>()?.Close();

        yield return new WaitForEndOfFrame();
    }

    public IEnumerator SaveScriptMain()
    {
        Script_Main script = ScriptManager.GetScript_Main();
        if (script == null)
            yield return null;

        // Save various settings using ES3.
        SaveES3(Main_MeasureUnit, script.MeasureUnit); //That's an Enum
        SaveES3(Main_StarwallRestriction, script.StarwallRestriction); //That's an Enum
        SaveES3(Main_GammeIndex, script.GammeIndex); //That's an Int
        SaveES3(Main_Snap, script.snapWall); //That's an Bool
        SaveES3(Main_SnapRange, script.rangeSpehereCast); //That's an float
        SaveES3(Main_WallMat, script.MaterialDefault); //That's an Enum
        SaveES3(Main_PartMat, script.PartitionsMaterials); //That's an Enum
        SaveES3(Main_PartDiv, script.PartitionsDivision); //That's an Enum
        SaveES3(Main_PartModel, script.PartitionsModel); //That's an Enum

        yield return null;
    }

    public struct SavePlans
    {
        public string PlanName;
        public string LevelName;
        public byte[] TextureBytesArray;
        public float TextureWidth;
        public float TextureHeight;
        public bool IsVisible;
        public Vector3 Position;
        public Vector3 Scale;
    }

    public class PlanData : MonoBehaviour
    {
        public string PlanName;
        public GameObject Level;
        public Texture2D Texture;
        public float TextureWidth;
        public float TextureHeight;
        public byte[] TextureBytesArray;
        public bool IsVisible;
        public Vector3 Position;
        public Vector3 Scale;
    }

    public IEnumerator SavePlan()
    {
        #region Get Scripts
        ImportPlan impPlan = ScriptManager.GetScript_ImportPlan();
        if (impPlan == null)
            yield return null;
        #endregion

        List<string> levelsWithPlan = new List<string>();

        foreach (GameObject plan in impPlan.DictioLevelsWithPlan.Values)
        {
            PlanData planData = plan.GetComponent<PlanData>();
            SavePlans savePlan = new SavePlans();

            savePlan.PlanName = plan.name;
            savePlan.LevelName = planData.Level.name;
            savePlan.TextureWidth = planData.TextureWidth;
            savePlan.TextureHeight = planData.TextureHeight;
            savePlan.IsVisible = planData.IsVisible;
            savePlan.Position = planData.Position;
            savePlan.Scale = planData.Scale;

            Texture2D tempTexture = new Texture2D((int)planData.TextureWidth, (int)planData.TextureHeight);
            tempTexture.LoadImage(planData.TextureBytesArray);
            byte[] jpgData = tempTexture.EncodeToJPG(75); // JPEG (Lossy, quality 75%)

            savePlan.TextureBytesArray = jpgData; //This is the Code that Crash, we can have multiple Texture2D 
            levelsWithPlan.Add(planData.Level.name);

            SaveES3("PlanData_" + savePlan.LevelName, savePlan);

            if (levelsWithPlan.Count > 0)
                SaveES3("ArrayLevelsWithPlan", levelsWithPlan.ToArray());
        }

        yield return new WaitForEndOfFrame();
    }

    private void SaveES3(string key, object value)
    {
        ES3.Save(key, value, filename, settings);
    }
}
CloudManager

Code: Select all

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Ricimi;

namespace Planer
{
    public class CloudManager : MonoBehaviour
    {
        [Space(10)]
        [Header("Host")]
        public string url;
        public string apiKey;
        [Space(10)]
        [Header("User")]
        public string userFTP;//MERCI DE NE PAS TOUCHER
        public string passwordFTP;//MERCI DE NE PAS TOUCHER
        //public string user;
        //public string password;
        [Space(10)]
        [Header("FilePath")]
        public string path;
        public byte[] byteResult;
        [Space(10)]
        [Header("Test")]
        public bool testCondition;
        public bool conditionMet;
        [SerializeField] [Range(0f, 1f)] private float progress;

        // Start is called before the first frame update
        void Start()
        {
            testCondition = false;
            conditionMet = false;
        }

        // Update is called once per frame
        void Update()
        {
            if (testCondition)
            {
                conditionMet = ES3.FileExists(path);
                if (conditionMet)
                    testCondition = false;
            }
        }

        public IEnumerator SaveFile()
        {
            // Create a new ES3Cloud object with the URL to our ES3.php file.
            var cloud = new ES3Cloud(url, apiKey);

            // Synchronise a local file with the cloud for a particular user.
            yield return StartCoroutine(cloud.Sync(path, userFTP, passwordFTP));

            if (cloud.isError)
                Debug.LogError(cloud.error);
        }

        public IEnumerator LoadFile(Popup script)
        {
            // Create a new ES3Cloud object with the URL to our ES3.php file.
            /*var cloud = new ES3Cloud(url, apiKey);

            //cloud.downloadProgress;
            yield return StartCoroutine(cloud.DownloadFile(path, userFTP, passwordFTP));

            if (cloud.isError)
                Debug.LogError(cloud.error);

            testCondition = true;
            yield return new WaitUntil(() => conditionMet);*/

            SaveManager saveMng = ScriptManager.GetScript_SaveManager();
            saveMng.LoadBoutonClicked(path, byteResult);

            //Between this two Method i want to do a loading ...
            script.Close();

            yield return new WaitForEndOfFrame();
        }

        public IEnumerator LoadFileGamme(Popup script, string filename)
        {
            // Create a new ES3Cloud object with the URL to our ES3.php file.
            var cloud = new ES3Cloud(url, apiKey);

            //cloud.downloadProgress;
            yield return StartCoroutine(cloud.DownloadFile(path, userFTP, passwordFTP));

            if (!cloud.isError)
            {
                testCondition = true;
                yield return new WaitUntil(() => conditionMet);

                SaveManager saveMng = ScriptManager.GetScript_SaveManager();
                saveMng.LoadGammeBoutonClicked(filename);

                //Between this two Method i want to do a loading ...
                script.Close();
            }
            else
                Debug.LogError(cloud.error);
        }

        public IEnumerator UploadFile(Popup script = null)
        {
            if (!ES3.FileExists(path))
                yield break;

            var cloud = new ES3Cloud(url, apiKey);

            //Uploading...

            // Upload the default file to the server.
            yield return StartCoroutine(cloud.UploadFile(path, userFTP, passwordFTP));
            // Now check that no errors occurred.
            if (cloud.isError)
                Debug.LogError(cloud.error);

            if (script != null)
                script.Close();
        }

        private IEnumerator CheckExistsOnCloud()
        {

            // Create a new ES3Cloud object with the URL to our ES3Cloud.php file.
            var cloud = new ES3Cloud(url, apiKey);

            /* Download the filenames of a particular user which begin with 'myFolder/' and output them to console. */
            yield return StartCoroutine(cloud.SearchFilenames(path, userFTP, passwordFTP));

            if (cloud.isError)
                Debug.LogError(cloud.error);

            foreach (var filename in cloud.filenames)
                Debug.Log(filename);

            // Download the filenames of all global files on the server which begin with 'myFolder/' and output them to console.
            yield return StartCoroutine(cloud.DownloadFilenames(userFTP, passwordFTP));

            if (cloud.isError)
                Debug.LogError(cloud.error);

            foreach (var filename in cloud.filenames)
                Debug.Log(filename);
        }
    }

}
User avatar
Joel
Moodkie Staff
Posts: 5081
Joined: Wed Nov 07, 2012 10:32 pm

Re: Local Save Selection in Unity WebGL with Easy Save 3

Post by Joel »

Hi there,
If we must use the cache, what is the difference between "Cache" and "ES3.StoreCachedFile()"?
These aren't comparable things. The Cache is a storage location (it basically stores data in RAM), and ES3.StoreCachedFile() is the method used to take data stored in the Cache and store it to a file on local storage. You can find information on the Cache in the Docs:

https://docs.moodkie.com/easy-save-3/es ... ng-caching
Based on your last message, it seems we should be using "Cache", is that correct?
That is correct.
we can no longer open the saved file using the same load method that works fine in the editor with the uncompressed format.
This will be because the save file contains uncompressed data, but you're trying to load it as if it's compressed data. You should delete the uncompressed save data. I recommend the Error Handling guide first if you encounter any errors as this is covered there:

https://docs.moodkie.com/easy-save-3/es ... -handling/
here the SaveManager
Your UploadFile and DownloadFile calls aren't using your ES3Settings object, so they're uploading and downloading from the default location (File).

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
dev_PmaInc
Posts: 20
Joined: Thu Nov 30, 2023 3:20 pm

Re: Local Save Selection in Unity WebGL with Easy Save 3

Post by dev_PmaInc »

Hi Joel,

I've sent the project as you requested. I forgot to mention that I need it to work on WebGL.

You should only need to modify the variable in the Inspector, and it should work as expected. However, I want it to save to the cloud, but something isn’t working, and I’m not sure what I’m missing.

Could you help me understand what might be going wrong?

Thanks in advance!
User avatar
Joel
Moodkie Staff
Posts: 5081
Joined: Wed Nov 07, 2012 10:32 pm

Re: Local Save Selection in Unity WebGL with Easy Save 3

Post by Joel »

dev_PmaInc wrote: Wed Feb 19, 2025 2:21 pm Hi Joel,

I've sent the project as you requested. I forgot to mention that I need it to work on WebGL.

You should only need to modify the variable in the Inspector, and it should work as expected. However, I want it to save to the cloud, but something isn’t working, and I’m not sure what I’m missing.

Could you help me understand what might be going wrong?

Thanks in advance!
Hi there,

I responded to your repro project this morning but it looks like you might not have got it, so I'll post the response here:
You appear to have Log Warnings disabled in the Easy Save settings (this is enabled by default). When you enable this, you'll receive a warning which states that your textures need to be read/write enabled.
All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
Post Reply