Tuesday, 6 August 2013

a java-script game, Loading Images and Asynchronous vs. Synchronous

Web Browsers in general are inherently parallel entities.  They like to do things in parallel.  It makes web pages load a lot faster.  It makes perfect sense.  It does have implications on how things work.  Our neat model of doing things in steps doesn't fit that model quite as well.

There are several ways you could deal with this.  You could start loading the images you need, and start your game with an incomplete set of images and wait for the rest to arrive as they do.  Not very neat - but - possible.

The other way would be to wait for all the images to load.  The "please wait, loading..." approach.

For the purposes of our game, we assume we're loading a fixed set of images, and we load them 'up-front', before the game starts that is.  We will create a little library for doing just that.

We create an instance of the ImageLibrary class show below.  We call 'setup()' on the library which starts it loading the images in parallel.  Once all the images have loaded, the library calls us back.

Whats this callback stuff I keep talking about?  Well, in Computer-Science there are two ways of communicating with systems (with that I mean entities other than your own pogram).  Synchronous and Asynchronous.  Synchronous should be most familiar to you.  Suppose I want to load a file from disc, and there is a function provided by the Operating System called "loadFile( filename )".

In synchronous mode, I'd call / invoke loadFile("my file") and the program would what we call "block" until the loading of the file had completed.  In most cases, that block would be really short, almost unnoticeable.  The "blocking" means my program will be stopped until the loading (or whatever the remote entity needs to do) has completed.  We call it a "block" because we're passing control to another part of the system and effectively go to sleep ourselves until it is all done.  The trick is, you never really aware that you've been put to sleep.  As far as you're concerned, its just another step in the program.

In asynchronous mode you'd have to give "loadFile" one additional piece of information.  You'd have to tell "loadFile" who to notify of the completion of the task (in a lot of cases the system callback could also include information on what happened (e.g. successfully loaded the file, could not find the file, etc).  It is asynchronous because it doesn't block our program.  But at the same time, our program doesn't get what it needs immediately either.  The advantage of asynchronous is that we can start doing other things while we wait.  Sometimes that isn't an option.  It could be that we need whatever it is right here and now.  You can use "asynchronous mode" in a synchronous fashion (Don't fret.  Most of the time, computer programs behave in a synchronous fashion).

Whether something is synchronous or asynchronous is not up to us.  It is part of whatever library / function  you're using at the time.

You could do the following for instance to turn something inherently asynchronous into a synchronous step.

// a variable for waiting
var stillWaiting = true; // we start 'waiting'

// the callback function
function notificationDone()
{
  stillWaiting = false; // no longer waiting
}

// a function to go to sleep or a second
function sleepForASecond()
{
  var date = new Date();  // get current date/time
  var curDate = null;
  // wait for 1000 milli seconds
  do
  {
    curDate = new Date(); // update the time
  }
  while ( (curDate - date) < 1000 ); // 1 second = 1000 milliseconds
}

// some asynchronous load that calls 'notificationDone'
// when finished
loadFile( "some file", notificationDone );

// a little loop that repeats forever until
// loadFile calls back
while ( stillWaiting )
{
  sleepForASecond(); // wait for a second
}

There is always a danger with asynchronous callbacks that you never get the callback.  This can happen if something goes terribly wrong.  Worse, the thing you're waiting for takes a long time to complete due to unforeseen circumstances.  I've seen web-servers return a byte every few seconds.  Technically that isn't considered a time-out.  It however results in pages taking hours to complete.  I'm sure you've experienced it yourself in your own web-browser.  The only thing you can do is to 'stop' the page loading, and restart ('refresh') the page starting from scratch.

You could change the while loop above to include a time limit.

// get the current time in milliseconds and add 5 seconds to it
var counter = 0;
while ( stillWaiting && counter < 5 )
{
  sleepForASecond();
  counter = counter + 1;
}

if ( stillWaiting )
{
  alert("we didn't get what we were waiting for, time-out!");
}

This is the same while loop we had before, but it will only ever wait for five seconds (unless the callback completes before then).

In general - I wouldn't recommend you use the "while () { sleep }" approach in java-script.  It works well in other languages like Java or C# where you have more control over the process itself, but isn't that elegant in java-script.

Lets look at our image library inside an html page.  You might have guessed by now that java-scripts Image() load function is asynchronous (Setting an image's .src to a url starts it loading an image).  The library is designed to call us back when its done.  Don't forget our new friend jQuery, loaded right at the beginning.

<html>
<head>
  <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
</head>
<body>
  <div id="divPleaseWait">please wait, loading...</div>

  <script language="javascript">

  function ImageLibrary()
  {
    var self = this;
    var lookup = {}; // hashmap for name -> image
    var available = {}; // hashmap for name -> loaded (true/false)
    var parentCallback = null;

    // callback must implement a ready() method
    // and will be called from the image library
    // once all images have been loaded
    this.setup = function( callback )
    {
      var base = "http://peter3125.freewebhost.co.nz/";
      parentCallback = callback;
      // this is a list of all the images we want to load
      // I've given each image a "friendly" name and a filename
      this.load( "ship1", base + "img/gf5/ship1.png");
      this.load( "ship2", base + "img/gf5/ship2.png");
      this.load( "ship3", base + "img/gf5/ship3.png");
      this.load( "ship4", base + "img/gf5/ship4.png");
      this.load( "background", base + "img/gf5/background.png");
      this.load( "base", base + "img/gf5/base.png");
      this.load( "smallbase", base + "img/gf5/smallbase.png" );
      this.load( "explosion", base + "img/gf5/explosion.png");
      this.load( "turret", base + "img/gf5/turret.png");
      this.load( "exhaust", base + "img/gf5/exhaust2.png");
      this.load( "missile", base + "img/gf5/missile.png");
    };

    // setup a loader with a wait function
    this.load = function( str, src )
    {
        var img = new Image();
        // setup the internal callback function of the library
        img.onload = function() { self.loaded(str, src); };
        img.src = src; // do the loading
        lookup[str] = img;
        available[str] = false;
    };

    // get an image by name (for later)
    this.get = function( str )
    {
        return lookup[str];
    };

    // callback from the img.onload function
    this.loaded = function( str, src )
    {
        // note that this image
        // has now been loaded
        available[str] = true;

        // all images loaded?
        var allAvailable = true;
        $.each( available, function(i, item)
        {
            if ( !item )
            {
                // nope - found an image not yet loaded
                allAvailable = false;
            }
        });

        // callback the parent when we're done
        if ( allAvailable && parentCallback )
        {
            parentCallback();
        }
    };
  }

  // the callback function
  function callbackNotification()
  {
    $("#divPleaseWait").hide(); // hide the message
    // start the game, or something - we're ready!
    alert('all images have been loaded, we can start our game');
  }

  // create an instance of the image library
  var library = new ImageLibrary();
  // start the loading and give it a callback function
  library.setup( callbackNotification );
  </script>
</body>
</html>

The library just illustrates a point.  But its powerful enough for you to use in your own games.  We'll take a look later at how to use this library exactly in the game.

No comments:

Post a Comment