Local Storage Headache

Discussion and feedback on Construct 2

Post » Wed Aug 23, 2017 7:57 am

I haven't really played around with LocalStorage much, but all I can tell is it's completely inferior to the older webstorage plugin.

I'm trying to do the following and having a heck of a time figuring out the best way to do it.

I have multiple dictionaries, arrays, and global variables that I need saved. In the game, you can have multiple saves to a saveslot, similar to the "save" under the system actions. I then need to be able to get access to the name of all the games (as well as a couple other stats) that exist to put the data into a list that you can select from.

My original idea was to save everything to a single dictionary, save it to the local storage using AsJson, and then also save a separate local storage called "gameName" (the value of this would be "gamename1;game1time;%completion-gamename2;game1time;%completion-gamename3 " etc. etc. Then, in order to create the list to select from I would do

Code: Select all
repeat(tokencount(LocalStorage.ItemValue, "-")
times, and set everything according to

Code: Select all
tokenat(tokenat(LocalStorage.itemvalue, loopindex, ";"), <textinstance>, "-")


I assume you're lost by now - me too. Can anyone else think of a better way to do this? I don't want to use the built in save and load function because I need more control over it. The constant "check if item exists, check if item has been gotten after it exists" is super clunky and confusing. It's making it very difficult to organize this in the way I have my event sheets, functions, and variables.

I'd love to not have to resort to saving to a txt file, but will if I have to.
B
43
S
12
G
1
Posts: 545
Reputation: 4,246

Post » Thu Aug 24, 2017 8:00 am

I've looked a bit more into this issue - localstorage just isn't the right tool for multiple saves like I'm trying to do. I'm wondering if anyone has had success in using javascript in creating their own save function plugin?
B
43
S
12
G
1
Posts: 545
Reputation: 4,246

Post » Thu Aug 24, 2017 8:24 am

I usually save everything to one dictionary (to save an array to dictionary, I just use Array.AsJSON and put it into the dictionary).
Then I save that Dictionary.AsJSON to the localstorage. When loading, I do the opposite (load the dictionary from JSON (from local storage) and so on). This also allows me to simply do some encryption to reduce hacking/cheating attempts.
B
32
S
7
G
4
Posts: 398
Reputation: 4,591

Post » Thu Aug 24, 2017 9:25 am

Working with LocalStorage isn't very comfy indeed. I made SyncStorage plugin which is sychronous, can encode data, works with local storage, files and external DBs to make my life easier.
I submitted it to Scirra Store yesterday and it just got approved so you may want to read more on the store page: https://www.scirra.com/store/construct2 ... orage-3800
Last edited by BackendFreak on Thu Aug 24, 2017 10:22 am, edited 1 time in total.
ImageImageImage
B
28
S
16
G
82
Posts: 1,031
Reputation: 45,806

Post » Thu Aug 24, 2017 9:58 am

Local Storage is very flexible. It is just the structure of a dictionary. Named keys with values attached.

It just feels messy because you are not confident with it.

First things first. There is one 'rule' that you have to obey to: 'It Takes Time'. Loading/saving can take several ticks.

C2 is very consequent in how it handles actions that generate a 'result' that is not available in the same tick as the action is invoked.

Take the easy example of path finding.
The result of the action 'find path (x,y)' is not available inmediately. The path is available several ticks later !
Therefor the action 'find path' has a sister condition 'on path found'.
So, you invoke the action 'find path', this path is calculated in the background -it takes some time-, and the 'on path found' triggers when that path is ready, sometimes even 1 second later.

Local Storage is handled the same way, just very consequent. Loading/Storing takes time. From several ticks to seconds.

You have the action 'Get item' and its sister condition 'On any item get'. The 'key' connects them.
The condition needs to check the 'loading state' every tick, therefor (A) it is strictly forbidden to place the condition inside a 'on layout start' (runs only 1 tick), and (B) it has to be a root event.

You do not have to chain a 'Check item exists' + 'On item exists' + 'Get item' + 'On item get'.
It loads the keys also on 'On item exists'
So, this construction ....

On layout startup
____ Action 'Check item exists'

On item exists
____ Set something to ItemValue

.... works fine too.

Okay, so that is the base. It also has a 2 more consequences.

Loading/Saving happens Async. Meaning ...


On layout startup
____ Action > Get item "Score"
____ Action > Get item "Life"

On item get "Score"
____ Set something to ItemValue

On item get "Life"
____ Set something to ItemValue

Will not necessary trigger 'On item get "Score"' first. It can be "Life" that is ready loading first. Async.
Conclusion, if the flow depends on the load order, you have to chain them.

A second consequence goes with "It Takes Time".

Say, you load an array. And that array is essential for the well-being of the game.
In that case you can just not allow the game to run before all loading data and placing data is done. It will totally mess up if you do.

I usually do this with using groups. I have a group "Prep" holding all the loading and placing data events and a group "Game" holding the events for the rest of the game. I uncheck "Active on start" for the group "game" in the event sheet. And set i 'active' during run time in the group "Prep" when ALL loading and placing is done and over with.

I assume that there other methods to archive this. But, this what i am comf with.

Warning: If you gonna Load Async, then this 'not allowing the rest to run' is not that easy.
Therefor, and you proposed that yourself, i usually bring everything in ONE dictionary and save/load its .AsJson.
This way it is easy to know when ALL data is processed.

And that is also the ONLY advantage of using ONE dictionary. It is convenient to handle the 'Async problem', and it is easy to handle the 'Wait for completion' problem.

If you solve those 'problems' (to give it a word) in another flawless flow, then there is no reason to use 1 dictionary. Just have it mind, and explicit code for it, so there are no exceptions.

The same happens, by the way, on the end of the layout. Say you store the data in the end. Then you better change Layout in under a 'On all sets complete' event. Or, change layout after all the sets (storing) is done and over with. Set also Takes Time.

So you have the action 'Set item' and its sister condition 'On item set'.

Alright, so you have multiple dictionaries, arrays, and global variables that needs to be saved.
And, they are unique / gameslot.

That means that those data have the 'form' of an 'Array'. In the X index the name of saveslot, on Y the chunks of data. But we do not need to load all data, just the data for the current slot.

So, or you auto generate the 'Name', or you let the user choose a 'Name'. Dont really matter (besides the duplicate checking".

It is about the 'Name'.

So have an Array with all the 'Name' on the X-index. Every time you make a new 'gameslot' you push the 'Name' to the back of that Array. Lets name that Array 'SLOTS'.

When the game starts, we are in the "Open Layout". We will not go to the "Game Layout" before every loading is done and over with. And we try to do this as flexible as possible.

Local Storage will have a Key "SLOTS" with value "SLOTS.AsJson".

Event sheet for "Open Layout" has 2 Groups. "Before" and "After". "After" is set to non active in the event sheet.

Under the group "Before" and under a trigger 'Once while true", we check if the key "SLOTS" exist.
Then ...
On item exists , we set the Array "SLOTS" to the json in 'ItemValue', we activate the group "After"
On item missing, we make the size of the array (0,1,1), and set it in the Local Storage as .AsJson under the key "SLOTS".
On item set, we activate the group "After" (accounting for the time that 'set' takes.)

Now "After is activated".

Under de group "After", (under a once while true trigger) we present the array 'SLOTS' to the user, for him to choose.
We wait for interaction.
After the user has chosen we have the String 'Name'

Now the Local Storage will have keys 'Name'&"Whatever you wanna store".

So under the trigger that marks a users choice, we get all the keys that we need and process them.

On Trigger (whatever that will be)
____ Get 'Name'&"Score"
____ Get 'Name'&"Life"
____ Get 'Name'&"Characters"
____ Get 'Name'&"Whatever"

On any item get
___Sub ... Compare key is 'Name'&"Score" ?
________Process score
___Compare key is 'Name'&"Life" ?
________Process life
__And so on ........................

On all gets complete
___ Change layout

Sets happen under right key, but i am sure you get that by now.
B
33
S
18
G
28
Posts: 2,493
Reputation: 20,950

Post » Thu Aug 24, 2017 5:40 pm

@99Instances2Go - Thank you. You were right; it's because im not confident it. I really just hate the idea that I can't use saving in a function, and instead have to call triggers. However, I've figured out my problem (and it's pretty much what you said). I just needed to save the data in an array for the save slots and then save everything else to a dictionary. The only thing I needed to do was create a splashscreen that loads everything away from any #includes (so if I call a "local storage item get" somewhere else, I can add specific save and load functions that wouldn't pertain to the first time).

@BackendFreak - This looks great! I'm definitely going to consider this, as well. However, it looks like you still have to check if it's loaded or missing in this, too. Really what I want is to be able to save and load a game in a function without checks, whatsoever (that includes CHANGING said local storage and calling it from a function, expression, etc.)
B
43
S
12
G
1
Posts: 545
Reputation: 4,246

Post » Thu Aug 24, 2017 6:41 pm

ome6a1717 wrote:@BackendFreak - This looks great! I'm definitely going to consider this, as well. However, it looks like you still have to check if it's loaded or missing in this, too. Really what I want is to be able to save and load a game in a function without checks, whatsoever (that includes CHANGING said local storage and calling it from a function, expression, etc.)


It's just the initial load. You do it only once at the start of the project to load previous app storage state.

Once all is loaded, you can use Set/Get/Compare straight away without any checks and delays.

"On missing data" condition just tells you that it's the very first time the app runs on this device (nothing has been saved yet), so it's just a helpful condition under which you can set the initial data in storage. You can think of this condition as "On very first app run".

If you have any further doubts or questions just let me know, I'm happy to answer.
ImageImageImage
B
28
S
16
G
82
Posts: 1,031
Reputation: 45,806


Return to Construct 2 General

Who is online

Users browsing this forum: No registered users and 10 guests