Showing posts with label local storage. Show all posts
Showing posts with label local storage. Show all posts

Friday, January 10, 2014

IndexedDB (Game Programming) - revised

Sometimes I can't let an idea go. I wrote a simple post on Local Storage and thought that would be all that needed to be said in terms of saving high scores for your game! Well, it turns out that Local Storage has been proved harmful (just like GOTO) and that you don't want to use it in Firefox OS. Synchronous programming is not good!

Lucky for us, the Firefox OS team has created a small library called async_storage.js that covers the same territory as Local Storage. I wrote about it here: http://firefoxosgaming.blogspot.com/2014/01/asynchronous-storage-game-programming.html. Done!

Well, not quite, because there were a few nits I had to add about how it worked. And there was that nagging feeling that maybe I should dig into IndexedDB, which is what async_storage.js is based upon. As a low-level kind of guy, I like knowing what's under the hood. Well, IndexedDB isn't simple, but it's not really that hard. And it's a great tool to add to your toolbox. Not just for saving high scores, but for any complicated stuff. I remember when I worked on a large adventure game in the 80's and the head of the project said that adventure games are really all about databases!

Okay, so what is IndexedDB? Essentially it is a database that can be stored on your phone. When you turn off the phone and come back later, the data is still there. But there are a few different types of databases (highly simplified).

Flat

Flat databases are just tables. You have rows and columns and you put data in and get it back later. Specify the type and width of the columns and you're done. This is how Excel and other spreadsheets work. You've all seen that somewhere, I'm sure. Are there any spreadsheets available for Firefox OS?

Relational

In the 90's, relational databases came into their own and became standard. Essentially you have a bunch of tables and you relate them to each other. If you specify your data carefully, you can do very powerful things. Or mess things up quickly. The language that most people use for this is SQL (Structured Query Language). Right now this is what most corporations use.

No-SQL

A recent trend is to do away with SQL and make simplified databases that work with key-value pairs. They don't require SQL and these databases are hot right now. IndexedDB is one of these. If you'd like to know more about no-SQL databases, I recommend a cool book called Seven Databases in Seven Weeks: A Guide to Modern Databases and the NoSQL Movement. A nice read an an eye opener if all you know about databases are flat or relational.

IndexedDB

IndexedDB is supported in Firefox OS, Firefox desktop, Firefox for Android, Chrome, Internet Explorer, but not Safari and a few other mobiles. We only care about the ever-growing Firefox family and we're good with IndexedDB.

As usual, the Mozilla Developer Network (MDN) covers IndexedDB well. Start at a great overview here: https://developer.mozilla.org/en-US/docs/IndexedDB/Basic_Concepts_Behind_IndexedDB. You'll want to browse through the large API starting here: https://developer.mozilla.org/en-US/docs/IndexedDB.

Two tips for reading the docs:

  1. The docs are laid out as interfaces (for example, IDBObjectStore). You're not going to type that. You're going to type something.objectstore.something. The IDBxxx name is for C++ developers and that's not you. That's really more for the Mozilla developers and the original W3C specification.
     
  2. The sample code is confusing and might be out of date. The reference has lots of code snippets with references to earlier definitions not included. You can figure it out, but because IndexedDB is still pretty new, there aren't a lot of helpful samples and tutorials elsewhere. Part of that is that there are several ways to skin this kind of cat, and the API is complicated. And because this is a different type of database, some things aren't always there or named what you think they might be. But it does work! Not really a criticism of MDN, because I know how much they have to do and how little resources they really have!
So with that said, the main thing to understand is that you store your data as key-value pairs. The value is the data you want to store and retrieve, and key is the label for that data. For example, you might have a bunch of names for your pets. The names would be keys and the type of pet would be the data. You want unique keys (unless all your pets are named Spot) and each key will identify a particular pet. So "Rover" would be your "dog" and "Felix" would be your "cat". You can also index your data so "Pets" might be your key and Pets[1] would be a particular pet. There are a lot of ways to skin these pets (please forgive the American slang). Indexing and other manipulation can be very powerful. And everything is asynchronous, so you won't have problems with waiting.

Indexed DB Steps


Indexed DB requires a few steps that you will use every time. Learn them and it is actually pretty easy. Here are the steps:
  1. Open your database. If there is no database, one will be created for you.  You need to specify a name and a version. Tip: don't use decimals for your version.
  2. Create a store. You can have more than one store in your database. Think of it as a table. You could have one store for your cats and another for your dogs. You only need to do these first two steps once. Each time you change the version, you need to do it over again. You'll need to specify the store name and a keyPath. The key path (or just key) is just the way you will sort your data. Make sure that the key will be unique for your data. If you have two dogs named Spot, you're in trouble.
  3. Set up a transaction. A transaction is a specific change to the database. You're going to add, put, get, or delete data, using key-value pairs. But before you can do that, you must set up the transaction. To set up the transaction, you need the store name. All transactions are tied to a specific store.
  4. Add, put, get, and delete your data. These operations make up a transaction. You can probably figure out what each operation does, but add will add to the database while put just replaces something that is there (or adds it if nothing is there). The commands take different parameters, depending on what you are doing. For example, add and put will require a key and value, but a get and delete only requires the key.
     
  5. Check to see if errors happen. You'll want to do this. Always. Luckily you can set up events easily to do this. onsuccess and onerror are your friend.
