Search Unity

Resolved Saving in progress

Discussion in 'Scripting' started by mikeohc, Mar 30, 2023.

  1. mikeohc

    mikeohc

    Joined:
    Jul 1, 2020
    Posts:
    215
    I'm wondering how I can achieve showing the player that saving is valid. For example, a lot of games have a save in progress icon in the corner to show that the game is either auto saving or the save action is in progress.

    Most of the tutorial or system I found never touch upon this subject. I've thought about just using a coroutine to wait a few seconds with the icon enabled then disable it afterwards so the player gets a feedback that saving happened.

    I found this from EasySave.
    Have all the saving in progress in every game been a lie?
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,856
    Most games I've played just have an indicator that the game is in the process of saving, rather than a progress bar, which is much easier to implement.

    I believe there is ways to tally 'progress' in asynchronous code, though nothing I've ever actually looked into.

    But yes, in many cases of game design, outright lying to the player gets you a better response than being honest. Most games are lying, or at least, manipulating the information it feeds to or tells the player to make them feel good about themselves.
     
  3. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,990
    I wouldn't call it lying. One issue with "realistic" progress bars is, if the process is too quick, it would only be a brief flash. I remember playing Half-life back in 1999 where the loading of a new section took several seconds. When you play on modern machines the loading only takes a tiny fraction of a second and only causes a very short hiccup. The game shows the text "loading" in the middle of the screen, but on modern hardware you barely see it because it's just too quick.

    So a saving indicator is shown for a certain amount of time so the user actually can notice it. Most saving routines complete in a single frame since gathering gamestates over multiple frames would lead to countless issues unless you effectively freeze the game during that time. Of course it's possible that an asynchronous saving routine would gather relevant data synchronously in memory and then do the writing to disk asynchronously in the background. Though we generally only talk about fraction of seconds here, especially when writing to SSDs. If an HDD is used it's possible that the disk is currently powered down and spinning up the platters could take a few seconds before the file can be written to disk.

    So I would not say that saving or loading indicators are always a "lie". But it is common to stretch the time it's displayed to give better feedback to the user. Autosaving should help the user so he knows / recognises a save spot. If the indicator only flashes for 1 frame most would miss it completely.
     
    StarBornMoonBeam and mikeohc like this.
  4. mikeohc

    mikeohc

    Joined:
    Jul 1, 2020
    Posts:
    215
    So I shouldn't worry about the data taking more than say 2 seconds to save? The coroutine I have just waits 2 seconds to toggle a UI icon for players to see.
     
  5. Zalosath

    Zalosath

    Joined:
    Sep 13, 2014
    Posts:
    687
    If the save takes longer than 2 seconds then this could lead to unfinished saves if the user instant quits the game. I'd have your progress bar stop at 90%, then fully fill when the save is actually complete.
     
  6. mikeohc

    mikeohc

    Joined:
    Jul 1, 2020
    Posts:
    215
    I guess that's my main question in this post. How would I know if all the data has finished being written?

    Currently I have a save/load system that takes in an index from a list using UI buttons. Then with the index I execute any of the following.

    saveloadscreen.JPG

    Side note - why do some games force you to close all UI when you press save? Is there a reason for that?
     
  7. Zalosath

    Zalosath

    Joined:
    Sep 13, 2014
    Posts:
    687
    Implementation for knowing when a save is finished will differ depending on your saving process, so it'll depend.

    I can't say for certain why games require you to close UI, but my educated guess would be that the game doesn't want you to make any changes while the save is happening, this probably prevents things like duplication glitches for inventories and other unintended bugs.
     
    mikeohc likes this.
  8. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    You may not notice a saving prompt, but you will likely notice the loading prompt. The more data you save the more time it could take depending on where the data is pulled from, and how it is sorted before saving.
    You could simulate this delay, or have a smooth place to put this delay, by preparing for that long frame interval, or you could break your save compile over a number of frames, and send a debug for each piece such as stage 1 saving map, stage 2 saving character position, stage 3 saving character rotation, there’s no need really as it’s more useful on a load to package it over multiple frames so you can pin point each loading stage, and never cause that long frame. It’s an art really. And it depends what you want to do. If you want to show your players all that happens and never break frame rate then it can give a nice finish to the system to have.

    When you create a file display system as you have done in your post, after you save a file you need to refresh your load basket so it finds and lists the new save file. Often this will involve closing the UI windows and reopening them to trigger a new list of directory.
     
    mikeohc likes this.
  9. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    Sometimes the save might be real time. After an enemy attack or change to player health for example, a slower frame based save compiler would help keep frame rate during this process. In an options window, where I click save there’s no need to worry for the long frame unless it’s a design thing
     
    mikeohc likes this.
  10. mikeohc

    mikeohc

    Joined:
    Jul 1, 2020
    Posts:
    215
    I'm using JSON on the back to deserialize and saving it that way. Since it's local singleplayer, I don't care too much other than making sure the save is valid before player quits. So I need a way to ensure the front end save icon doesn't disable prior to the back end writing to data has been completed.

    The approach I'm thinking about is try/catch to open the exact file I'm saving/overwriting from here. Is this a valid method or am I shooting myself in the foot before I know it.
    Code (CSharp):
    1. try
    2. {
    3.     using (Stream stream = new FileStream("File.txt"))
    4.     {
    5.     }
    6. }
    7. catch
    8. {
    9.     // Check if file is in use (or whatever else went wrong), and do any user communication that you need to
    10. }
    Does Unity have built in support for async write to file/savedata? I know AsyncOperation can be used for loading scenes...

    Thanks! I think those make sense. The refresh I have as a method I can call anywhere, e.g. after the saving is executed. But I guess once the data becomes larger, it would be wise to close the UI so the player can't overwrite the exact file that's being saved.
     
  11. karderos

    karderos

    Joined:
    Mar 28, 2023
    Posts:
    376
    Here is my thoughts on save, in my game I have a ton of data to save, lets say that I need to save X amount of data and in my game X is a big number. If I just saved it all in one frame it would hang the game to the point that windows would show you "not responding". So I split it with a coroutine so it saves step by step. Now I need 100 000 steps to save the game but I will never know how much time is needed for each step. I can make a progress bar that just counts % by checking the current step and how far it is from 100 000 but its not possible to know how long it will take, maybe it will get to 90% in 5 secs and then for the last 10% it might take 30sec. At least the bar is always moving a little so you dont think the game has crashed.

    To answer your question I dont think that saving bars are 100% a lie, just that its not useful to estimate how much time is needed for a save, same as loading, its just to let the player know that the game has not crashed.
     
  12. mikeohc

    mikeohc

    Joined:
    Jul 1, 2020
    Posts:
    215
    How are you accomplishing that if you don't mind me asking. I don't have experience writing async saving.

    I think there's a misunderstanding. I don't mean to use a saving bar. I meant to reference EasySave to indicate that the time a save icon is shown on screen is arbitrary.

    What I meant is I need to ensure the save icon in the top right lasts longer than the saving happening back end. Placing a hardcoded wait time doesn't seem to make sense, so I wanted to know how if there's a way to ensure the savefile is finished being written before I disable the icon.

    I can add a minimum of 2 seconds before disabling in the case that modern computer saves instantly like Bunny83 mentioned.

    savingicon.JPG
     
  13. Zalosath

    Zalosath

    Joined:
    Sep 13, 2014
    Posts:
    687
    That'll definitely work, but you'll have to keep checking it every X amount of time to find when it's finished. I'm not sure if there is another way to do it.
     
    mikeohc likes this.
  14. karderos

    karderos

    Joined:
    Mar 28, 2023
    Posts:
    376
    For the async question, I don't know how to do it either, I don't do async, just split it with a coroutine and I lock the game while saving ( like a saving screen/ loading screen )

    For knowing when you are done saving it should be quite easy, if you showed how you are saving I could probably tell you an easy way to know, for most saves you just place a piece of code at the end of your saving code and if that piece of code is reached it means the save is complete. The piece of code could even be displaying "Save Complete", which literally cannot be displayed unless the save has completed.

    But maybe you are saving in a way I dont understand if you are constantly saving, its a different kind of save than what I am envisioning.
     
    mikeohc likes this.
  15. mikeohc

    mikeohc

    Joined:
    Jul 1, 2020
    Posts:
    215
    I would think if using a coroutine this can be done by yielding until the save is written? I hadn't thought I was using synchronous saving until someone pointed out. So yes, I am aware of this method.

    I guess I was thinking of the savedata writing in the background while the game still runs but the save system should know when it's done asynchronously like loading additive scenes.

    I'm just using Bayat Game's Save Game Free. The backend is just built in Unity deserialization things. In this case I'm using JSON. Players only save when they go into save/load screen. Auto save only happen on quitting the game.
     
  16. karderos

    karderos

    Joined:
    Mar 28, 2023
    Posts:
    376
    i was checking the source code of that asset and on cursory look it seems that it saves instantly.

    Code (CSharp):
    1. public static void Save<T>(string identifier, T obj, bool encode, string password, ISaveGameSerializer serializer, ISaveGameEncoder encoder, Encoding encoding, SaveGamePath path)
    2.         {
    3.             if (string.IsNullOrEmpty(identifier))
    4.             {
    5.                 throw new System.ArgumentNullException("identifier");
    6.             }
    7.             if (OnSaving != null)
    8.             {
    9.                 OnSaving(
    10.                     obj,
    11.                     identifier,
    12.                     encode,
    13.                     password,
    14.                     serializer,
    15.                     encoder,
    16.                     encoding,
    17.                     path);
    18.             }
    19.             if (serializer == null)
    20.             {
    21.                 serializer = SaveGame.Serializer;
    22.             }
    23.             if (encoding == null)
    24.             {
    25.                 encoding = SaveGame.DefaultEncoding;
    26.             }
    27.             string filePath = "";
    28.             if (!IsFilePath(identifier))
    29.             {
    30.                 switch (path)
    31.                 {
    32.                     default:
    33.                     case SaveGamePath.PersistentDataPath:
    34.                         filePath = string.Format("{0}/{1}", Application.persistentDataPath, identifier);
    35.                         break;
    36.                     case SaveGamePath.DataPath:
    37.                         filePath = string.Format("{0}/{1}", Application.dataPath, identifier);
    38.                         break;
    39.                 }
    40.             }
    41.             else
    42.             {
    43.                 filePath = identifier;
    44.             }
    45.             if (obj == null)
    46.             {
    47.                 obj = default(T);
    48.             }
    49.             Stream stream = null;
    50.             Directory.CreateDirectory(Path.GetDirectoryName(filePath));
    51.             if (encode)
    52.             {
    53.                 stream = new MemoryStream();
    54.             }
    55.             else
    56.             {
    57.                 if (!usePlayerPrefs)
    58.                 {
    59.                     stream = File.Create(filePath);
    60.                 }
    61.                 else
    62.                 {
    63.                     stream = new MemoryStream();
    64.                 }
    65.             }
    66.             serializer.Serialize(obj, stream, encoding);
    67.             if (encode)
    68.             {
    69.                 string data = System.Convert.ToBase64String(((MemoryStream)stream).ToArray());
    70.                 string encoded = encoder.Encode(data, password);
    71.                 if (!usePlayerPrefs)
    72.                 {
    73.                     File.WriteAllText(filePath, encoded, encoding);
    74.                 }
    75.                 else
    76.                 {
    77.                     PlayerPrefs.SetString(filePath, encoded);
    78.                     PlayerPrefs.Save();
    79.                 }
    80.             }
    81.             else if (usePlayerPrefs)
    82.             {
    83.                 string data = encoding.GetString(((MemoryStream)stream).ToArray());
    84.                 PlayerPrefs.SetString(filePath, data);
    85.                 PlayerPrefs.Save();
    86.             }
    87.             stream.Dispose();
    88.             if (SaveCallback != null)
    89.             {
    90.                 SaveCallback.Invoke(
    91.                     obj,
    92.                     identifier,
    93.                     encode,
    94.                     password,
    95.                     serializer,
    96.                     encoder,
    97.                     encoding,
    98.                     path);
    99.             }
    100.             if (OnSaved != null)
    101.             {
    102.                 OnSaved(
    103.                     obj,
    104.                     identifier,
    105.                     encode,
    106.                     password,
    107.                     serializer,
    108.                     encoder,
    109.                     encoding,
    110.                     path);
    111.             }
    112.         }
    have you tried doing:

    Code (CSharp):
    1. SaveGame.Save<int> ( "x", x);
    2. SaveGame.Save<int> ( "y", y);
    3. ...etc
    4. SaveGame.Save<int> ( "z", z);
    5.  
    6. try
    7. {
    8.     using (Stream stream = new FileStream("File.txt"))
    9.     {
    10. Debug.Log("Save Complete");
    11.     }
    12. }
    13. catch
    14. {
    15.  Debug.Log("save was not instant as expected");
    16. }
    17.  
    18.  
    I dont know the asset but are you putting a component on each object that you want to save? Anyway from what I see in this asset, the only way that you can prevent a save or load from completing is by computer crashing or shutting down the game using the task manager.
     
    Last edited: Mar 31, 2023
    mikeohc likes this.
  17. mikeohc

    mikeohc

    Joined:
    Jul 1, 2020
    Posts:
    215
    Nope, I have a Playerdata class to save all the info I need. I parse the info to the DataManager class, which updates all relevant objects to the corresponding save.

    So that's the idea I have with checking for if saving is done. The next goal is to do this asynchronously in the background. This guide here seems to point me in the direction of executing async events.
     
  18. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    Do you need to? Async introduces at least a 100x chance of bugs and inconsistencies, if not even maybe 1000x, especially in edge-casey begin / end life code.

    If you can ransack all the data you need to save without causing a noticeable hiccup, just DO it, then you can be clever and serialize the data and write the JSON to disk in your background thread, if it honestly takes that long.

    Otherwise, just write it instantly and throw up a glowy "SAVING..." message for a few seconds, but meanwhile all the actual work is long done and over with.

    You can always "journal" between two savegame files... current and previous.

    When you save:

    - delete previous
    - rename current to previous
    - start writing to previous

    When you load:

    - try to load current
    - if current is invalid, load previous and immediately save it over current
     
    mikeohc likes this.
  19. karderos

    karderos

    Joined:
    Mar 28, 2023
    Posts:
    376
    do you have any reason to do this async? Do you want to avoid the game stopping for you to save? Is the current save not executing fast enough? Doing things async is difficult and unstable terrain which you should only walk if you have a good reason to.
     
    mikeohc likes this.
  20. mikeohc

    mikeohc

    Joined:
    Jul 1, 2020
    Posts:
    215
    I'm falling into the trap of premature optimization. Thanks for getting me back on track!