// ================================================================================================= // // Starling Framework // Copyright Gamua GmbH. All Rights Reserved. // // This program is free software. You can redistribute and/or modify it // in accordance with the terms of the accompanying license agreement. // // ================================================================================================= package starling.utils { import flash.display.Bitmap; import flash.display.Loader; import flash.display.LoaderInfo; import flash.events.HTTPStatusEvent; import flash.events.IOErrorEvent; import flash.events.ProgressEvent; import flash.events.SecurityErrorEvent; import flash.media.Sound; import flash.media.SoundChannel; import flash.media.SoundTransform; import flash.net.FileReference; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.net.URLRequest; import flash.system.ImageDecodingPolicy; import flash.system.LoaderContext; import flash.system.System; import flash.utils.ByteArray; import flash.utils.Dictionary; import flash.utils.describeType; import flash.utils.getQualifiedClassName; import flash.utils.setTimeout; import starling.core.Starling; import starling.events.Event; import starling.events.EventDispatcher; import starling.text.BitmapFont; import starling.text.TextField; import starling.textures.AtfData; import starling.textures.Texture; import starling.textures.TextureAtlas; import starling.textures.TextureOptions; /** Dispatched when all textures have been restored after a context loss. */ [Event(name="texturesRestored", type="starling.events.Event")] /** Dispatched when an URLLoader fails with an IO_ERROR while processing the queue. * The 'data' property of the Event contains the URL-String that could not be loaded. */ [Event(name="ioError", type="starling.events.Event")] /** Dispatched when an URLLoader fails with a SECURITY_ERROR while processing the queue. * The 'data' property of the Event contains the URL-String that could not be loaded. */ [Event(name="securityError", type="starling.events.Event")] /** Dispatched when an XML or JSON file couldn't be parsed. * The 'data' property of the Event contains the name of the asset that could not be parsed. */ [Event(name="parseError", type="starling.events.Event")] /** The AssetManager handles loading and accessing a variety of asset types. You can * add assets directly (via the 'add...' methods) or asynchronously via a queue. This allows * you to deal with assets in a unified way, no matter if they are loaded from a file, * directory, URL, or from an embedded object. * *
The class can deal with the following media types: *
For more information on how to add assets from different sources, read the documentation * of the "enqueue()" method.
* * Context Loss * *When the stage3D context is lost (and you have enabled 'Starling.handleLostContext'), * the AssetManager will automatically restore all loaded textures. To save memory, it will * get them from their original sources. Since this is done asynchronously, your images might * not reappear all at once, but during a timeframe of several seconds. If you want, you can * pause your game during that time; the AssetManager dispatches an "Event.TEXTURES_RESTORED" * event when all textures have been restored.
* * Error handling * *Loading of some assets may fail while the queue is being processed. In that case, the * AssetManager will dispatch events of type "IO_ERROR", "SECURITY_ERROR" or "PARSE_ERROR". * You can listen to those events and handle the errors manually (e.g., you could enqueue * them once again and retry, or provide placeholder textures). Queue processing will * continue even when those events are dispatched.
* * Using variable texture formats * *When you enqueue a texture, its properties for "format", "scale", "mipMapping", and * "repeat" will reflect the settings of the AssetManager at the time they were enqueued. * This means that you can enqueue a bunch of textures, then change the settings and enqueue * some more. Like this:
* *Beware that all references to the assets will remain intact, even though the assets * are no longer valid. Call 'purge' if you want to remove all resources and reuse * the AssetManager later.
*/ public function dispose():void { for each (var texture:Texture in _textures) texture.dispose(); for each (var atlas:TextureAtlas in _atlases) atlas.dispose(); for each (var xml:XML in _xmls) System.disposeXML(xml); for each (var byteArray:ByteArray in _byteArrays) byteArray.clear(); } // retrieving /** Returns a texture with a certain name. The method first looks through the directly * added textures; if no texture with that name is found, it scans through all * texture atlases. */ public function getTexture(name:String):Texture { if (name in _textures) return _textures[name]; else { for each (var atlas:TextureAtlas in _atlases) { var texture:Texture = atlas.getTexture(name); if (texture) return texture; } return null; } } /** Returns all textures that start with a certain string, sorted alphabetically * (especially useful for "MovieClip"). */ public function getTextures(prefix:String="", out:Vector.out
-vector, the names will be added to that vector. */
public function getTextureAtlasNames(prefix:String="", out:Vector.out
-vector, the names will be added to that vector. */
public function getSoundNames(prefix:String="", out:Vector.out
-vector, the names will be added to that vector. */
public function getXmlNames(prefix:String="", out:Vector.out
-vector, the names will be added to that vector. */
public function getObjectNames(prefix:String="", out:Vector.out
-vector, the names will be added to that vector. */
public function getByteArrayNames(prefix:String="", out:Vector.png, jpg, gif, atf, mp3, xml, fnt, json, binary
.static
embedded assets.Suitable object names are extracted automatically: A file named "image.png" will be * accessible under the name "image". When enqueuing embedded assets via a class, * the variable name of the embedded object will be used as its name. An exception * are texture atlases: they will have the same name as the actual texture they are * referencing.
* *XMLs that contain texture atlases or bitmap fonts are processed directly: fonts are * registered at the TextField class, atlas textures can be acquired with the * "getTexture()" method. All other XMLs are available via "getXml()".
* *If you pass in JSON data, it will be parsed into an object and will be available via * "getObject()".
*/ public function enqueue(...rawAssets):void { for each (var rawAsset:Object in rawAssets) { if (rawAsset is Array) { enqueue.apply(this, rawAsset); } else if (rawAsset is Class) { var typeXml:XML = describeType(rawAsset); var childNode:XML; if (_verbose) log("Looking for static embedded assets in '" + (typeXml.@name).split("::").pop() + "'"); for each (childNode in typeXml.constant.(@type == "Class")) enqueueWithName(rawAsset[childNode.@name], childNode.@name); for each (childNode in typeXml.variable.(@type == "Class")) enqueueWithName(rawAsset[childNode.@name], childNode.@name); } else if (getQualifiedClassName(rawAsset) == "flash.filesystem::File") { if (!rawAsset["exists"]) { log("File or directory not found: '" + rawAsset["url"] + "'"); } else if (!rawAsset["isHidden"]) { if (rawAsset["isDirectory"]) enqueue.apply(this, rawAsset["getDirectoryListing"]()); else enqueueWithName(rawAsset); } } else if (rawAsset is String || rawAsset is URLRequest) { enqueueWithName(rawAsset); } else { log("Ignoring unsupported asset type: " + getQualifiedClassName(rawAsset)); } } } /** Enqueues a single asset with a custom name that can be used to access it later. * If the asset is a texture, you can also add custom texture options. * * @param asset The asset that will be enqueued; accepts the same objects as the * 'enqueue' method. * @param name The name under which the asset will be found later. If you pass null or * omit the parameter, it's attempted to generate a name automatically. * @param options Custom options that will be used if 'asset' points to texture data. * @return the name with which the asset was registered. */ public function enqueueWithName(asset:Object, name:String=null, options:TextureOptions=null):String { var filename:String = null; if (getQualifiedClassName(asset) == "flash.filesystem::File") { filename = asset["name"]; asset = decodeURI(asset["url"]); } if (name == null) name = getName(asset); if (options == null) options = _defaultTextureOptions.clone(); else options = options.clone(); log("Enqueuing '" + (filename || name) + "'"); _queue.push({ name: name, asset: asset, options: options }); return name; } /** Loads all enqueued assets asynchronously. The 'onProgress' function will be called * with a 'ratio' between '0.0' and '1.0', with '1.0' meaning that it's complete. * *When you call this method, the manager will save a reference to "Starling.current"; * all textures that are loaded will be accessible only from within this instance. Thus, * if you are working with more than one Starling instance, be sure to call * "makeCurrent()" on the appropriate instance before processing the queue.
* * @param onProgressfunction(ratio:Number):void;
*/
public function loadQueue(onProgress:Function):void
{
if (onProgress == null)
throw new ArgumentError("Argument 'onProgress' must not be null");
if (_queue.length == 0)
{
onProgress(1.0);
return;
}
_starling = Starling.current;
if (_starling == null || _starling.context == null)
throw new Error("The Starling instance needs to be ready before assets can be loaded.");
const PROGRESS_PART_ASSETS:Number = 0.9;
const PROGRESS_PART_XMLS:Number = 1.0 - PROGRESS_PART_ASSETS;
var i:int;
var canceled:Boolean = false;
var xmls:Vector.The method has to transform this object into one of the types that the AssetManager * can work with, e.g. a Bitmap, a Sound, XML data, or a ByteArray. This object needs to * be passed to the 'onComplete' callback.
* *The calling method will then process this data accordingly (e.g. a Bitmap will be * transformed into a texture). Unknown types will be available via 'getObject()'.
* *When overriding this method, you can call 'onProgress' with a number between 0 and 1 * to update the total queue loading progress.
*/ protected function loadRawAsset(rawAsset:Object, onProgress:Function, onComplete:Function):void { var extension:String = null; var loaderInfo:LoaderInfo = null; var urlLoader:URLLoader = null; var urlRequest:URLRequest = null; var url:String = null; if (rawAsset is Class) { setTimeout(complete, 1, new rawAsset()); } else if (rawAsset is String || rawAsset is URLRequest) { urlRequest = rawAsset as URLRequest || new URLRequest(rawAsset as String); url = urlRequest.url; extension = getExtensionFromUrl(url); urlLoader = new URLLoader(); urlLoader.dataFormat = URLLoaderDataFormat.BINARY; urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onIoError); urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError); urlLoader.addEventListener(HTTP_RESPONSE_STATUS, onHttpResponseStatus); urlLoader.addEventListener(ProgressEvent.PROGRESS, onLoadProgress); urlLoader.addEventListener(Event.COMPLETE, onUrlLoaderComplete); urlLoader.load(urlRequest); } function onIoError(event:IOErrorEvent):void { log("IO error: " + event.text); dispatchEventWith(Event.IO_ERROR, false, url); complete(null); } function onSecurityError(event:SecurityErrorEvent):void { log("security error: " + event.text); dispatchEventWith(Event.SECURITY_ERROR, false, url); complete(null); } function onHttpResponseStatus(event:HTTPStatusEvent):void { if (extension == null) { var headers:Array = event["responseHeaders"]; var contentType:String = getHttpHeader(headers, "Content-Type"); if (contentType && /(audio|image)\//.exec(contentType)) extension = contentType.split("/").pop(); } } function onLoadProgress(event:ProgressEvent):void { if (onProgress != null && event.bytesTotal > 0) onProgress(event.bytesLoaded / event.bytesTotal); } function onUrlLoaderComplete(event:Object):void { var bytes:ByteArray = transformData(urlLoader.data as ByteArray, url); var sound:Sound; if (bytes == null) { complete(null); return; } if (extension) extension = extension.toLowerCase(); switch (extension) { case "mpeg": case "mp3": sound = new Sound(); sound.loadCompressedDataFromByteArray(bytes, bytes.length); bytes.clear(); complete(sound); break; case "jpg": case "jpeg": case "png": case "gif": var loaderContext:LoaderContext = new LoaderContext(_checkPolicyFile); var loader:Loader = new Loader(); loaderContext.imageDecodingPolicy = ImageDecodingPolicy.ON_LOAD; loaderInfo = loader.contentLoaderInfo; loaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIoError); loaderInfo.addEventListener(Event.COMPLETE, onLoaderComplete); loader.loadBytes(bytes, loaderContext); break; default: // any XML / JSON / binary data complete(bytes); break; } } function onLoaderComplete(event:Object):void { urlLoader.data.clear(); complete(event.target.content); } function complete(asset:Object):void { // clean up event listeners if (urlLoader) { urlLoader.removeEventListener(IOErrorEvent.IO_ERROR, onIoError); urlLoader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError); urlLoader.removeEventListener(HTTP_RESPONSE_STATUS, onHttpResponseStatus); urlLoader.removeEventListener(ProgressEvent.PROGRESS, onLoadProgress); urlLoader.removeEventListener(Event.COMPLETE, onUrlLoaderComplete); } if (loaderInfo) { loaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, onIoError); loaderInfo.removeEventListener(Event.COMPLETE, onLoaderComplete); } // On mobile, it is not allowed / endorsed to make stage3D calls while the app // is in the background. Thus, we pause queue processing if that's the case. if (SystemUtil.isDesktop) onComplete(asset); else SystemUtil.executeWhenApplicationIsActive(onComplete, asset); } } // helpers /** This method is called by 'enqueue' to determine the name under which an asset will be * accessible; override it if you need a custom naming scheme. Note that this method won't * be called for embedded assets. * * @param rawAsset either a String, an URLRequest or a FileReference. */ protected function getName(rawAsset:Object):String { var name:String; if (rawAsset is String) name = rawAsset as String; else if (rawAsset is URLRequest) name = (rawAsset as URLRequest).url; else if (rawAsset is FileReference) name = (rawAsset as FileReference).name; if (name) { name = name.replace(/%20/g, " "); // URLs use '%20' for spaces name = getBasenameFromUrl(name); if (name) return name; else throw new ArgumentError("Could not extract name from String '" + rawAsset + "'"); } else { name = getQualifiedClassName(rawAsset); throw new ArgumentError("Cannot extract names for objects of type '" + name + "'"); } } /** This method is called when raw byte data has been loaded from an URL or a file. * Override it to process the downloaded data in some way (e.g. decompression) or * to cache it on disk. * *It's okay to call one (or more) of the 'add...' methods from here. If the binary * data contains multiple objects, this allows you to process all of them at once. * Return 'null' to abort processing of the current item.
*/ protected function transformData(data:ByteArray, url:String):ByteArray { return data; } /** This method is called during loading of assets when 'verbose' is activated. Per * default, it traces 'message' to the console. */ protected function log(message:String):void { if (_verbose) trace("[AssetManager]", message); } private function byteArrayStartsWith(bytes:ByteArray, char:String):Boolean { var start:int = 0; var length:int = bytes.length; var wanted:int = char.charCodeAt(0); // recognize BOMs if (length >= 4 && (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0xfe && bytes[3] == 0xff) || (bytes[0] == 0xff && bytes[1] == 0xfe && bytes[2] == 0x00 && bytes[3] == 0x00)) { start = 4; // UTF-32 } else if (length >= 3 && bytes[0] == 0xef && bytes[1] == 0xbb && bytes[2] == 0xbf) { start = 3; // UTF-8 } else if (length >= 2 && (bytes[0] == 0xfe && bytes[1] == 0xff) || (bytes[0] == 0xff && bytes[1] == 0xfe)) { start = 2; // UTF-16 } // find first meaningful letter for (var i:int=start; iContext3DTextureFormat
assigned to this property. @default "bgra" */
public function get textureFormat():String { return _defaultTextureOptions.format; }
public function set textureFormat(value:String):void { _defaultTextureOptions.format = value; }
/** Indicates if the underlying Stage3D textures should be created as the power-of-two based
* Texture
class instead of the more memory efficient RectangleTexture
.
* @default false */
public function get forcePotTextures():Boolean { return _defaultTextureOptions.forcePotTexture; }
public function set forcePotTextures(value:Boolean):void { _defaultTextureOptions.forcePotTexture = value; }
/** Specifies whether a check should be made for the existence of a URL policy file before
* loading an object from a remote server. More information about this topic can be found
* in the 'flash.system.LoaderContext' documentation. @default false */
public function get checkPolicyFile():Boolean { return _checkPolicyFile; }
public function set checkPolicyFile(value:Boolean):void { _checkPolicyFile = value; }
/** Indicates if atlas XML data should be stored for access via the 'getXml' method.
* If true, you can access an XML under the same name as the atlas.
* If false, XMLs will be disposed when the atlas was created. @default false. */
public function get keepAtlasXmls():Boolean { return _keepAtlasXmls; }
public function set keepAtlasXmls(value:Boolean):void { _keepAtlasXmls = value; }
/** Indicates if bitmap font XML data should be stored for access via the 'getXml' method.
* If true, you can access an XML under the same name as the bitmap font.
* If false, XMLs will be disposed when the font was created. @default false. */
public function get keepFontXmls():Boolean { return _keepFontXmls; }
public function set keepFontXmls(value:Boolean):void { _keepFontXmls = value; }
/** The maximum number of parallel connections that are spawned when loading the queue.
* More connections can reduce loading times, but require more memory. @default 3. */
public function get numConnections():int { return _numConnections; }
public function set numConnections(value:int):void { _numConnections = value; }
}
}