Essentially you'll set up things so you only do steps 1 and 2 once for every database version, and the onupgradeneeded event takes care of that for you. Steps 3 and 4 you do each time you want to add, put, get, or delete data. There's plenty more and you can have fun with indexing all day. But don't forget to do step 5 for every action you take. Be careful out there, people!

I've created a very simple sample that will show you the basic code to do IndexedDB. I'm only creating one database, one store, and one item (with one key-value pair). In keeping with game design and making this really, really simple, I'm just storing the value of a score, letting you generate random scores, and then storing them in the database and getting them back again and displaying the results.

The Code

So here's the code, tested and run on the Geeksphone Peak with Firefox OS 1.1.

<!DOCTYPE HTML>
<html>
 
  <head>
    <meta charset="UTF-8">
    <title>
      IndexedDB
    </title>
 
    <script type="text/javascript">
   
    // Global variables.
    var highScore = 0;
    var myDb;                    // Database object
    var myDatabase = "FoxBase";  // Database name
    var myVersion = 1;           // Database version
    var myStoreName = "myScore"; // Database store name
    var myStoreObject;           // Database object store
    var myTrans;                 // Database transaction 
    var flag = 0;                // Flag for initial data.   

        // Load event listener
      window.addEventListener("load", getLoaded, false);
       
      // Function called on page load.
      function getLoaded() { 
     
        // Save button listener
        myButtonSave.addEventListener(
          "mousedown", myClickSave,false);

        // Retrieve button listener
        myButtonLoad.addEventListener(
          "mousedown", myClickLoad,false);
         
        // Clear button listener
        myButtonClear.addEventListener(
          "mousedown", myStorageClear,false);
         
        // Length button listener
        myButtonLength.addEventListener(
          "mousedown", myStorageLength,false);       
         
        // Background color
        document.body.style.backgroundColor = "Coral";
       
        // Generate a random score.
        randomScore = Math.floor(Math.random()*10000);
        info.innerHTML = "High score: " + randomScore;
       
        // Initialize with name and version.
        // If no database exists, one will be created.
        myRequest = window.indexedDB.open(myDatabase,
          myVersion);
            
        // This is called on version change.
        // Fired first time database is created.
        // Not fired again unless new version created.
        myRequest.onupgradeneeded = function(e) {
       
          // Save result of version change.
          myDb = e.target.result;
         
          flag = 1; // First time through.
         
          // Kill duplicate name if there is one.
          if(myDb.objectStoreNames.contains(myStoreName)) {
            myDb.deleteObjectStore(myStoreName);
          }
         
          // Make an object store.
          myObjectStore = myDb.createObjectStore(myStoreName,
            { keyPath: "initials" });
           
              // Provide feedback.
              console.log(myStoreName + " object store added!");
            }
                       
        // Was the request successful?
        myRequest.onsuccess = function(e) {
         
          // Provide feedback.
              // Database name and version.
          console.log(myDatabase + " version: " +
            myVersion + " loaded!");
              // Database state.
          console.log("Database request state: " +
            myRequest.readyState + "!");
           
          // Save reference to database.
          // You need this if not first time.
          myDb = e.target.result;        

          // Initialize data only if first time.
          if (flag==1) {
         
            // Initialize the score.   
                // Get the transaction.
                myTrans = myDb.transaction([myStoreName],
            "readwrite");         
            // Get the store object.
                myStoreObject = myTrans.objectStore(myStoreName);
         
            // Put the data you want to save into the store.
                // The key is "initials" and the data is "score".
                // Insert the current random score.         
            myStoreObject.put({
              "score" : highScore,
              "initials" : "ABC"
            });   
         
                // Provide feedback.
                console.log("Score initialized to zero.");
          }           
        }
       
        // Did the request fail?
        myRequest.onerror = function(e) {
       
          // Provide feedback.
          alert("Error: " + e.target.errorCode);       
        }

            // Page loaded and initialized.
        console.log("The page is loaded!");
      }
     
      // Put button event handler
      function myClickSave() {
     
          // Open a transaction on the database.
        myTrans = myDb.transaction([myStoreName],
          "readwrite");
       
            // Get the store object.
        myStoreObject = myTrans.objectStore(myStoreName);
       
            // Put the data you want to save into the store.
            // The key is "initials" and the data is "score".
            // Insert the current random score.
        myRequest = myStoreObject.put({
          "score" : randomScore,
          "initials" : "ABC"
        });   
       
            // Provide feedback.
            console.log("New score added!");   
        putScore.innerHTML = "New score added!";     
        }
     
      // Get button event handler
    function myClickLoad() {
     
        // Open a transaction on the database.
      myTrans = myDb.transaction([myStoreName]);
       
          // Get the store object.
      myStoreObject = myTrans.objectStore(myStoreName);
       
          // Get the item name corresponding to the "initials".
      myRequest = myStoreObject.get("ABC");
       
          // Did we get the score?
          myRequest.onsuccess = function(e) {
       
            // Display the actual score we got.
            console.log(myRequest.result.score);
        getScore.innerHTML = myRequest.result.score;       
          }    
    }
     
      // Delete button event handler.
      function myStorageClear() {
     
        // Delete the database.
        // Use this only for testing!
        window.indexedDB.deleteDatabase(myDatabase);
       
        // Oh, noes, you killed the database!
        console.log("You deleted the whole database!");      
      }
 
      // Create new score button event handler.
      function myStorageLength() {

        // Generate a random score.
        randomScore = Math.floor(Math.random()*10000);
        info.innerHTML = "High score: " + randomScore;
      }
      
    </script>
  </head>
 
  <body>
 
    <p id="info">Watch this space!</p>
   
    <p>Put something in the database.</p>
    <p id="putScore">PUT SCORE</p>
   
    <p>Get something from the database.</p>
    <p id="getScore">GET SCORE</p>
   
    <button id="myButtonSave">Put Score</button>
    <br><br>
   
    <button id="myButtonLoad">Get Score</button>
    <br><br>
   
    <button id="myButtonClear">Delete Database</button>
    <br><br>
   
    <button id="myButtonLength">Create New Score</button>

  </body>

