A number of long-time GML function for saving files work in a synchronous manner, which means they make the game wait while the load or save operation is performed. Therefore, if you use these functions in your UWP projects you will see your game freeze as the file is loaded/saved and processed by the Xbox One console.
This is something that is not ideal when submitting games to Microsoft, and to resolve the issue you need to change the way you save and load your files.
This applies to functions such as ini_open()/ini_close(), ds_map_secure_save()/ds_map_secure_load(), buffer_save()/buffer_load(), etc.
However, it does not apply to buffer_save_async(), as this will ask the system to do the save operation in a separate asynchronous thread. Which avoids the "stall" in your game and therefore any risk to your submission builds.
How do I save my data asynchronously?
All data that is to be saved should be "wrapped" in the asynchronous buffer functions, as shown in the example below:
buffer_async_group_begin("save_folder_name"); // Replace with your intended real save folder name
buffer_async_group_option("showdialog", 0); // Stop platform dialogues appearing for this auto-save (if you do this your player won't be able to select a slot manually)
buffer_async_group_option("slottitle", "SaveForMyGame"); // Set the title of the slot we're going to save into
buffer_async_group_option("subtitle", "Save file for my awesome game"); // Set a subtitle that's visible in the XBox UI
global.savebuff = buffer_create(1,buffer_grow,1);
/*
All your current saving code goes here. (Opening your ini/JSON/secure save file as a string,
modifying the file, then closing it again.)
However, at the end of your current code, make sure you capture the end result
in a var so we can pass this to buffer_write. E.g., modify a line which closes
an ini file like so:
var closestring = ini_close();
*/
buffer_write(global.savebuff, buffer_string, closestring);
buffer_save_async(global.savebuff, "my_save_file.sav", 0, buffer_get_size(global.savebuff)); // Pass the data to be saved
global.saveid = buffer_async_group_end(); // Start the save process and return the save request ID
Remember to not start a second save operation when you're still waiting for an Async Load event.
How do I know when my save is finished?
When the save is finished you'll receive an Asynchronous Save / Load event where the async_load map will have a key “id” set to whichever variable you used to hold the return from buffer_async_group_end();.
The async_load map also has a key called “status”, which indicates the result of the save operation. Negative numbers indicate an error occurred.
So your Async Save Load event code would be something like this:
var ident = async_load[? "id" ];
var status = async_load[? "status"];
var error = async_load[? "error"];
if (ident == global.saveid)
{
buffer_delete(global.savebuff);
show_debug_message("Save status = " + string(status) + ", error = " + string(error));
}
How do I load my data asynchronously?
The following is an example of how you would now load code, again, wrapped in the asynchronous buffer functions:
global.loadbuff = buffer_create(1, buffer_grow, 1);
buffer_async_group_begin("save_folder_name"); // Replace with your actual save folder name
buffer_async_group_option("showdialog", 0);
buffer_async_group_option("slottitle", "SaveForMyGame"); // Don't show any dialogues, load from slot 0
buffer_load_async(global.loadbuff, "my_save_file.sav", 0, -1); // Say what we want to load and into which buffer
global.loadid = buffer_async_group_end(); // Actually start loading now, and return a request ID value
Remember to not start a second load operation when you're still waiting for an Async Load event.
How do I know when my load is finished?
Again, you'll get an Asynchronous Save / Load event and the async_load map will have a key “id” set to global.loadid (or whatever you called it). Then you can get the data from the global.loadbuff variable (or whatever you called it).
So your Async Save Load event code we showed above would be extended like this:
else if (ident == global.loadid)
{
var buffstring = buffer_read(global.loadbuff, buffer_string);
/*
All your current loading code goes here. (Opening your ini/JSON/secure save file from strings,
reading in values, then closing it again.)
*/
buffer_delete(global.loadbuff);
show_debug_message("read string from buffer " + buffstring);
}
What if I want to load an Included File?
As a final note for loading, if you pass in a filename that begins with working_directory it will skip checking in the save directory for the file and load it directly from the datafiles folder. This means that you don't have to mount the save directory and should make it a lot faster.
Note that you have to know that you are definitely loading a Included File, not a custom save file!
Where can I get more info?
We would suggest reviewing the UWP submission policies if you have any further questions here or would like detailed specifics - also for anything about "how long can my game stall for if I do not want to use async functions?", as this is not something we would recommend or can answer for you.
Similarly, we also cannot tell you how long any synchronous operation will last or should last, because this depends entirely on what you're asking your code to do in that one individual request. You can use get_timer() to measure this if you want, but if you then have questions about if this is acceptable, either see the links below or speak to your Microsoft rep directly.
You can find the general UWP store policies here: https://docs.microsoft.com/en-us/windows/uwp/publish/store-policies
More in-depth info on certification here: https://docs.microsoft.com/en-us/windows/uwp/debug-test-perf/windows-app-certification-kit-tests
And you will also see that link above references a tool you can actually use to check your games locally before submitting them, get a solid idea if they will pass or not. That tool can be found here: https://docs.microsoft.com/en-us/windows/uwp/debug-test-perf/windows-app-certification-kit