views:

1220

answers:

2

I've been trying to create an universal asset loader class (with help of the folks here at stackoverflow), which remembers previousely downloaded assets by storing them in an associative array.

This is the end result:

AssetLoader.as

package
{
    import flash.display.Loader;
    import flash.events.Event;
    import flash.net.URLRequest;
    import flash.utils.ByteArray;

    public final class AssetLoader extends Loader
    {
     public static var storedAssets:Object = {};
     private var postUrl:String;
     private var urlRequest:URLRequest;
     private var cached:Boolean = false;

     public final function AssetLoader(postUrl:String):void
     {
      this.postUrl = postUrl;
      if (storedAssets[postUrl])
      {
       cached = true;
      }
      else
      {
       urlRequest = new URLRequest(Settings.ASSETS_PRE_URL + postUrl);
       contentLoaderInfo.addEventListener(Event.COMPLETE, OnAssetLoadComplete);
      }
     }

     //starts loading the asset
     public final function loadAsset():void
     {
      if (cached)
      {
       loadBytes(storedAssets[postUrl]);
      }
      else
      {
       load(urlRequest);
      }
     }

     //runs when the asset download has been completed
     private final function OnAssetLoadComplete(event:Event):void
     {
      storedAssets[postUrl] = contentLoaderInfo.bytes;
     }
    }
}

Settings.ASSETS_PRE_URL equals "http://site.com/assets/"

Now, my problem is that it is causing the client to crash whenever it tries to retrieve the caches version (the newly downloaded one does work) from the class:

var assetLdr:AssetLoader = new AssetLoader("ships/" + graphicId + ".gif");
assetLdr.contentLoaderInfo.addEventListener(Event.COMPLETE, onShipAssetComplete);
assetLdr.loadAsset();

private function onShipAssetComplete(event:Event):void
{
    var shipImage:Bitmap = Bitmap(event.target.loader.content);
    // Do stuff with shipImage
}

When the cached version is being loaded, I get the following error in dutch: "TypeError: Error #1034: Afgedwongen typeomzetting is mislukt: kan flash.display::MovieClip@5c13421 niet omzetten in flash.display.Bitmap. at GameShip/onShipAssetComplete()" - means something like "type convertion has failed, can not convert flash.display::MovieClip@... to flash.display.Bitmap".

So, I wonder, how should I extend this loader class and make it return a cached asset the right way? Is my way of storing the asset in the array invalid maybe? Or should I use something else than loadBytes in the AssetLoader method?

+1  A: 

I'm not sure why you are insistent on using the contentLoaderInfo if you are busy encapsulating the functionality -- go ahead and encapsulate the data too. Also, why store the bytes for an object instead of a simple reference to the actual object?

Here is an example of what I mean. Take a look at the one degenerate case ... that is a request that could be cached but isn't because the laoder is in the process of loading ...

package 
{

import flash.display.BitmapData;
import flash.display.Sprite;

public class TestAssetLoader extends Sprite
{
    public var loader:AssetLoader;
    public var degenerateLoader:AssetLoader;
    public var cachedLoader:AssetLoader;

    public function TestAssetLoader()
    {
        loader = new AssetLoader("picasso_blue_guitar.jpg");
        loader.addEventListener(AssetLoaderEvent.ASSET_LOAD_COMPLETE, handleAssetLoaded);
        loader.loadAsset();

        // NOTE: you'll have to think about this case ....
        // where an asset is in the process of loading when you get another request
        // e.g. it isn't yet cached but is already being loaded ... 
        degenerateLoader = new AssetLoader("picasso_blue_guitar.jpg");
        degenerateLoader.loadAsset();
    }

    private function handleAssetLoaded(event:AssetLoaderEvent):void
    {
        // here is your content
        // var myImage:Bitmap = Bitmap(event.content);

        // This is guaranteed to hit the cache
        cachedLoader = new AssetLoader("picasso_blue_guitar.jpg");
        cachedLoader.loadAsset();
    }
}
}

The changed up asset Loader:

package
{
    import flash.display.Loader;
    import flash.events.Event;
    import flash.net.URLRequest;

    public final class AssetLoader extends Loader
    {
        public static var ASSETS_PRE_URL:String = "";

        public static var storedAssets:Object = {};
        private var postUrl:String;

        public final function AssetLoader(_postUrl:String):void
        {
                postUrl = _postUrl;
        }

        //starts loading the asset
        public final function loadAsset():void
        {
            if(storedAssets[postUrl])
            {
                trace("cached load");

                var resource:DisplayObject = storedAssets[postUrl];

                if(resource is Bitmap)
                {
                    resource = new Bitmap(Bitmap(resource).bitmapData);
                }

                dispatchEvent(new AssetLoaderEvent(AssetLoaderEvent.ASSET_LOAD_COMPLETE, resource));
            }
            else
            {
                var urlRequest:URLRequest = new URLRequest(ASSETS_PRE_URL + postUrl);
             contentLoaderInfo.addEventListener(Event.COMPLETE, OnAssetLoadComplete);
                 load(urlRequest);
            }
        }

        //runs when the asset download has been completed
        private final function OnAssetLoadComplete(event:Event):void
        {
            trace("non-cached load");
            var loader:Loader = Loader(event.target.loader); 
            storedAssets[postUrl] = loader.content;
            dispatchEvent(new AssetLoaderEvent(AssetLoaderEvent.ASSET_LOAD_COMPLETE, loader.content));
        }
    }
}

And the event:

package
{
import flash.display.DisplayObject;
import flash.events.Event;

public class AssetLoaderEvent extends Event
{
    public static const ASSET_LOAD_COMPLETE:String = "AssetLoaderEvent_LoadComplete";

    public var  content:DisplayObject;

    public function AssetLoaderEvent(type:String, _content:DisplayObject, bubbles:Boolean=false, cancelable:Boolean=false)
    {
        content = _content; 
        super(type, bubbles, cancelable);
    }

    override public function clone():Event
    {
        return new AssetLoaderEvent(type, content, bubbles, cancelable); 
    }

    override public function toString():String
    {
        return "[AssettLoaderEvent] " + type;
    }
}
}
James Fassett
Thanks a lot but did you try it? I just implemented it in my code and the first image loads fine (downloaded), a few seconds later I call the second image (cached), and it shows fine. However, the first image suddenly disappears when the second (cached) image is loaded. Any idea what could cause this?
Tom
Yup - my bad. I've updated the code to duplicate the Bitmap resource when getting the cached copy. You can't add the same DisplayObject to the stage with two different parents. You might want to instead store the bitmapData in the map instead of the Bitmap ... that's up to you.
James Fassett
Thanks. So I suppose I have to do the same kind of thing for sounds etc.?
Tom
I'm not certain for sounds - definitely anything that is added to the display list.
James Fassett
+1  A: 

Hi Tom.

You are probably aware of that, but there is an open source library for as3 (bulkloader) that does that and much more. (shameless plug here, since I am the author.

At the very least, reading through the source code might give you ideas on issues to tackle and maybe some implementation pointers.

Cheers Arthur Debert

Arthur Debert
Nice work! I didn't use it because I thought it was meant for images only, might be wrong here. It did seem to have too many features for what I want, which would lead to unneeded amounts of code.
Tom
It loads anything. It's 16kb compiled, if that's way to large, cut the loadingtypes you don't need and the convenience methods (getNetStream, getSound, etc), and you should be good to go.
Arthur Debert