</html>


This is about the smallest I could boil it down to and still have a complete example that lets you see the putting and getting happen on your phone. If you close the application and kill it, the data will still be there the next time you load the app. Magic!

What It Looks Like

 When you load the app into your Firefox OS phone, here is what it looks like:


The top line is the high score. The next two lines will tell you about the Put operations and the two after that will tell you about Get operations. The four buttons will Put the score into the database, Get the score from the database, delete the database, and generate a new score from a random number.



The above screen shows what happens if you press the Get button. The initial score is set to zero, so if you get it, that is what you get, and it is displayed below the heading "Get something from the database." The randomly generated high score is at the top, but is not yet in the database.


The above screen shows what happens when you press the Put button. Under the "Put something in the database" it says "New score added!" You're just being notified that a score was added. The data that was added was whatever the high score was, as displayed at the top of the screen.


The above screen shows what happens if you now press the Get button to see what you stored with the Put button. You'll see that the number is the same as the high score.

The "Create New Score" button just generates a new high score randomly, letting you save it with the Put button if you want to test it.

The "Delete Database" was something I put in for testing. It just deletes the database. You only need this if you are changing the code and need to delete the database to start over again with the same version. If you delete the database, you'll need to completely kill the app and start over again. On the phone, you do a long press on Home and tap on the upper-left-hand corner of the app you want to kill.

How Does It Work?

This is just a simple HTML5 page with buttons and text in the body. I had a bunch of globals and event listeners for the buttons and their corresponding event handlers. The code starts in the handler that is loaded when the page loads.

Step 1 - Open the Database

The first step for Indexed DB is to open the database.

        myRequest = window.indexedDB.open(myDatabase,
          myVersion);


You just open a database with a name and version (defined in the globals). The result of this will be stored as a request that you can use to  use to see if your request to open worked.

Step 2 - Create a Store

       myRequest.onupgradeneeded = function(e) {
       
          myDb = e.target.result;
         
          if(myDb.objectStoreNames.contains(myStoreName)) {
            myDb.deleteObjectStore(myStoreName);
          }
         
          myObjectStore = myDb.createObjectStore(myStoreName,
            { keyPath: "initials" });
           
              console.log(myStoreName + " object store added!");
            }

You want to do this only when the onupgradeneeded event takes place, and that takes place once in a lifetime for each version of your database. First you see if there is a store with that name already, and if there is, you delete it and make one with the same name. You want to be careful. Who knows what is already hidden away in that database? The store is created with a name and a keyPath. The name can be anything and I chose "initials" as my key because it might be fun to expand this and let people save scores with their initials, just like in the arcade machines. I also added a console.log statement that says the store was added. Maybe it wasn't really, but with JavaScript, if a log doesn't print out, something went wrong. But fortunately there's a way to tell what happened.

