Search Unity

How to check playerprefs has actually written data?

Discussion in 'Web' started by Aurigan, Mar 2, 2016.

  1. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    I'm using playerprefs to save game state. If a user has decided to toggle on 'block 3rd party cookies and site data' in Chrome this will stop playerprefs actually saving anything in a webGL build. Specifically this is happening on itch.io because they save games on google storage rather than their servers.

    I tried to add a check to see if playerprefs were *actually* writing like this:

    Code (CSharp):
    1.         PlayerPrefs.SetString (key, serialized);
    2.         int randomInt = UnityEngine.Random.Range(1, int.MaxValue);
    3.         PlayerPrefs.SetInt("SanityCheck",randomInt);
    4.         PlayerPrefs.Save();
    5.  
    6.         //check that prefs are actually getting written
    7.         int sanityCheck = PlayerPrefs.GetInt("SanityCheck",0);
    8.         if(sanityCheck != randomInt){
    9.               //show user error
    10.         }
    But ... this claims to always be working - I'm assuming because playerprefs is getting cached in memory. How can I check that the write really happened?
     
  2. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    PlayerPrefs.Save();
    Hello Aurigan.

    PlayerPrefs are loaded from the database once when WebGL content is launched and are saved to the database each time you execute PlayerPrefs.Save() under condition that indexedDB is accessible. When you get or set PlayerPrefs values, you access the memory instance of PlayerPrefs, and not the file system.

    If you disable 3rd party cookies and data, then indexedDB can not be accessed and PlayerPrefs can not be read or saved when you execute PlayerPrefs.Save(). Also, indexedDB is not accessible from a cross-domain iframe in Safari, and from a cross-domain iframe in Firefox prior to 42.0. Normally, if you are able to execute window.indexedDB.open() without error or exception from your document, then you can expect PlayerPrefs to be loaded and saved correctly.

    Please note that in WebGL you should always execute PlayerPrefs.Save() during gameplay (for example, each time user confirms the change) to ensure the data is saved to the database. You should not expect PlayerPrefs to be saved when user closes the window (i.e. in OnApplicationQuit etc.), because all indexedDB operations are asynchronous, and there is generally no reliable way to perform any asynchronous operation when user closes the window. Success of such operation depends on browser implementation and timing, so you should always assume that PlayerPrefs are not saved at the moment when window is closed.
     
    Last edited: Mar 2, 2016
  3. jonas-echterhoff

    jonas-echterhoff

    Unity Technologies

    Joined:
    Aug 18, 2005
    Posts:
    1,666
    I think you should be able to test if this is supported from JavaScript, like this:

    Code (csharp):
    1.  
    2. HasIndexedDB: function()
    3. {
    4.     return !!window.indexedDB;
    5. },
    6.  
     
  4. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    Hey there, I understand the issue, the question is what to do about it ;)

    Reading between the lines ... are you suggesting that I write a js function to add to my custom webGL template, to call from unity, that would run window.indexedDB.open() and report back to Unity any issue?
     
  5. jonas-echterhoff

    jonas-echterhoff

    Unity Technologies

    Joined:
    Aug 18, 2005
    Posts:
    1,666
    Yes (and actually, that is a much better suggestion then my JavaScript to check if window.indexedDB exists, because checking if the API actually works is more reliable and will catch more corner cases).
     
  6. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    Hrm, I think this still might not work ... looking at the game page (https://scarybee.itch.io/slurpy-derpy?secret=AFUU9vxWIJ02rl9jFBK8ZinzfyY) it looks like the index.html is stored on itch.io (so would be 1st party) but the game data is on google storage (3rd party). So running anything from the index.html will give false positives.

    I guess I can try running the js from the game code instead of writing it into the index.html file ... will give that a go.
     
  7. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    It does not matter where js or data are stored. All operations are executed in the context of the html document, no matter where js code comes from.
    And the document comes from https://commondatastorage.googleapis.com/itchio/html/167776/webGL/index.html in an iframe.
     
  8. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
  9. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    I've tested this and ... it doesn't work (false positives), this is the URL https://scarybee.itch.io/slurpy-derpy?secret=AFUU9vxWIJ02rl9jFBK8ZinzfyY with the function check in it. I also tried running the js as an externalEval (which gives the IndexDB2 messages in console log) but as expected(?) this gives the same false result.

    So, currently still stuck for a way to check if playerprefs are working or not.
     
  10. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    Yuck, ok, I got this to work.

    In a Start() function I'm calling this JS method, baked into my custom index.html

    Code (JavaScript):
    1.  
    2.       var testDB;
    3.       function checkIndexedDBAvailable () {
    4.           try {
    5.               var request = window.indexedDB.open("plsworkpls");
    6.             request.onerror = function(event) {
    7.                 console.log("failed to open IndexedDB onerror: "+event.target.errorCode);  
    8.                 SendMessage('DataController', 'FailedToOpenIndexedDB', ''+event.target.errorCode);
    9.             };
    10.             request.onsuccess = function(event) {
    11.                 console.log("IndexedDB available");
    12.                 testDB = event.target.result;
    13.             };
    14.         }
    15.         catch(err) {
    16.             console.log("failed to open IndexedDB: "+err.message);
    17.             SendMessage('DataController', 'FailedToOpenIndexedDB', ''+err.message);
    18.         }
    19.       }
    Using this the onerror handler gets used if I'm blocking 3rd party cookies/data.

    Things to note:

    * The open() on indexDB requires a name
    * The open is async so won't error immediately (in chrome at least)
    * In chrome there is no error code so this will send an empty string back to Unity.
    * This is way more work that it should have been :)
     
  11. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Yes, checking if indexedDB is accessible takes some code, but that is what you have to do if you want to know the indexedDB status in advance before the WebGL content is loaded, so that you can make appropriate decision.

    On the other hand, if you just want to know if indexedDB works after the WebGL content is loaded, it would be much easier, for example, you can use IDBFS.dbs.hasOwnProperty("/idbfs") test, i.e. try the following:
    Code (CSharp):
    1. var Module = {
    2.   onRuntimeInitialized: function() { alert("Database works: " + IDBFS.dbs.hasOwnProperty("/idbfs")) },
    3.   TOTAL_MEMORY: 268435456,
    4.   ...
    5. }
    Note that this sort of check is not documented and therefore is version-specific.
     
    Last edited: Mar 3, 2016
  12. liortal

    liortal

    Joined:
    Oct 17, 2012
    Posts:
    3,562
    We also faced this issue now on Safari (our game runs in Facebook and can't access the indexedDB).
    Is there any suggested workaround we could apply? for saving simple types (e.g: key / values)
     
  13. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello liortal.

    Currently Safari does not allow access to the IndexedDB from a cross-domain iframe. This is a Safari security restriction and there is no workaround for it. You might however consider storing the data using facebook api or remotely on your own server (and load it when user authorizes), the same way you would store the user online profile. Other possibilities like local storage or browser cookies should not be relied on because those can be explicitly forbidden by user in the browser settings (in order to avoid 3rd party tracking).
     
  14. liortal

    liortal

    Joined:
    Oct 17, 2012
    Posts:
    3,562
    We have actually implemented a workaround using localStorage. Could you perhaps tell me (since i am not a web dev), what are the pros/cons of using that (for storing key/values) ? (apart from what you specified)

    Also, if PlayerPrefs are essentially simple-typed key-value pairs, why are you using IndexedDB for it and not localStorage for example?
     
  15. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Well, indexedDB allows you to store significantly larger amount of data than localStorage, which is very important for caching the data file and streaming assets. And even more importantly, localStorage is synchronous, so it would make your page unresponsive while working with large amount of data, while indexedDB is completely asynchronous. Plus, PlayerPrefs are not stored as key=>value pairs, but instead as a complete file in the common virtual file system based on indexedDB. It is of course technically possible to store the cached assets and PlayerPrefs in different storages, however, it would significantly overcomplicate the system, as it would require two separate virtual file systems. But, yes, this is still something to be considered.
     
    Last edited: Apr 5, 2016
    liortal likes this.
  16. xenonmiii

    xenonmiii

    Joined:
    Aug 2, 2010
    Posts:
    147
    @alexsuvorov We're seeing a similar problem in Chrome, in a facebook app, where PlayerPrefs at first works fine, and then after a while (haven't found the cause yet), we update some value (e.g. coins), and upon closing and relaunching the app, the change is not there anymore.

    So for example, if players buy coins, they see the coins have been given. But upon relaunching the app, the coins vanish because the previous value is being read. (We are doing PlayerPrefs.Save() on every change to PlayerPrefs)

    We have already tried to make a repro project, but couldn't reproduce. We do not believe we are putting too much data in PlayerPrefs, although we do not know how to check the size of the webgl playerprefs. Didn't see any playerprefs errors in console either. Any hints?
     
  17. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello xenonmiii.

    When you are talking about the previous value being read, do you mean that the previous value has been saved successfully before? Do you assume that during the very same launch PlayerPrefs are saved on some of the PlayerPrefs.Save() executions and not saved on others? Are you sure the experiment is clear (i.e. you don't have 2 games opened in different tabs at the same time etc.)? You do realize that you normally should not store information like coins in PlayerPrefs as coins are profile-specific and not browser-specific?
     
  18. xenonmiii

    xenonmiii

    Joined:
    Aug 2, 2010
    Posts:
    147
    @alexsuvorov Hi alex, Yes. It saved fine, then at some point it is as if the playerprefs get corrupted and doesn't save to disk anymore. There was only one webgl tab open in the experiment.

    We have now implemented a work around, where we use localStorage to save data instead. So far we didn't meet with the same problem after two days QA testing it.
     
  19. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    If you have some difficulty with the indexedDB saving, does that mean you are able to reproduce the issue? Which exact version of Unity are you using?
     
  20. xenonmiii

    xenonmiii

    Joined:
    Aug 2, 2010
    Posts:
    147
    @alexsuvorov The issue occurs randomly. But don't have exact steps how to reproduce. I used to be testing the app, and then all of a sudden, when I reboot the app I see old values rather than the latest ones. Then I go to clear the data related with the webgl app, and I see more than 1 indexedDB listed in chrome.

    Before we switched to using LocalStorage, I was on 5.3.4p1.