To solve the problem of loading assets in flash for once and for all, we’ve decided to release our nl.dpdk.loader Package containg our Loader class.
We’re very proud to release this package since it solves all problems with bulk asset loading and object/data mangement, at the same time making it extensible by other users to write their own loading schemes if necessary. On top of that, it is prioritized so you can rearrange loading priorities (so you can decide which files will always be loaded first in an application, or even change that at runtime), you can instantiate multiple Loader objects and they are automatically managed behind the scenes making sure that the loaded files are queued up and managed centrally behind the scenes. Data that is associated with the file you want to load is managed for you and keeps it’s association with the file to be loaded, so that when it has been loaded you will have easy access to the associated data. Each module in your application can instantiate it’s own Loader object but because of priorities that can be managed, each module can make sure that it behaves as a good citizen and set their own priority, so that important files can be set with a high priority and will be loaded before lower priority files, without each module knowing other stuff is being loaded at all.
It features file progress statistics about how many files have loaded, the bytes, how many files to load etc.
You can load different types of files (xml, bitmap data, swf, binary data, sound, text etc) and if they are not available by default (eg: zip files) you can easily write your own task to do that and plug it in the system and it will work with all the functionality already made available through the loader.
There are lots of details to cover and to explore which we will do in this article, and if you want more details, take a look at the source code to find out about the full power of the Loader.
In object-oriented programming, it is considered a good coding practice to make encapsulated classes which are responsible for one thing, and one thing only. Robert C. Martin aptly calls this the Single-Responsibility Principle, stating that there should only be one reason for a class to change.
The native ActionScript 3.0 loader fails in this regard, as it handles both loading and display of the loaded file. While this blend of responsibility may be a time saver when dealing with a limited number of files, it does become an issue if this amount grows. Making a gallery for example, can get very messy when you are trying to load a batch of images, before displaying them sequentially.
To solve this problem we have created our very own Loader package which handles loading issues not implemented in other bulk loading implementations. In many ways, the loader is a combination of a number of previously released dpdk packages, such as Collections and the task based sequence manager, making it incredibly powerful and easily extendable for customized use. Some of the core features include cross-instance prioritization, data association to a loading item, the ability to load any file of any file type and consume the incoming data in the way you like and progress statistics management. These will be elaborated in the examples below.
The basics
To start, we will create a loader instance, add some files, and start the loading sequence:
//create a new loader instance var loader:Loader = new Loader(); //add two images to the loader instance loader.add("www.dpdk.nl/images/001.jpg"); loader.add("www.dpdk.nl/images/002.jpg"); //start loading loader.execute();
Keen observers might note that the interface for the loader is very similar to that of our sequence framework. Like a sequence, the loader maintains a list of tasks, which are created internally. Different loader tasks exist for different file formats, and the appropriate task is chosen based on the file extension of the file you pass as the URL parameter in the add method. If no extension is available in the URL, you may explicitly pass a data type in the add method, using one of the constants in the DataTypes class.
Most common file types have been mapped to predetermined tasks, and any extension that cannot be matched is defaulted to a binary type. It is however possible to map any specific file extension to a loading task you created yourself. This task can then be plugged into the loader framework, and enjoy the full range of functionality it has to offer.
Cross instance prioritization
Another particularly powerful feature of our loader package is the ability to have different instances of the Loader class loading files in a prioritized queue, without having any knowledge of (and any dependency on) each other.
//create a new loader instance with a low priority var unimportantLoader:Loader = new Loader(Priorities.LOWEST); //add unimportant files unimportantLoader.add("trivial.txt"); unimportantLoader.add("insignificant.flv"); //create a new loader instance with a high priority var importantLoader:Loader = new Loader(Priorities.HIGHEST); //add important files importantLoader.add("crucial.swf"); importantLoader.add("fundamental.mp3"); //start loading unimportantLoader.execute(); importantLoader.execute();
Every task added to the loader shares the priority of that Loader instance, which can be set through the first optional parameter in the constructor of the Loader. Whenever a task is done executing, the LoaderManager running behind the scenes determines which Loader has the highest priority, and has it call the execute method on its current task. In the example above, both tasks in importantLoader will be executed before the tasks in unimportantLoader, regardless of the order in which their respective execute methods are called.
Every Loader is registered with LoaderManager upon instantiation, while LoaderManager itself is a Singleton. This makes it possible to execute multiple Loader instances throughout your application simultaneously, while ensuring that important files will always be loaded before less important ones.
Data association
The next feature we would like to highlight is data association through the second, optional, parameter of the add method on the Loader class. This untyped parameter allows you to create a coupling between the file you wish to load, and any data already available in your application, which can then be easily accessed after the file is finished loading. This is a very powerful feature and can make your coding life much easier. You can pass anything in this parameter, ranging from captions for loaded images to objects that need to be configured by loaded xml.
The example below will show how using this data parameter will speed up the development of a gallery.
function initializeGallery():void { //list of image url's var imageURLs:List = new LinkedList(); //...fill the list here... //list of image containers on the stage var imageContainers:List = new LinkedList(); //....fill the list here //loader that will load the images var loader:Loader = new Loader(); //iterates over the list, creating a loader task for each url var iterator:IIterator = imageURLs.iterator(); while(iterator.hasNext()) { //an image container is added as the data parameter for each load task loader.add(iterator.next(), imageContainers.removeFirst()); //this event will be dispatched every time an item finishes loading loader.addEventListener(LoaderItemEvent.DONE, itemDoneHandler); } //start loading loader.execute(); } function itemDoneHandler(event:LoaderItemEvent):void { //container to which the loaded item will be added var container:MovieClip = event.getData(); //the loaded image itself var image:Bitmap = event.getLoadedContent(); //adds the image to the container we passed as the data parameter of the add method container.addChild(image); }
The LoaderItemEvent carries all the data created by the Loader. The getData method will return anything you passed as the second parameter in the add method on the Loader, whereas getLoadedContent will return the loaded data itself. In addition, the URL of the loaded item is accessible through the getUrl method. If loading fails for any reason, a LoaderItemEvent.ERROR will be dispatched, with the error message available through the getError method.
By decoupling loading from display, we allow far greater flexibility than the native loader can offer, as now both the loader and the image container can be used, and reused independently.
Progress tracking
Progress tracking is another potential concern this package can alleviate. Any decent loader has some sort of visual progress indication, and our Loader provides a number of features that can be used to feed this visualization with progress information.
For use cases where you need to track the progress of each file individually, the loader dispatches a LoaderItemProgressEvent, which holds a numeric representation of the progress of each item. In addition, the loader has two methods: getTotalFilesAdded and getTotalFilesLoaded, which could be used to add a fractural representation of the overall progress.
The Loader also provides ways of tracking overall progress for smooth progress visualizations. The major issue here is knowing the total file size in bytes. If this information is available to you, either through a CMS or brute force, this number can be passed as the second constructor parameter of the Loader, like this:
//sets the total size of files to be loaded to 9000 bytes var loader:Loader = new Loader(Priorities.NORMAL, 9000);
If this size is not known however, two solutions remain. One would be to call the getProgressOfFiles method on the Loader instance after each task completes. This can get rather choppy however, as it divides the number of completed tasks by the total tasks added to the Loader. A better solution might be to use the lengthy, but descriptively named AddToLoaderWithContentSizeTask.
//the loader instance var loader:Loader = new Loader(); //the sequence which populate the loader var sequence:Sequence = new Sequence(); //adds a magical task that determines the size of the file, and adds it to the loader sequence.add(new AddToLoaderWithContentSizeTask(loader, "IWishIKnewYourSize.swf")); sequence.add(new AddToLoaderWithContentSizeTask(loader, "ButNowIDontNeedTo.swf")); //executes the loader once the files have been added to it. sequence.add(new CallbackTask(loader.execute)); //start the sequence sequence.execute();
In truth, this tasks is not magical, it just works hard. It creates a native loader instance which starts loading, but is immediately stopped once the size of the given file is known to Flash. This size is then saved, and passed along with the other parameters to the loader instance passed to the constructor of the task. While this is incredibly convenient, it does increase the total loading time, as each file starts to load first to get the bytesize, and then immediately stops loading when this is in. This creates a little asynchronous overhead in time, but notice that the file is only loaded so as to get the http header in and determine filesize. When this information has been retrieved, further loading of the file does not happen.
The Loader has a very simple api and is very flexible in the way you can use it, offering various features that are relevant for power users. These will be handled in other articles or you can read the source code and find out for yourself
The flexibility offered here is present throughout this package, and offers time saving solutions of a vast array of potential use cases. Moreover, the existing code can be build upon to meet any challenge that might arise. The loader has been successfully unit-tested and the individual functions have been commented, so have a look at the source code if any questions remain.
I think the name “Loader” is confusing, since Flash already has a “Loader” class.
I don’t think our server guys like the idea of us starting two-requests for every file we load, just to get the size header: it creates extra server load and messes up their error reporting. If we deploy this in a site that’s hosted on a CDN those people will go berzerk if the CDN cache was just flushed and every request goes to the source-server (that will be hit twice as hard).
Also naming it Loader is really confusing. I think its bad form to alias a native class (it’s technically correct with the packages, but using this in a smart editor like FDT will mess your auto-suggestion something horrid).
How is this better then LoaderMax or the arcane BulkLoader? Both are solutions that dont include a lot of extra code, while for this solution we’ll get the a huge chunk of the dpdk libary.
Hey guys,
I can understand the confusion about the naming part. We’ll look into that.
@Bart: good post. by default there is just one request, which loads the file, so I think you misunderstood what happens.
We do however offer the possibility to use a combination of a Sequence and a Loader to be able to fetch the header of the file which contains the filesize, so you can accurately get the total byte size of the files you are about to load. In that way, you can get accurate loading progress without knowing the filesize in advance, it does however need a littlebit of extra time when starting the loader to ‘touch’ the files. The Sequence and Tasks are part of our package and we like to use building blocks with which we can create different functionality, which is one of the things OOP allows you to do. In this way, we did not have to burden our Loader with this functionality, while at the same time allowing it to happen with the use of another specialized package.
About filesize, we use the package as is, and it is not optimized for a small footprint since we use most of the classes in it in our projects. LoaderMax has been made with a small footprint and no dependencies, our Loader is just a part of this Library. I’m not sure if LoaderMax is tested, but our package is unittested and as such we build on components that work and have proven themselves.
LoaderMax seems to have the same functionalities as we do, but we hadn’t heard of it before, since we’ve been using the Loader itself for quite some time now, it seems LoaderMax is fairly new? Both packages seem to be able to do about the same but I’m not sure if LoaderMax has object association with it, the ability to have data associated with a loading item. Also, we also have a simple asset manager that integrates with the loader.
I feel our api is simpler since you don’t burden the client of our code with the knowledge of which loader to instantiate, the api is conform a lot of other classes in our package which makes it very consistent, it is better in OOP setup, since it doesn’t concern itself with how the loaded content is used (no DisplayObject association), but it CAN be done via object association. It can be extended and configured with new Tasks to suit your specific need but comes with sensible defaults for most use cases.
THe philosophy behind the loaders is a little different but they can both have their place. A matter of preference and your use cases I guess.
May I maybe repost this to at least one of my sites on this subject? I’d link back to the unique, of program. Let myself know by email everything you think?
greetings, fantastic blog page, and a good understand! at least one for my book marks.
Fine information, We are checking back frequent to find refreshes.