Step 2.5 - Did it happen?

        myRequest.onsuccess = function(e) {
         
          console.log(myDatabase + " version: " +
            myVersion + " loaded!");
          console.log("Database request state: " +
            myRequest.readyState + "!");


          myDb = e.target.result;


This code will be triggered if the creation was successful. It logs the database name and version, and also the ready state. The ready state will tell you if the loading is over or still happening.

I also added a reference to the database itself here with the variable myDb. This same line was in the onupgradeneeded event handler. Why twice? Because if I just have it in the onupgradeneeded handler, it will only get created the first time through. That makes it difficult to use again for the button event handlers, who also use myDb to get a reference to the database. You can only get that reference by opening the database and using a handler. Because the onupgradeneeded handler is asynchronous, you can't assume that it will get the reference from the database open. So doing it twice is the only way to make sure! With asynchronous programming, you must be sure. You can't assume.

Step 3 - Start a transaction

Now that the database and store are prepared, it's time to do a first transaction, to set the score to zero. We're only going to do this once. You want this code in the onsuccess event handler because it might not run if you try to do it in the onupgradeneeded handler. But that handler gets called when the database is opened (every time you run the app) and again when you create the store inside the upgradedneeded handler. So my hackish solution is to create a flag that starts out at 0 as a global and is set (set means flag =1) inside the onupgradeneeded handler. Then when the success event handler is called, the flag will determine whether the initializing transaction takes place.

                                flag = 1; // First time through.

Then, in the onsuccess event handler, you start the transaction like this:

             if (flag==1) {

               myTrans = myDb.transaction([myStoreName],
                 "readwrite");                     

This will open a transaction. Notice that the store name has brackets around it, indicating that it is really an array. We're only using the first item in the array, but the transaction folks like to know these things. The transaction is readwrite, indicating that you can read or write.

Note that this transaction is actually part of the onsuccess event handler. You don't want to even try to create something if the object store wasn't successfully created.

Step 4 - Put the data

          myStoreObject = myTrans.objectStore(myStoreName);
                  
          myStoreObject.put({
            "score" : highScore,
            "initials" : "ABC"
          });   
         
          console.log("Score initialized to zero.");

        }

This is still part of the onsuccess event handler. First you get the object store by using its name and then you put the data in that store using a key-value pair. The key is "initials" and you must always use this to store your data, and you must make sure that each key is unique. For simplicity, I chose simple keys and values, but for more complicated data, you'll want to have an index that is unique so that all your data is solid.

Notice that the commands are tied not only to the specific store, but to a specific transaction.

And note, also, that this initialization only takes place once. If you kill the app and come back later, the data will not be initialized but will be whatever you last saved it as.

Finally, I used a console message to say that the Put took place and what it was about.

Getting the Data

I have a button that you can press to get the current value in the database of the score. Here's the code.

    function myClickLoad() {
     
      myTrans = myDb.transaction([myStoreName]);
       
      myStoreObject = myTrans.objectStore(myStoreName);
       
      myRequest = myStoreObject.get("ABC");
       
      myRequest.onsuccess = function(e) {

        console.log(myRequest.result.score);
        getScore.innerHTML = myRequest.result.score;       
      }    
    }


This event handler creates a transaction using values from the global variables. This needs the database and store name. The database name was created when you initialized the database with onupgradeneeded. You need to store these as globals and the variables are saved with the database.

This is similar to the initialization of the score. You get your transaction, get your store, and then you get your data, based on the "initials" key. The particular key is called "ABC" and the Get gets it. Get it? Then you see if you got it with the event handler. If you did, the score will be:

       myRequest.result.score

The "score" is the matching part of the pair that corresponds to "initials". The result is the result of the event, which was to get the data.

Putting the Data

This is similar to getting the data.

      function myClickSave() {
     
        myTrans = myDb.transaction([myStoreName],
          "readwrite");
       
        myStoreObject = myTrans.objectStore(myStoreName);
       
        myRequest = myStoreObject.put({
          "score" : randomScore,
          "initials" : "ABC"
        });   
       
          console.log("New score added!");   
          putScore.innerHTML = "New score added!";     
      }


You start a transaction, get the store, save the data with a key and value. The names of the key and value pairs are "initials" and "score" and the data that is saved in this case are "ABC" and the value of randomScore.

Deleting the Database

Don't try this at home! You only should do this while you are programming, but I thought I'd leave it in for your use. Once a database is deleted, you need to close the browser and kill it thoroughly dead.

     function myStorageClear() {
     
        window.indexedDB.deleteDatabase(myDatabase);

        console.log("You deleted the whole database!");      
      }

Again, use global variables.

Generating a New Score

I put this in a button event handler.

     function myStorageLength() {

        randomScore = Math.floor(Math.random()*10000);
        info.innerHTML = "High score: " + randomScore;
      }

Ignore the function name and button id names. I wanted to use the same UI as my earlier examples. Probably not a good idea. There isn't any method or property that I could find for IndexedDB that tells how many items are in a store. Maybe that's by design?

This just uses good old Math to make a random number and puts it on the screen.

That's It!

I hope you enjoyed this somewhat winding trip down the database lane. IndexedDB isn't that hard to use but it satisfies all the requirements for good database management in HTML5 and is completely supported by Firefox OS.

(Well, I take that back. There's a synchronous version of IndexedDB that will be tailored for Web Workers, but it's not ready yet. Web Workers are cool but that's the subject of a different post, later, much later.)

Now I'm back to review some games and then to create a simple game and see if I can put it in the Marketplace. I'll do that over a series of posts. Stay tuned, but not iTuned!

Not Quite

If you read this in the last few hours, I'm sorry to tell you that I made two errors my my logic.
  1. The score gets initialized every time. The code is the right place, you can't read and write data until the onsuccess event fires. The problem is that the same event is firing at two different times. When you open the database it fires, but it also fires when you create the store. My quick solution to this is to create a flag that is only set when the onupgradeneeded fires (the first time through). Then, when the onsuccess event triggers, if the flag is set, the data is initialized. Otherwise not. It is very important to be aware of the flow, especially with asynchronous timing. If you try to read or write data before the database is ready, you'll get an error.
     
  2. The myDb was set in the onupgradeneeded event handler. That would mean that in subsequent times through, the myDb variable wouldn't have a value. So I added it to the onsuccess event in the main routine. That way, whichever event is triggering the onsuccess, the myDb variable is created, which is the way to reference the database in the other event handlers.
I've fixed the code above and I'll put a note in the comments and tweet the changes. I apologize for these errors. No one pointed them out, but the wit of the staircase prompted me to go back and look again, only to see that I had made a subtle but important error. Two errors, not related.

IndexedDB is a bit tricky because it is asynchronous, but I think I've wrapped my head around it and I hope you will too!

Wednesday, January 8, 2014

Asynchronous Storage (Game Programming)

Recently, I wrote this post (http://firefoxosgaming.blogspot.com/2014/01/local-storage-game-programming.html) on Local Storage, a simple way to store data on your phone between sessions. Simple, easy, works on all browsers. Or so I thought! But storage turns out to be more complicated.



I got some comments from Gabriele Svelto and Dietrich Ayala. They told me that Local Storage is not recommended for Firefox OS. Since they are both on the FxOS team, that's good enough for me. Turns out that for Firefox OS, Local Storage is a performance and I/O problem.

This isn't terribly surprising, giving how critical computing is on a phone with limited resources. One thing I've learned over the years is that synchronous programming can be a problem. What is synchronous programming? It's where you make a call and wait until the call is returned. That's an oversimplified answer, but when things are tight, you don't want to wait.

So if you use Local Storage, your call will take place and nothing else can happen while you are doing it. That can be fine if your calls are small and there's nothing else going on and you have plenty of resources. But you can't afford to have your phone freeze up.

The opposite of synchronous programming is ... wait for it, asynchronous programming. What a difference that first letter of the alphabet makes (if you're using the English alphabet - for the tinkopar, it would be t). Asynchronous programming says that you can make a call and then go do something else. You register a callback (a separate function) and when your call goes through, the function will respond and take action. How cool is that?

So Local Storage is out. Don't use it on Firefox OS. What's the alternative? If you are doing any kind of serious game, you want to let the player save their game for another day. And memorizing codes went out with old game consoles. Well, there's another way to save data, and its name is IndexedDB. This is a more recent way to save data, and is asynchronous. If you want to know more about it, see this page on MDN https://developer.mozilla.org/en-US/docs/IndexedDB. This has been around in Firefox for a long time, but other browsers were slow to catch up and Safari still isn't there at all and some of the other mobile browsers are AWOL as well. But it works in Firefox OS and works well.

However, the API for IndexedDB is a bit more complicated. It uses real database concepts and you need to understand how it works. It uses transactions and the DOM, but not SQL (I hate SQL, don't you?) Read more about it here on MDN: https://developer.mozilla.org/en-US/docs/IndexedDB/Basic_Concepts_Behind_IndexedDB

So you can't use Local Storage and you may not want to use IndexedDB if you're starting out. What is a game programmer to do?

Riding to the rescue of overwhelmed developers everywhere, Mozilla has made a compromise API that is almost as simple as Local Storage, but uses IndexedDB under the hood. This technology comes in the form of a small JavaScript library called async_storage.js. You can get it here: https://github.com/mozilla-b2g/gaia/blob/master/shared/js/async_storage.js. Copy it all and save it as a file named async_storage.js. But before you do, make sure you understand that this is open source under the Apache license and that you follow all the rules. If you don't know those rules, read them here: https://github.com/mozilla-b2g/gaia/blob/master/LICENSE.

Once you've got your async_storage.js file prepared, save it to your project. To use this cool piece of software, insert this line into your web page:

    <script src="async_storage.js"></script>

If the async_storage.js file isn't in your root, change the line above to tell the packager where it is.

What does this new asynchronous storage API do? Pretty much exactly what Local Storage did. It uses the same concepts and names, but with one small difference. Every method must include an extra function call as a callback. And there are no properties now, only methods.

Maybe an example would be nice. I'll use the same example I used in my Local Storage post: http://firefoxosgaming.blogspot.com/2014/01/local-storage-game-programming.html. Here's what the UI looks like.



Enter a key and value in the top two boxes and touch Save to ... save. Retrieve your data with a key you enter in the third box and touch Retrieve. Clear the storage with Clear and get the number of key/value pairs by touching the Number of Items button. Each action will produce a message at the top of the screen (where it says "Watch this space!"

And here's the new code, run and tested on my brand new shiny Geeksphone Peak.

<!DOCTYPE HTML>
<html>
 
  <head>
    <meta charset="UTF-8">
    <title>
      Async Storage
    </title>
   
    <script src="async_storage.js"></script>
 
    <script type="text/javascript">

        // Load event listener
      window.addEventListener("load", getLoaded, false);
       
      // Function called on page load.
      function getLoaded() { 
     
        // Save button listener
        myButtonSave.addEventListener(
          "mousedown", myClickSave,false);

        // Retrieve button listener
        myButtonLoad.addEventListener(
          "mousedown", myClickLoad,false);
         
        // Clear button listener
        myButtonClear.addEventListener(
          "mousedown", myStorageClear,false);
         
        // Length button listener
        myButtonLength.addEventListener(
          "mousedown", myStorageLength,false);       
         
        // Background color
        document.body.style.backgroundColor = "GreenYellow";

            // Output to console.
        console.log("The page is loaded.");
      }
     
      // Save button event handler
      function myClickSave() {
     
        // Save data with key.
        asyncStorage.setItem(myKey.value, myData.value,
          function() {
            console.log("New value stored.");
            info.innerHTML = "Input - Key: " + myKey.value +
              " Value: " + myData.value;      
        });     
      }
     
      // Retrieve button event handler
      function myClickLoad() {
     
        // Get data from key.
        asyncStorage.getItem(getKey.value,
          function(myValue) {
            console.log("The value is ", myValue);
            myValue = "Retrieve - Key: " + getKey.value +
              " Value: " + myValue;          
            info.innerHTML = myValue;
        });           
      }
     
      // Clear button event handler.
      function myStorageClear() {
     
        // Clear the storage.
        asyncStorage.clear(function() {
          console.log("Storage cleared!");
          info.innerHTML = "Storage cleared!";
        });
      }
 
      // Number of Items button event handler.
      function myStorageLength() {

        // Get the number of items in storage.
        asyncStorage.length(function(myLength) {
          console.log("The number of items is ",  length);
          info.innerHTML = "Items: " + myLength;        
        });      
      }
      
    </script>
  </head>
 
  <body>
 
    <p id="info">Watch this space!</p>
 
    <p> Enter key here</p>
    <input id="myKey" autofocus>
    <br>
   
    <p> Enter data here</p>
    <input id="myData">
    <br>
   
    <button id="myButtonSave">Save</button>
   
    <p> Retrieve data with this key</p>
    <input id="getKey"><br>
   
    <button id="myButtonLoad">Retrieve</button>
    <br><br>
   
    <button id="myButtonClear">Clear</button>
    <br><br>
   
    <button id="myButtonLength">Number of Items</button>

  </body>

</html>


Short and simple. Enter some key/value pairs, retrieve them, add more, count them, and clear. If you don't clear the database, and you shut down and come back tomorrow, the data will still be there!

The API details are actually in the JavaScript file, but here's a quick summary:

setItem - saves an item with a key/value pair.

getItem - gets an item value with a specific key.

removeItem - removes an item with a specific key.

clear - clears the database.

length - tells you how many key/value pairs are in the database.

key - gives you the value at a specific location in the database.

Note on the key API: the order stored isn't necessary guaranteed, so use this only to loop through all the items.

Sounds pretty much like the Local Storage API. But there are two differences:

1. You must use the asyncStorage object for all your calls and attach your methods to it.

2. Each method must have an additional parameter that specifies the function that will be called when the asynchronous call is done.

This last one is cool and not that hard. Here's an example:

Instead of typing:

        asyncStorage.clear();

You must type:

       asyncStorage.clear(myClearCallback);

Then create a function called myClearCallback() that will be called when the asynchronous call returns. You don't need to call it myClearCallback, any function name is fine as long as it is unique and has a function to go to.

If you don't want to create a separate function for your callback, you can use an anonymous function. Anonymous functions look like this and you use them inside the parameter. Here is an anonymous function:

       function() {
          // Stuff here you want to have happen
          // when the asynchronous call returns.
       }

It looks like this if you insert it into the asyncStorage.clear function:


        asyncStorage.clear(function() {
          console.log("Storage cleared!");
          info.innerHTML = "Storage cleared!";
        });
 

This displays a console.log message and a UI message when the asynchronous call returns.

The syntax is a bit overcrowded with punctuation, but you can always have a separate callback function if you want. By the way, callbacks are called by event listeners, I just haven't called them callbacks before.

This is a little different from programming you may have seen, in that you can no longer type something like:

       myValue = localStorage(key);

Values are not returned directly. That would be synchronous! So you must insert a callback function in your parameters. If the localStorage didn't have any parameters (like clear and length), you have to add one (and length is now a function, not a property). If localStorage had parameters, add one more!

I used anonymous functions for my four functions. Here is how each bit looks:

setItem

        // Save data with key.
        asyncStorage.setItem(myKey.value, myData.value,
          function() {
            console.log("New value stored.");
            info.innerHTML = "Input - Key: " + myKey.value +
              " Value: " + myData.value;      
        }); 
    

getItem

        // Get data from key.
        asyncStorage.getItem(getKey.value,
          function(myValue) {
            console.log("The value is ", myValue);
            myValue = "Retrieve - Key: " + getKey.value +
              " Value: " + myValue;          
            info.innerHTML = myValue;
        });       
    

clear

        // Clear the storage.
        asyncStorage.clear(function() {
          console.log("Storage cleared!");
          info.innerHTML = "Storage cleared!";
        });


length

        // Get the number of items in storage.
        asyncStorage.length(function(myLength) {
          console.log("The number of items is ",  length);
          info.innerHTML = "Items: " + myLength;        
        });  


myKey  and myData are defined by their input boxes.

So this isn't really that hard to use and lets you be asynchronous with your data. The folks at Mozilla were geniuses to think of a way to be asynchronous with an API that was synchronous. All you need to do is load in the tiny library and add a callback for each function you use.

NOTE: The callbacks are not required for setItem() and clear(). But I recommend using them so you can be sure your items are saved or cleared. Remember the poster on Mulder's wall: "Trust No One." You will need the callbacks for getItem() and length() because otherwise you won't get the data you requested.

NOTE: After more discussion, the callbacks don't tell you much more than something worked. If it didn't work, something very wrong happened. But usually they work. If you need more confidence about success and failure, check out my post on IndexedDB at http://firefoxosgaming.blogspot.com/2014/01/indexeddb-game-programming.html. async_storage.js uses IndexedDB under the hood so if you want more than simple, check out IndexedDB.



Wednesday, January 1, 2014

Local Storage (Game Programming)

One of the things you might want to do when you are programming games is to save the game for the player between sessions. After all, phone games are all about short bursts of play that may need to be interrupted. A player is standing in line and ... time to leave the game. But coming back tomorrow is definitely a good thing.



There are two ways to save information to memory for later. The easiest is called Local Storage and is supported and I will cover it here. There is another, more complicated way called IndexedDB that is also supported, and allows more complex database storage management, and that will be the subject of a later post. But for the simple games I'm exploring, Local Storage is easy to use and doesn't require understanding of database theory. Local Storage has cousins named global and session, and you can read about all the storage family at https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage.

Local storage is very simple. You have some text data that you want to store. You store it with a specific name and retrieve it later by using that same name (called a key). This isn't necessarily data you want to sort through like you would with a database (that's what IndexedDB is for). You want to save and load, and that's it. The saving and loading takes place in a manner called synchronous, which means everything waits while you save your data. But since the data isn't across a network or to hard disk, the saving is pretty fast. You can't store more than 5MB, so this API is strictly for small potatoes.

If you know what cookies are, this API does much the same thing, but cookies are limited to 2KB. So use this for saving the status of your game, not a collection of last year's Twitter posts.

So, here's the code for a simple example of Local Storage.

<!DOCTYPE HTML>
<html>
 
  <head>
    <meta charset="UTF-8">
    <title>
      Local Storage
    </title>
 
    <script type="text/javascript">

        // Load event listener
      window.addEventListener("load", getLoaded, false);
       
      // Function called on page load.
      function getLoaded() { 
     
        // Save button listener
        myButtonSave.addEventListener(
          "mousedown", myClickSave,false);

        // Retrieve button listener
        myButtonLoad.addEventListener(
          "mousedown", myClickLoad,false);
         
        // Clear button listener
        myButtonClear.addEventListener(
          "mousedown", myStorageClear,false);
         
        // Length button listener
        myButtonLength.addEventListener(
          "mousedown", myStorageLength,false);       
         
        // Background color
        document.body.style.backgroundColor = "GreenYellow";

            // Output to console.
        console.log("The page is loaded.");
      }
     
      // Save button event handler
      function myClickSave() {
     
        // Display save inputs.
        info.innerHTML = "Input - Key: " + myKey.value +
          " Value: " + myData.value;
         
        // Save to local storage.
        localStorage.setItem(myKey.value, myData.value);     
      }
     
      // Retrieve buton event handler
      function myClickLoad() {
     
        // Get data from key.
        myValue = localStorage.getItem(getKey.value);
       
        // Create retrieve text string.
        myValue = "Retrieve - Key: " + getKey.value +
          " Value: " + myValue;
       
        // Display retrieve text string.
        info.innerHTML = myValue;  
      }
     
      // CLear the storage.
      function myStorageClear() {
     
        // Clear the storage.
        localStorage.clear();

        // Display storage cleared message.
        info.innerHTML = "Storage cleared!";   
      }
 
      // Determine how many storage items.
      function myStorageLength() {
     
        // Get the number of items.
        myLength = localStorage.length;
       
        // Display the number of items.
        info.innerHTML = "Items: " + myLength;
      }
 
      
    </script>
  </head>
 
  <body>
 
    <p id="info">Watch this space!</p>
 
    <p> Enter key here</p>
    <input id="myKey" autofocus>
    <br>
   
    <p> Enter data here</p>
    <input id="myData">
    <br>
   
    <button id="myButtonSave">Save</button>
   
    <p> Retrieve data with this key</p>
    <input id="getKey"><br>
   
    <button id="myButtonLoad">Retrieve</button>
    <br><br>
   
    <button id="myButtonClear">Clear</button>
    <br><br>
   
    <button id="myButtonLength">Number of Items</button>

  </body>

</html>


When you load it into the Firefox OS Simulator or into your Firefox OS phone, it looks like this:



To get started, press on the box below "Enter key here" and enter a word that will be the key for your data. Next, go to the box below it and enter the data you want to save. Then press the button that says "Save". I picked "a" for the key and "b" for the data, but any text (numbers and letters will do). The results should look like this:


The values you entered should be at the top (where it used to say "Watch this space!"), You've now entered data with key. The data will stay on your phone until you clear it out. Kill the app and when you bring it back, the data will be there. (You kill an app by doing a long press on the Home button and clicking on the upper left of the app you want to kill.)

Next, enter another key-value pair. I picked "c" and "d" and pressed Save. My new results should look like this:


We now have two key-value pairs. If you'd like to verify this, press the "Number of Items" button and you should see this:


And now the exciting part. Press on the box just below "Retrieve data with this key" and type in a key that is associated with some data. I picked the key "a" and pressed the "Retrieve" button. Here is what I got:


The key "a" retrieved the value "b". Remember, you can use any text for the key and any text for the data you store. If you want to store something that is not text, you must convert it to a number; easy to do in JavaScript. In my example, if you type the number 1, it will be treated as text.

Next, when you're finished with your testing, clear out the storage by pressing on the "Clear" button.


You'll see the message at the top, "Storage cleared!" And finally, to see that the storage is gone, press on the "Number of items" and you'll get this:


The number of items is 0!

The code is actually pretty simple. It follows the pattern that I've been using in other examples. The buttons are created in the body of the page (and I just laid them out loosely with break tags). Each button has an event listener associated with it, and a function that will handle the event.

Local Storage API

The code for Local Storage is pretty simple. You use the localStorage object and its methods and properties. Here's a quick list of the methods and properties:

     setItem(key, data) - stores data by using a key

     getItem(key) - retrieves data by using a key.

     clear() - clears the storage. Note that this is a method and needs those parentheses.

     length - indicates the number of items. Note that this is a property and does not use parentheses.

The rest of the code uses the value of the input boxes (for example, the box with the id of "getKey" will be used in getItem as getKey.value) and display paragraphs (with the id of "info" will be used as info.innerHTML).

All in all, very simple. But crucial for making your player happy. They want to come back and play again, so make it easy for them! The Local Storage API is easy to use and works. So use it!

This post concludes my thoughts on what are the basics needed to create a game in HTML5 with Firefox OS. Moving, colliding, tilting, music, touching, etc. There's always more, but these are the simple tasks that most games will use. It's been fun for me to explore each one and I hope it's been interesting for you. Blogger tells me I've had a total of 12,000 page views since I started this blog in early October and I've written 60 posts. I don't know if that's a lot or not very much, but like I say, I'm having fun!

Next I'll start by making a complete (but small) game. I may even enter it in the One Game a Month contest http://www.onegameamonth.com/. I'd like to spend the next year exploring various game types (all simple and small) and I'm also thinking of exploring some of the game libraries such as Phaser (http://phaser.io/). After that, I'd like to talk about mechanics, a bit like Three Hundred Mechanics (http://www.squidi.net/three/) but at a lower level. And maybe put some games in the Marketplace. But right now I'm having fun just sorting out ideas and focusing on Firefox OS. I also might write a book. Well, that's a lot of stuff to do, but I am obviously interested in all this and having a lot of fun.

Stay tuned, but not iTuned!