mirror of
https://github.com/TerryCavanagh/VVVVVV.git
synced 2024-09-27 16:57:25 +02:00
1303 lines
54 KiB
ActionScript
1303 lines
54 KiB
ActionScript
|
// =================================================================================================
|
||
|
//
|
||
|
// 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.
|
||
|
*
|
||
|
* <p>The class can deal with the following media types:
|
||
|
* <ul>
|
||
|
* <li>Textures, either from Bitmaps or ATF data</li>
|
||
|
* <li>Texture atlases</li>
|
||
|
* <li>Bitmap Fonts</li>
|
||
|
* <li>Sounds</li>
|
||
|
* <li>XML data</li>
|
||
|
* <li>JSON data</li>
|
||
|
* <li>ByteArrays</li>
|
||
|
* </ul>
|
||
|
* </p>
|
||
|
*
|
||
|
* <p>For more information on how to add assets from different sources, read the documentation
|
||
|
* of the "enqueue()" method.</p>
|
||
|
*
|
||
|
* <strong>Context Loss</strong>
|
||
|
*
|
||
|
* <p>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.</p>
|
||
|
*
|
||
|
* <strong>Error handling</strong>
|
||
|
*
|
||
|
* <p>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.</p>
|
||
|
*
|
||
|
* <strong>Using variable texture formats</strong>
|
||
|
*
|
||
|
* <p>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:</p>
|
||
|
*
|
||
|
* <listing>
|
||
|
* var appDir:File = File.applicationDirectory;
|
||
|
* var assets:AssetManager = new AssetManager();
|
||
|
*
|
||
|
* assets.textureFormat = Context3DTextureFormat.BGRA;
|
||
|
* assets.enqueue(appDir.resolvePath("textures/32bit"));
|
||
|
*
|
||
|
* assets.textureFormat = Context3DTextureFormat.BGRA_PACKED;
|
||
|
* assets.enqueue(appDir.resolvePath("textures/16bit"));
|
||
|
*
|
||
|
* assets.loadQueue(...);</listing>
|
||
|
*/
|
||
|
public class AssetManager extends EventDispatcher
|
||
|
{
|
||
|
// This HTTPStatusEvent is only available in AIR
|
||
|
private static const HTTP_RESPONSE_STATUS:String = "httpResponseStatus";
|
||
|
|
||
|
private var _starling:Starling;
|
||
|
private var _numLostTextures:int;
|
||
|
private var _numRestoredTextures:int;
|
||
|
private var _numLoadingQueues:int;
|
||
|
|
||
|
private var _defaultTextureOptions:TextureOptions;
|
||
|
private var _checkPolicyFile:Boolean;
|
||
|
private var _keepAtlasXmls:Boolean;
|
||
|
private var _keepFontXmls:Boolean;
|
||
|
private var _numConnections:int;
|
||
|
private var _verbose:Boolean;
|
||
|
private var _queue:Array;
|
||
|
|
||
|
private var _textures:Dictionary;
|
||
|
private var _atlases:Dictionary;
|
||
|
private var _sounds:Dictionary;
|
||
|
private var _xmls:Dictionary;
|
||
|
private var _objects:Dictionary;
|
||
|
private var _byteArrays:Dictionary;
|
||
|
|
||
|
/** helper objects */
|
||
|
private static var sNames:Vector.<String> = new <String>[];
|
||
|
|
||
|
/** Regex for name / extension extraction from URL. */
|
||
|
private static const NAME_REGEX:RegExp = /([^\?\/\\]+?)(?:\.([\w\-]+))?(?:\?.*)?$/;
|
||
|
|
||
|
/** Create a new AssetManager. The 'scaleFactor' and 'useMipmaps' parameters define
|
||
|
* how enqueued bitmaps will be converted to textures. */
|
||
|
public function AssetManager(scaleFactor:Number=1, useMipmaps:Boolean=false)
|
||
|
{
|
||
|
_defaultTextureOptions = new TextureOptions(scaleFactor, useMipmaps);
|
||
|
_textures = new Dictionary();
|
||
|
_atlases = new Dictionary();
|
||
|
_sounds = new Dictionary();
|
||
|
_xmls = new Dictionary();
|
||
|
_objects = new Dictionary();
|
||
|
_byteArrays = new Dictionary();
|
||
|
_numConnections = 3;
|
||
|
_verbose = true;
|
||
|
_queue = [];
|
||
|
}
|
||
|
|
||
|
/** Disposes all contained textures, XMLs and ByteArrays.
|
||
|
*
|
||
|
* <p>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.</p>
|
||
|
*/
|
||
|
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.<Texture>=null):Vector.<Texture>
|
||
|
{
|
||
|
if (out == null) out = new <Texture>[];
|
||
|
|
||
|
for each (var name:String in getTextureNames(prefix, sNames))
|
||
|
out[out.length] = getTexture(name); // avoid 'push'
|
||
|
|
||
|
sNames.length = 0;
|
||
|
return out;
|
||
|
}
|
||
|
|
||
|
/** Returns all texture names that start with a certain string, sorted alphabetically. */
|
||
|
public function getTextureNames(prefix:String="", out:Vector.<String>=null):Vector.<String>
|
||
|
{
|
||
|
out = getDictionaryKeys(_textures, prefix, out);
|
||
|
|
||
|
for each (var atlas:TextureAtlas in _atlases)
|
||
|
atlas.getNames(prefix, out);
|
||
|
|
||
|
out.sort(Array.CASEINSENSITIVE);
|
||
|
return out;
|
||
|
}
|
||
|
|
||
|
/** Returns a texture atlas with a certain name, or null if it's not found. */
|
||
|
public function getTextureAtlas(name:String):TextureAtlas
|
||
|
{
|
||
|
return _atlases[name] as TextureAtlas;
|
||
|
}
|
||
|
|
||
|
/** Returns all texture atlas names that start with a certain string, sorted alphabetically.
|
||
|
* If you pass an <code>out</code>-vector, the names will be added to that vector. */
|
||
|
public function getTextureAtlasNames(prefix:String="", out:Vector.<String>=null):Vector.<String>
|
||
|
{
|
||
|
return getDictionaryKeys(_atlases, prefix, out);
|
||
|
}
|
||
|
|
||
|
/** Returns a sound with a certain name, or null if it's not found. */
|
||
|
public function getSound(name:String):Sound
|
||
|
{
|
||
|
return _sounds[name];
|
||
|
}
|
||
|
|
||
|
/** Returns all sound names that start with a certain string, sorted alphabetically.
|
||
|
* If you pass an <code>out</code>-vector, the names will be added to that vector. */
|
||
|
public function getSoundNames(prefix:String="", out:Vector.<String>=null):Vector.<String>
|
||
|
{
|
||
|
return getDictionaryKeys(_sounds, prefix, out);
|
||
|
}
|
||
|
|
||
|
/** Generates a new SoundChannel object to play back the sound. This method returns a
|
||
|
* SoundChannel object, which you can access to stop the sound and to control volume. */
|
||
|
public function playSound(name:String, startTime:Number=0, loops:int=0,
|
||
|
transform:SoundTransform=null):SoundChannel
|
||
|
{
|
||
|
if (name in _sounds)
|
||
|
return getSound(name).play(startTime, loops, transform);
|
||
|
else
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/** Returns an XML with a certain name, or null if it's not found. */
|
||
|
public function getXml(name:String):XML
|
||
|
{
|
||
|
return _xmls[name];
|
||
|
}
|
||
|
|
||
|
/** Returns all XML names that start with a certain string, sorted alphabetically.
|
||
|
* If you pass an <code>out</code>-vector, the names will be added to that vector. */
|
||
|
public function getXmlNames(prefix:String="", out:Vector.<String>=null):Vector.<String>
|
||
|
{
|
||
|
return getDictionaryKeys(_xmls, prefix, out);
|
||
|
}
|
||
|
|
||
|
/** Returns an object with a certain name, or null if it's not found. Enqueued JSON
|
||
|
* data is parsed and can be accessed with this method. */
|
||
|
public function getObject(name:String):Object
|
||
|
{
|
||
|
return _objects[name];
|
||
|
}
|
||
|
|
||
|
/** Returns all object names that start with a certain string, sorted alphabetically.
|
||
|
* If you pass an <code>out</code>-vector, the names will be added to that vector. */
|
||
|
public function getObjectNames(prefix:String="", out:Vector.<String>=null):Vector.<String>
|
||
|
{
|
||
|
return getDictionaryKeys(_objects, prefix, out);
|
||
|
}
|
||
|
|
||
|
/** Returns a byte array with a certain name, or null if it's not found. */
|
||
|
public function getByteArray(name:String):ByteArray
|
||
|
{
|
||
|
return _byteArrays[name];
|
||
|
}
|
||
|
|
||
|
/** Returns all byte array names that start with a certain string, sorted alphabetically.
|
||
|
* If you pass an <code>out</code>-vector, the names will be added to that vector. */
|
||
|
public function getByteArrayNames(prefix:String="", out:Vector.<String>=null):Vector.<String>
|
||
|
{
|
||
|
return getDictionaryKeys(_byteArrays, prefix, out);
|
||
|
}
|
||
|
|
||
|
// direct adding
|
||
|
|
||
|
/** Register a texture under a certain name. It will be available right away.
|
||
|
* If the name was already taken, the existing texture will be disposed and replaced
|
||
|
* by the new one. */
|
||
|
public function addTexture(name:String, texture:Texture):void
|
||
|
{
|
||
|
log("Adding texture '" + name + "'");
|
||
|
|
||
|
if (name in _textures)
|
||
|
{
|
||
|
log("Warning: name was already in use; the previous texture will be replaced.");
|
||
|
_textures[name].dispose();
|
||
|
}
|
||
|
|
||
|
_textures[name] = texture;
|
||
|
}
|
||
|
|
||
|
/** Register a texture atlas under a certain name. It will be available right away.
|
||
|
* If the name was already taken, the existing atlas will be disposed and replaced
|
||
|
* by the new one. */
|
||
|
public function addTextureAtlas(name:String, atlas:TextureAtlas):void
|
||
|
{
|
||
|
log("Adding texture atlas '" + name + "'");
|
||
|
|
||
|
if (name in _atlases)
|
||
|
{
|
||
|
log("Warning: name was already in use; the previous atlas will be replaced.");
|
||
|
_atlases[name].dispose();
|
||
|
}
|
||
|
|
||
|
_atlases[name] = atlas;
|
||
|
}
|
||
|
|
||
|
/** Register a sound under a certain name. It will be available right away.
|
||
|
* If the name was already taken, the existing sound will be replaced by the new one. */
|
||
|
public function addSound(name:String, sound:Sound):void
|
||
|
{
|
||
|
log("Adding sound '" + name + "'");
|
||
|
|
||
|
if (name in _sounds)
|
||
|
log("Warning: name was already in use; the previous sound will be replaced.");
|
||
|
|
||
|
_sounds[name] = sound;
|
||
|
}
|
||
|
|
||
|
/** Register an XML object under a certain name. It will be available right away.
|
||
|
* If the name was already taken, the existing XML will be disposed and replaced
|
||
|
* by the new one. */
|
||
|
public function addXml(name:String, xml:XML):void
|
||
|
{
|
||
|
log("Adding XML '" + name + "'");
|
||
|
|
||
|
if (name in _xmls)
|
||
|
{
|
||
|
log("Warning: name was already in use; the previous XML will be replaced.");
|
||
|
System.disposeXML(_xmls[name]);
|
||
|
}
|
||
|
|
||
|
_xmls[name] = xml;
|
||
|
}
|
||
|
|
||
|
/** Register an arbitrary object under a certain name. It will be available right away.
|
||
|
* If the name was already taken, the existing object will be replaced by the new one. */
|
||
|
public function addObject(name:String, object:Object):void
|
||
|
{
|
||
|
log("Adding object '" + name + "'");
|
||
|
|
||
|
if (name in _objects)
|
||
|
log("Warning: name was already in use; the previous object will be replaced.");
|
||
|
|
||
|
_objects[name] = object;
|
||
|
}
|
||
|
|
||
|
/** Register a byte array under a certain name. It will be available right away.
|
||
|
* If the name was already taken, the existing byte array will be cleared and replaced
|
||
|
* by the new one. */
|
||
|
public function addByteArray(name:String, byteArray:ByteArray):void
|
||
|
{
|
||
|
log("Adding byte array '" + name + "'");
|
||
|
|
||
|
if (name in _byteArrays)
|
||
|
{
|
||
|
log("Warning: name was already in use; the previous byte array will be replaced.");
|
||
|
_byteArrays[name].clear();
|
||
|
}
|
||
|
|
||
|
_byteArrays[name] = byteArray;
|
||
|
}
|
||
|
|
||
|
// removing
|
||
|
|
||
|
/** Removes a certain texture, optionally disposing it. */
|
||
|
public function removeTexture(name:String, dispose:Boolean=true):void
|
||
|
{
|
||
|
log("Removing texture '" + name + "'");
|
||
|
|
||
|
if (dispose && name in _textures)
|
||
|
_textures[name].dispose();
|
||
|
|
||
|
delete _textures[name];
|
||
|
}
|
||
|
|
||
|
/** Removes a certain texture atlas, optionally disposing it. */
|
||
|
public function removeTextureAtlas(name:String, dispose:Boolean=true):void
|
||
|
{
|
||
|
log("Removing texture atlas '" + name + "'");
|
||
|
|
||
|
if (dispose && name in _atlases)
|
||
|
_atlases[name].dispose();
|
||
|
|
||
|
delete _atlases[name];
|
||
|
}
|
||
|
|
||
|
/** Removes a certain sound. */
|
||
|
public function removeSound(name:String):void
|
||
|
{
|
||
|
log("Removing sound '"+ name + "'");
|
||
|
delete _sounds[name];
|
||
|
}
|
||
|
|
||
|
/** Removes a certain Xml object, optionally disposing it. */
|
||
|
public function removeXml(name:String, dispose:Boolean=true):void
|
||
|
{
|
||
|
log("Removing xml '"+ name + "'");
|
||
|
|
||
|
if (dispose && name in _xmls)
|
||
|
System.disposeXML(_xmls[name]);
|
||
|
|
||
|
delete _xmls[name];
|
||
|
}
|
||
|
|
||
|
/** Removes a certain object. */
|
||
|
public function removeObject(name:String):void
|
||
|
{
|
||
|
log("Removing object '"+ name + "'");
|
||
|
delete _objects[name];
|
||
|
}
|
||
|
|
||
|
/** Removes a certain byte array, optionally disposing its memory right away. */
|
||
|
public function removeByteArray(name:String, dispose:Boolean=true):void
|
||
|
{
|
||
|
log("Removing byte array '"+ name + "'");
|
||
|
|
||
|
if (dispose && name in _byteArrays)
|
||
|
_byteArrays[name].clear();
|
||
|
|
||
|
delete _byteArrays[name];
|
||
|
}
|
||
|
|
||
|
/** Empties the queue and aborts any pending load operations. */
|
||
|
public function purgeQueue():void
|
||
|
{
|
||
|
_queue.length = 0;
|
||
|
dispatchEventWith(Event.CANCEL);
|
||
|
}
|
||
|
|
||
|
/** Removes assets of all types (disposing them along the way), empties the queue and
|
||
|
* aborts any pending load operations. */
|
||
|
public function purge():void
|
||
|
{
|
||
|
log("Purging all assets, emptying queue");
|
||
|
|
||
|
purgeQueue();
|
||
|
dispose();
|
||
|
|
||
|
_textures = new Dictionary();
|
||
|
_atlases = new Dictionary();
|
||
|
_sounds = new Dictionary();
|
||
|
_xmls = new Dictionary();
|
||
|
_objects = new Dictionary();
|
||
|
_byteArrays = new Dictionary();
|
||
|
}
|
||
|
|
||
|
// queued adding
|
||
|
|
||
|
/** Enqueues one or more raw assets; they will only be available after successfully
|
||
|
* executing the "loadQueue" method. This method accepts a variety of different objects:
|
||
|
*
|
||
|
* <ul>
|
||
|
* <li>Strings or URLRequests containing an URL to a local or remote resource. Supported
|
||
|
* types: <code>png, jpg, gif, atf, mp3, xml, fnt, json, binary</code>.</li>
|
||
|
* <li>Instances of the File class (AIR only) pointing to a directory or a file.
|
||
|
* Directories will be scanned recursively for all supported types.</li>
|
||
|
* <li>Classes that contain <code>static</code> embedded assets.</li>
|
||
|
* <li>If the file extension is not recognized, the data is analyzed to see if
|
||
|
* contains XML or JSON data. If it's neither, it is stored as ByteArray.</li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* <p>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.</p>
|
||
|
*
|
||
|
* <p>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()".</p>
|
||
|
*
|
||
|
* <p>If you pass in JSON data, it will be parsed into an object and will be available via
|
||
|
* "getObject()".</p>
|
||
|
*/
|
||
|
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.
|
||
|
*
|
||
|
* <p>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.</p>
|
||
|
*
|
||
|
* @param onProgress <code>function(ratio:Number):void;</code>
|
||
|
*/
|
||
|
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.<XML> = new <XML>[];
|
||
|
var assetInfos:Array = _queue.concat();
|
||
|
var assetCount:int = _queue.length;
|
||
|
var assetProgress:Array = [];
|
||
|
var assetIndex:int = 0;
|
||
|
|
||
|
for (i=0; i<assetCount; ++i)
|
||
|
assetProgress[i] = 0.0;
|
||
|
|
||
|
for (i=0; i<_numConnections; ++i)
|
||
|
loadNextQueueElement();
|
||
|
|
||
|
_queue.length = 0;
|
||
|
_numLoadingQueues++;
|
||
|
addEventListener(Event.CANCEL, cancel);
|
||
|
|
||
|
function loadNextQueueElement():void
|
||
|
{
|
||
|
if (assetIndex < assetInfos.length)
|
||
|
{
|
||
|
// increment asset index *before* using it, since
|
||
|
// 'loadQueueElement' could by synchronous in subclasses.
|
||
|
var index:int = assetIndex++;
|
||
|
loadQueueElement(index, assetInfos[index]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function loadQueueElement(index:int, assetInfo:Object):void
|
||
|
{
|
||
|
if (canceled) return;
|
||
|
|
||
|
var onElementProgress:Function = function(progress:Number):void
|
||
|
{
|
||
|
updateAssetProgress(index, progress * 0.8); // keep 20 % for completion
|
||
|
};
|
||
|
var onElementLoaded:Function = function():void
|
||
|
{
|
||
|
updateAssetProgress(index, 1.0);
|
||
|
assetCount--;
|
||
|
|
||
|
if (assetCount > 0) loadNextQueueElement();
|
||
|
else processXmls();
|
||
|
};
|
||
|
|
||
|
processRawAsset(assetInfo.name, assetInfo.asset, assetInfo.options,
|
||
|
xmls, onElementProgress, onElementLoaded);
|
||
|
}
|
||
|
|
||
|
function updateAssetProgress(index:int, progress:Number):void
|
||
|
{
|
||
|
assetProgress[index] = progress;
|
||
|
|
||
|
var sum:Number = 0.0;
|
||
|
var len:int = assetProgress.length;
|
||
|
|
||
|
for (i=0; i<len; ++i)
|
||
|
sum += assetProgress[i];
|
||
|
|
||
|
onProgress(sum / len * PROGRESS_PART_ASSETS);
|
||
|
}
|
||
|
|
||
|
function processXmls():void
|
||
|
{
|
||
|
// xmls are processed separately at the end, because the textures they reference
|
||
|
// have to be available for other XMLs. Texture atlases are processed first:
|
||
|
// that way, their textures can be referenced, too.
|
||
|
|
||
|
xmls.sort(function(a:XML, b:XML):int {
|
||
|
return a.localName() == "TextureAtlas" ? -1 : 1;
|
||
|
});
|
||
|
|
||
|
setTimeout(processXml, 1, 0);
|
||
|
}
|
||
|
|
||
|
function processXml(index:int):void
|
||
|
{
|
||
|
if (canceled) return;
|
||
|
else if (index == xmls.length)
|
||
|
{
|
||
|
finish();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var name:String;
|
||
|
var texture:Texture;
|
||
|
var xml:XML = xmls[index];
|
||
|
var rootNode:String = xml.localName();
|
||
|
var xmlProgress:Number = (index + 1) / (xmls.length + 1);
|
||
|
|
||
|
if (rootNode == "TextureAtlas")
|
||
|
{
|
||
|
name = getName(xml.@imagePath.toString());
|
||
|
texture = getTexture(name);
|
||
|
|
||
|
if (texture)
|
||
|
{
|
||
|
addTextureAtlas(name, new TextureAtlas(texture, xml));
|
||
|
removeTexture(name, false);
|
||
|
|
||
|
if (_keepAtlasXmls) addXml(name, xml);
|
||
|
else System.disposeXML(xml);
|
||
|
}
|
||
|
else log("Cannot create atlas: texture '" + name + "' is missing.");
|
||
|
}
|
||
|
else if (rootNode == "font")
|
||
|
{
|
||
|
name = getName(xml.pages.page.@file.toString());
|
||
|
texture = getTexture(name);
|
||
|
|
||
|
if (texture)
|
||
|
{
|
||
|
log("Adding bitmap font '" + name + "'");
|
||
|
TextField.registerCompositor(new BitmapFont(texture, xml), name);
|
||
|
removeTexture(name, false);
|
||
|
|
||
|
if (_keepFontXmls) addXml(name, xml);
|
||
|
else System.disposeXML(xml);
|
||
|
}
|
||
|
else log("Cannot create bitmap font: texture '" + name + "' is missing.");
|
||
|
}
|
||
|
else
|
||
|
throw new Error("XML contents not recognized: " + rootNode);
|
||
|
|
||
|
onProgress(PROGRESS_PART_ASSETS + PROGRESS_PART_XMLS * xmlProgress);
|
||
|
setTimeout(processXml, 1, index + 1);
|
||
|
}
|
||
|
|
||
|
function cancel():void
|
||
|
{
|
||
|
removeEventListener(Event.CANCEL, cancel);
|
||
|
_numLoadingQueues--;
|
||
|
canceled = true;
|
||
|
}
|
||
|
|
||
|
function finish():void
|
||
|
{
|
||
|
// We dance around the final "onProgress" call with some "setTimeout" calls here
|
||
|
// to make sure the progress bar gets the chance to be rendered. Otherwise, all
|
||
|
// would happen in one frame.
|
||
|
|
||
|
setTimeout(function():void
|
||
|
{
|
||
|
if (!canceled)
|
||
|
{
|
||
|
cancel();
|
||
|
onProgress(1.0);
|
||
|
}
|
||
|
}, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private function processRawAsset(name:String, rawAsset:Object, options:TextureOptions,
|
||
|
xmls:Vector.<XML>,
|
||
|
onProgress:Function, onComplete:Function):void
|
||
|
{
|
||
|
var canceled:Boolean = false;
|
||
|
|
||
|
addEventListener(Event.CANCEL, cancel);
|
||
|
loadRawAsset(rawAsset, progress, process);
|
||
|
|
||
|
function process(asset:Object):void
|
||
|
{
|
||
|
var texture:Texture;
|
||
|
var bytes:ByteArray;
|
||
|
var object:Object = null;
|
||
|
var xml:XML = null;
|
||
|
|
||
|
// the 'current' instance might have changed by now
|
||
|
// if we're running in a set-up with multiple instances.
|
||
|
_starling.makeCurrent();
|
||
|
|
||
|
if (canceled)
|
||
|
{
|
||
|
// do nothing
|
||
|
}
|
||
|
else if (asset == null)
|
||
|
{
|
||
|
onComplete();
|
||
|
}
|
||
|
else if (asset is Sound)
|
||
|
{
|
||
|
addSound(name, asset as Sound);
|
||
|
onComplete();
|
||
|
}
|
||
|
else if (asset is XML)
|
||
|
{
|
||
|
xml = asset as XML;
|
||
|
|
||
|
if (xml.localName() == "TextureAtlas" || xml.localName() == "font")
|
||
|
xmls.push(xml);
|
||
|
else
|
||
|
addXml(name, xml);
|
||
|
|
||
|
onComplete();
|
||
|
}
|
||
|
else if (_starling.context.driverInfo == "Disposed")
|
||
|
{
|
||
|
log("Context lost while processing assets, retrying ...");
|
||
|
setTimeout(process, 1, asset);
|
||
|
return; // to keep CANCEL event listener intact
|
||
|
}
|
||
|
else if (asset is Bitmap)
|
||
|
{
|
||
|
texture = Texture.fromData(asset, options);
|
||
|
texture.root.onRestore = function():void
|
||
|
{
|
||
|
_numLostTextures++;
|
||
|
loadRawAsset(rawAsset, null, function(asset:Object):void
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
if (asset == null) throw new Error("Reload failed");
|
||
|
texture.root.uploadBitmap(asset as Bitmap);
|
||
|
asset.bitmapData.dispose();
|
||
|
}
|
||
|
catch (e:Error)
|
||
|
{
|
||
|
log("Texture restoration failed for '" + name + "': " + e.message);
|
||
|
}
|
||
|
|
||
|
_numRestoredTextures++;
|
||
|
Starling.current.stage.setRequiresRedraw();
|
||
|
|
||
|
if (_numLostTextures == _numRestoredTextures)
|
||
|
dispatchEventWith(Event.TEXTURES_RESTORED);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
asset.bitmapData.dispose();
|
||
|
addTexture(name, texture);
|
||
|
onComplete();
|
||
|
}
|
||
|
else if (asset is ByteArray)
|
||
|
{
|
||
|
bytes = asset as ByteArray;
|
||
|
|
||
|
if (AtfData.isAtfData(bytes))
|
||
|
{
|
||
|
options.onReady = prependCallback(options.onReady, function():void
|
||
|
{
|
||
|
addTexture(name, texture);
|
||
|
onComplete();
|
||
|
});
|
||
|
|
||
|
texture = Texture.fromData(bytes, options);
|
||
|
texture.root.onRestore = function():void
|
||
|
{
|
||
|
_numLostTextures++;
|
||
|
loadRawAsset(rawAsset, null, function(asset:Object):void
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
if (asset == null) throw new Error("Reload failed");
|
||
|
texture.root.uploadAtfData(asset as ByteArray, 0, false);
|
||
|
asset.clear();
|
||
|
}
|
||
|
catch (e:Error)
|
||
|
{
|
||
|
log("Texture restoration failed for '" + name + "': " + e.message);
|
||
|
}
|
||
|
|
||
|
_numRestoredTextures++;
|
||
|
Starling.current.stage.setRequiresRedraw();
|
||
|
|
||
|
if (_numLostTextures == _numRestoredTextures)
|
||
|
dispatchEventWith(Event.TEXTURES_RESTORED);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
bytes.clear();
|
||
|
}
|
||
|
else if (byteArrayStartsWith(bytes, "{") || byteArrayStartsWith(bytes, "["))
|
||
|
{
|
||
|
try { object = JSON.parse(bytes.readUTFBytes(bytes.length)); }
|
||
|
catch (e:Error)
|
||
|
{
|
||
|
log("Could not parse JSON: " + e.message);
|
||
|
dispatchEventWith(Event.PARSE_ERROR, false, name);
|
||
|
}
|
||
|
|
||
|
if (object) addObject(name, object);
|
||
|
|
||
|
bytes.clear();
|
||
|
onComplete();
|
||
|
}
|
||
|
else if (byteArrayStartsWith(bytes, "<"))
|
||
|
{
|
||
|
try { xml = new XML(bytes); }
|
||
|
catch (e:Error)
|
||
|
{
|
||
|
log("Could not parse XML: " + e.message);
|
||
|
dispatchEventWith(Event.PARSE_ERROR, false, name);
|
||
|
}
|
||
|
|
||
|
process(xml);
|
||
|
bytes.clear();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
addByteArray(name, bytes);
|
||
|
onComplete();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
addObject(name, asset);
|
||
|
onComplete();
|
||
|
}
|
||
|
|
||
|
// avoid that objects stay in memory (through 'onRestore' functions)
|
||
|
asset = null;
|
||
|
bytes = null;
|
||
|
|
||
|
removeEventListener(Event.CANCEL, cancel);
|
||
|
}
|
||
|
|
||
|
function progress(ratio:Number):void
|
||
|
{
|
||
|
if (!canceled) onProgress(ratio);
|
||
|
}
|
||
|
|
||
|
function cancel():void
|
||
|
{
|
||
|
canceled = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** This method is called internally for each element of the queue when it is loaded.
|
||
|
* 'rawAsset' is typically either a class (pointing to an embedded asset) or a string
|
||
|
* (containing the path to a file). For texture data, it will also be called after a
|
||
|
* context loss.
|
||
|
*
|
||
|
* <p>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.</p>
|
||
|
*
|
||
|
* <p>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()'.</p>
|
||
|
*
|
||
|
* <p>When overriding this method, you can call 'onProgress' with a number between 0 and 1
|
||
|
* to update the total queue loading progress.</p>
|
||
|
*/
|
||
|
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.
|
||
|
*
|
||
|
* <p>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.</p> */
|
||
|
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; i<length; ++i)
|
||
|
{
|
||
|
var byte:int = bytes[i];
|
||
|
if (byte == 0 || byte == 10 || byte == 13 || byte == 32) continue; // null, \n, \r, space
|
||
|
else return byte == wanted;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private function getDictionaryKeys(dictionary:Dictionary, prefix:String="",
|
||
|
out:Vector.<String>=null):Vector.<String>
|
||
|
{
|
||
|
if (out == null) out = new <String>[];
|
||
|
|
||
|
for (var name:String in dictionary)
|
||
|
if (name.indexOf(prefix) == 0)
|
||
|
out[out.length] = name; // avoid 'push'
|
||
|
|
||
|
out.sort(Array.CASEINSENSITIVE);
|
||
|
return out;
|
||
|
}
|
||
|
|
||
|
private function getHttpHeader(headers:Array, headerName:String):String
|
||
|
{
|
||
|
if (headers)
|
||
|
{
|
||
|
for each (var header:Object in headers)
|
||
|
if (header.name == headerName) return header.value;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/** Extracts the base name of a file path or URL, i.e. the file name without extension. */
|
||
|
protected function getBasenameFromUrl(url:String):String
|
||
|
{
|
||
|
var matches:Array = NAME_REGEX.exec(url);
|
||
|
if (matches && matches.length > 0) return matches[1];
|
||
|
else return null;
|
||
|
}
|
||
|
|
||
|
/** Extracts the file extension from an URL. */
|
||
|
protected function getExtensionFromUrl(url:String):String
|
||
|
{
|
||
|
var matches:Array = NAME_REGEX.exec(url);
|
||
|
if (matches && matches.length > 1) return matches[2];
|
||
|
else return null;
|
||
|
}
|
||
|
|
||
|
private function prependCallback(oldCallback:Function, newCallback:Function):Function
|
||
|
{
|
||
|
// TODO: it might make sense to add this (together with "appendCallback")
|
||
|
// as a public utility method ("FunctionUtil"?)
|
||
|
|
||
|
if (oldCallback == null) return newCallback;
|
||
|
else if (newCallback == null) return oldCallback;
|
||
|
else return function():void
|
||
|
{
|
||
|
newCallback();
|
||
|
oldCallback();
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// properties
|
||
|
|
||
|
/** The queue contains one 'Object' for each enqueued asset. Each object has 'asset'
|
||
|
* and 'name' properties, pointing to the raw asset and its name, respectively. */
|
||
|
protected function get queue():Array { return _queue; }
|
||
|
|
||
|
/** Returns the number of raw assets that have been enqueued, but not yet loaded. */
|
||
|
public function get numQueuedAssets():int { return _queue.length; }
|
||
|
|
||
|
/** When activated, the class will trace information about added/enqueued assets.
|
||
|
* @default true */
|
||
|
public function get verbose():Boolean { return _verbose; }
|
||
|
public function set verbose(value:Boolean):void { _verbose = value; }
|
||
|
|
||
|
/** Indicates if a queue is currently being loaded. */
|
||
|
public function get isLoading():Boolean { return _numLoadingQueues > 0; }
|
||
|
|
||
|
/** For bitmap textures, this flag indicates if mip maps should be generated when they
|
||
|
* are loaded; for ATF textures, it indicates if mip maps are valid and should be
|
||
|
* used. @default false */
|
||
|
public function get useMipMaps():Boolean { return _defaultTextureOptions.mipMapping; }
|
||
|
public function set useMipMaps(value:Boolean):void { _defaultTextureOptions.mipMapping = value; }
|
||
|
|
||
|
/** Textures that are created from Bitmaps or ATF files will have the scale factor
|
||
|
* assigned here. @default 1 */
|
||
|
public function get scaleFactor():Number { return _defaultTextureOptions.scale; }
|
||
|
public function set scaleFactor(value:Number):void { _defaultTextureOptions.scale = value; }
|
||
|
|
||
|
/** Textures that are created from Bitmaps will be uploaded to the GPU with the
|
||
|
* <code>Context3DTextureFormat</code> 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
|
||
|
* <code>Texture</code> class instead of the more memory efficient <code>RectangleTexture</code>.
|
||
|
* @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; }
|
||
|
}
|
||
|
}
|