File:AssetLoad.js
/**
* @module Core
* @namespace springroll
*/
(function(undefined)
{
var Debug,
Task = include('springroll.Task'),
EventDispatcher = include('springroll.EventDispatcher');
/**
* Class that represents a single multi load
* @class AssetLoad
* @private
* @extends springroll.EventDispatcher
* @constructor
* @param {springroll.AssetManager} manager Reference to the manager
*/
var AssetLoad = function(manager)
{
EventDispatcher.call(this);
if (DEBUG)
{
Debug = include('springroll.Debug', false);
}
/**
* Reference to the Task Manager
* @property {springroll.AssetManager} manager
*/
this.manager = manager;
if (DEBUG)
{
this.id = AssetLoad.ID++;
}
/**
* How to display the results, either as single (0), map (1) or list (2)
* @property {int} mode
* @default 1
*/
this.mode = MAP_MODE;
/**
* If we should run the tasks in parallel (true) or serial (false)
* @property {Boolean} startAll
* @default true
*/
this.startAll = true;
/**
* If we should try to cache all items in the load
* @property {Boolean} cacheAll
* @default false
*/
this.cacheAll = false;
/**
* The list of tasks to load
* @property {Array} tasks
*/
this.tasks = [];
/**
* The results to return when we're done
* @property {Array|Object} results
*/
this.results = null;
/**
* If the load is currently running
* @property {Boolean} running
* @default false
*/
this.running = false;
/**
* The total number of assets loaded
* @property {int} numLoaded
* @default 0
*/
this.numLoaded = 0;
/**
* The total number of assets
* @property {int} total
* @default 0
*/
this.total = 0;
/**
* The default asset type if not defined
* @property {String} type
* @default null
*/
this.type = null;
};
// Reference to prototype
var p = EventDispatcher.extend(AssetLoad);
/**
* When an asset is finished
* @event taskDone
* @param {*} result The loader result
* @param {Object} originalAsset The original load asset
* @param {Array} assets Collection to add additional assets to
*/
/**
* When all assets have been completed loaded
* @event complete
* @param {Array|Object} results The results of load
*/
/**
* Check how many assets have finished loaded
* @event progress
* @param {Number} percentage The amount loaded from 0 to 1
*/
if (DEBUG)
{
/**
* Debugging Keep track of how many we've created
* @property {int} ID
* @static
* @private
*/
AssetLoad.ID = 1;
/**
* Debugging purposes
* @method toString
*/
p.toString = function()
{
return "[AssetLoad (index: " + this.id + ")]";
};
}
/**
* Initialize the Load
* @method setup
* @param {Object|Array} assets The collection of assets to load
* @param {Object} [options] The loading options
* @param {Boolean} [options.startAll=true] If we should run the tasks in order
* @param {Boolean} [options.autoStart=true] Automatically start running
* @param {Boolean} [options.cacheAll=false] If we should run the tasks in order
* @param {String} [options.type] The default asset type of load, gets attached to each asset
*/
p.setup = function(assets, options)
{
// Save options to load
this.startAll = options.startAll;
this.cacheAll = options.cacheAll;
this.type = options.type;
// Update the results mode and tasks
this.mode = this.addTasks(assets);
// Set the default container for the results
this.results = getAssetsContainer(this.mode);
// Start running
if (options.autoStart)
{
this.start();
}
};
/**
* Start the load process
* @method start
*/
p.start = function()
{
// Empty load percentage
this.trigger('progress', 0);
// Keep track if we're currently running
this.running = true;
this.nextTask();
};
/**
* Set back to the original state
* @method reset
*/
p.reset = function()
{
// Cancel any tasks
this.tasks.forEach(function(task)
{
task.status = Task.FINISHED;
task.destroy();
});
this.total = 0;
this.numLoaded = 0;
this.mode = MAP_MODE;
this.tasks.length = 0;
this.results = null;
this.type = null;
this.startAll = true;
this.cacheAll = false;
this.running = false;
};
/**
* The result is a single result
* @property {int} SINGLE_MODE
* @private
* @final
* @static
* @default 0
*/
var SINGLE_MODE = 0;
/**
* The result is a map of result objects
* @property {int} MAP_MODE
* @private
* @final
* @static
* @default 1
*/
var MAP_MODE = 1;
/**
* The result is an array of result objects
* @property {int} LIST_MODE
* @private
* @final
* @static
* @default 2
*/
var LIST_MODE = 2;
/**
* Create a list of tasks from assets
* @method addTasks
* @private
* @param {Object|Array} assets The assets to load
*/
p.addTasks = function(assets)
{
var asset;
var mode = MAP_MODE;
// Apply the defaults incase this is a single
// thing that we're trying to load
assets = applyDefaults(assets);
// Check for a task definition on the asset
// add default type for proper task recognition
if (assets.type === undefined && this.type)
{
assets.type = this.type;
}
var isSingle = this.getTaskByAsset(assets);
if (isSingle)
{
this.addTask(assets);
return SINGLE_MODE;
}
else
{
//if we added a default type for task recognition, remove it
if (assets.type === this.type && this.type)
{
delete assets.type;
}
var task;
if (Array.isArray(assets))
{
for (var i = 0; i < assets.length; i++)
{
asset = applyDefaults(assets[i]);
task = this.addTask(asset);
if (!task.id)
{
// If we don't have the id to return
// a mapped result, we'll fallback to array results
mode = LIST_MODE;
}
}
}
else if (Object.isPlain(assets))
{
for (var id in assets)
{
asset = applyDefaults(assets[id]);
task = this.addTask(asset);
if (!task.id)
{
task.id = id;
}
}
}
else if (DEBUG && Debug)
{
Debug.error("Asset type unsupported", asset);
}
}
return mode;
};
/**
* Convert assets into object defaults
* @method applyDefaults
* @private
* @static
* @param {*} asset The function to convert
* @return {Object} The object asset to use
*/
function applyDefaults(asset)
{
// convert to a LoadTask
if (isString(asset))
{
return {
src: asset
};
}
// convert to a FunctionTask
else if (isFunction(asset))
{
return {
async: asset
};
}
return asset;
}
/**
* Load a single asset
* @method addTask
* @private
* @param {Object} asset The asset to load,
* can either be an object, URL/path, or async function.
*/
p.addTask = function(asset)
{
if (asset.type === undefined && this.type)
{
asset.type = this.type;
}
var TaskClass = this.getTaskByAsset(asset);
var task;
if (TaskClass)
{
if (asset.cache === undefined && this.cacheAll)
{
asset.cache = true;
}
task = new TaskClass(asset);
this.tasks.push(task);
++this.total;
}
else if (true && Debug)
{
Debug.error("Unable to find a task definition for asset", asset);
}
return task;
};
/**
* Get the Task definition for an asset
* @method getTaskByAsset
* @private
* @static
* @param {Object} asset The asset to check
* @return {Function} The Task class
*/
p.getTaskByAsset = function(asset)
{
var TaskClass;
var taskDefs = this.manager.taskDefs;
// Loop backwards to get the registered tasks first
// then will default to the basic Loader task
for (var i = 0, len = taskDefs.length; i < len; i++)
{
TaskClass = taskDefs[i];
if (TaskClass.test(asset))
{
return TaskClass;
}
}
return null;
};
/**
* Run the next task that's waiting
* @method nextTask
* @private
*/
p.nextTask = function()
{
var tasks = this.tasks;
for (var i = 0; i < tasks.length; i++)
{
var task = tasks[i];
if (task.status === Task.WAITING)
{
task.status = Task.RUNNING;
task.start(this.taskDone.bind(this, task));
// If we aren't running in parallel, then stop
if (!this.startAll) return;
}
}
};
/**
* Handler when a task has completed
* @method taskDone
* @private
* @param {springroll.Task} task Reference to original task
* @param {*} [result] The result of load
*/
p.taskDone = function(task, result)
{
// Ignore if we're destroyed
if (!this.running) return;
// Default to null
result = result || null;
var index = this.tasks.indexOf(task);
// Task was already removed, because a clear
if (index === -1)
{
return;
}
// Remove the completed task
this.tasks.splice(index, 1);
// Assets
var assets = [];
// Handle the file load tasks
if (result)
{
// Handle the result
switch (this.mode)
{
case SINGLE_MODE:
this.results = result;
break;
case LIST_MODE:
this.results.push(result);
break;
case MAP_MODE:
this.results[task.id] = result;
break;
}
// Should we cache the task?
if (task.cache)
{
this.manager.cache.write(task.id, result);
}
}
// If the task has a complete method
// we'll make sure that gets called
// with a reference to the tasks
// can potentially add more
if (task.complete)
{
task.complete(result, task.original, assets);
}
// Asset is finished
this.trigger('taskDone', result, task.original, assets);
task.destroy();
// Add new assets to the things to load
var mode = this.addTasks(assets);
// Update the progress total
this.trigger('progress', ++this.numLoaded / this.total);
// Check to make sure if we're in
// map mode, we keep it that way
if (this.mode === MAP_MODE && mode !== this.mode)
{
if (DEBUG && Debug)
{
Debug.error("Load assets require IDs to return mapped results", assets);
return;
}
else
{
throw "Assets require IDs";
}
}
if (this.tasks.length)
{
// Run the next task
this.nextTask();
}
else
{
// We're finished!
this.trigger('complete', this.results);
}
};
/**
* Get an empty assets collection
* @method getAssetsContainer
* @private
* @param {int} mode The mode
* @return {Array|Object|null} Empty container for assets
*/
var getAssetsContainer = function(mode)
{
switch (mode)
{
case SINGLE_MODE:
return null;
case LIST_MODE:
return [];
case MAP_MODE:
return {};
}
};
/**
* Destroy this and discard
* @method destroy
*/
p.destroy = function()
{
EventDispatcher.prototype.destroy.call(this);
this.reset();
this.tasks = null;
this.manager = null;
};
/**
* Check if an object is an String type
* @method isString
* @private
* @param {*} obj The object
* @return {Boolean} If it's an String
*/
function isString(obj)
{
return typeof obj == "string";
}
/**
* Check if an object is an function type
* @method isFunction
* @private
* @param {*} obj The object
* @return {Boolean} If it's an function
*/
function isFunction(obj)
{
return typeof obj == "function";
}
// Assign to namespace
namespace('springroll').AssetLoad = AssetLoad;
}());