@awayjs/core
Version:
AwayJS core classes
537 lines (536 loc) • 23.8 kB
JavaScript
import { __extends } from "tslib";
import { AssetEvent } from '../events/AssetEvent';
import { EventDispatcher } from '../events/EventDispatcher';
import { URLLoaderEvent } from '../events/URLLoaderEvent';
import { LoaderEvent } from '../events/LoaderEvent';
import { ParserEvent } from '../events/ParserEvent';
import { URLLoader } from '../net/URLLoader';
import { URLLoaderDataFormat } from '../net/URLLoaderDataFormat';
import { ResourceDependency } from '../parsers/ResourceDependency';
import { LoaderContext } from './LoaderContext';
/**
* Dispatched when any asset finishes parsing. Also see specific events for each
* individual asset type (meshes, materials et c.)
*
* @eventType away.events.AssetEvent
*/
//[Event(name="assetComplete", type="away3d.events.AssetEvent")]
/**
* Dispatched when a full resource (including dependencies) finishes loading.
*
* @eventType away.events.LoaderEvent
*/
//[Event(name="resourceComplete", type="away3d.events.LoaderEvent")]
/**
* Dispatched when a single dependency (which may be the main file of a
* resource) finishes loading.
*
* @eventType away.events.LoaderEvent
*/
//[Event(name="dependencyComplete", type="away3d.events.LoaderEvent")]
/**
* Dispatched when an error occurs during loading. I
*
* @eventType away.events.LoaderEvent
*/
//[Event(name="loadError", type="away3d.events.LoaderEvent")]
/**
* Dispatched when an error occurs during parsing.
*
* @eventType away.events.ParserEvent
*/
//[Event(name="parseError", type="away3d.events.ParserEvent")]
/**
* Dispatched when an image asset dimensions are not a power of 2
*
* @eventType away.events.AssetEvent
*/
//[Event(name="textureSizeError", type="away3d.events.AssetEvent")]
/**
* Loader can load any file format that away.supports (or for which a
* third-party parser has been plugged in) and it's dependencies. Events are
* dispatched when assets are encountered and for when the resource (or it's
* dependencies) have been loaded.
*
* The Loader will not make assets available in any other way than through the
* dispatched events. To store assets and make them available at any point from
* any module in an application, use the AssetLibrary to load and manage assets.
*
* @see away.library.AssetLibrary
*/
var Loader = /** @class */ (function (_super) {
__extends(Loader, _super);
/**
* Create a new Loader object.
*/
function Loader(materialMode) {
if (materialMode === void 0) { materialMode = 0; }
var _this = _super.call(this) || this;
_this._materialMode = materialMode;
_this._stack = new Array();
_this._onReadyForDependenciesDelegate = function (event) { return _this._onReadyForDependencies(event); };
_this._onParseCompleteDelegate = function (event) { return _this._onParseComplete(event); };
_this._onParseErrorDelegate = function (event) { return _this._onParseError(event); };
_this._onLoadProgressDelegate = function (event) { return _this._onLoadProgress(event); };
_this._onLoadCompleteDelegate = function (event) { return _this._onLoadComplete(event); };
_this._onLoadErrorDelegate = function (event) { return _this._onLoadError(event); };
_this._onTextureSizeErrorDelegate = function (event) { return _this._onTextureSizeError(event); };
_this._onAssetCompleteDelegate = function (event) { return _this._onAssetComplete(event); };
return _this;
}
/**
* Enables a specific parser. When no specific parser is set for a
* loading/parsing opperation, loader3d can autoselect the correct parser to
* use. A parser must have been enabled, to be considered when autoselecting
* the parser.
*
* @param parser The parser export class to enable.
*
* @see away.parsers.Parsers
*/
Loader.enableParser = function (parser) {
if (Loader._parsers.indexOf(parser) < 0)
Loader._parsers.push(parser);
};
/**
* Enables a list of parsers. When no specific parser is set for a
* loading/parsing opperation, Loader can autoselect the correct parser to
* use. A parser must have been enabled, to be considered when autoselecting
* the parser.
*
* @param parsers An Array of parser classes to enable.
* @see away.parsers.Parsers
*/
Loader.enableParsers = function (parsers) {
for (var c = 0; c < parsers.length; c++)
Loader.enableParser(parsers[c]);
};
Object.defineProperty(Loader.prototype, "baseDependency", {
/**
* Returns the base dependency of the loader
*/
get: function () {
return this._baseDependency;
},
enumerable: false,
configurable: true
});
/**
* Loads a file and (optionally) all of its dependencies.
*
* @param req The URLRequest object containing the URL of the file to be
* loaded.
* @param context An optional context object providing additional parameters
* for loading
* @param ns An optional namespace string under which the file is to be
* loaded, allowing the differentiation of two resources with identical
* assets
* @param parser An optional parser object for translating the loaded data
* into a usable resource. If not provided, Loader will attempt to
* auto-detect the file type.
*/
Loader.prototype.load = function (req, context, ns, parser) {
if (context === void 0) { context = null; }
if (ns === void 0) { ns = null; }
if (parser === void 0) { parser = null; }
this._uri = req.url = req.url.replace(/\\/g, '/');
this._context = context;
this._namespace = ns;
this.dispatchEvent(new LoaderEvent(LoaderEvent.LOADER_START, this._uri, null, null));
this._baseDependency = new ResourceDependency('', req, null, parser, null);
this._retrieveDependency(this._baseDependency);
};
/**
* Loads a resource from already loaded data.
*
* @param data The data object containing all resource information.
* @param context An optional context object providing additional parameters
* for loading
* @param ns An optional namespace string under which the file is to be
* loaded, allowing the differentiation of two resources with identical
* assets
* @param parser An optional parser object for translating the loaded data
* into a usable resource. If not provided, Loader will attempt to
* auto-detect the file type.
*/
Loader.prototype.loadData = function (data, id, context, ns, parser) {
if (context === void 0) { context = null; }
if (ns === void 0) { ns = null; }
if (parser === void 0) { parser = null; }
this._uri = ns;
this._context = context;
this._namespace = ns;
this.dispatchEvent(new LoaderEvent(LoaderEvent.LOADER_START, this._uri, null, null));
this._baseDependency = new ResourceDependency(id, null, data, parser, null);
this._retrieveDependency(this._baseDependency);
};
/**
* Recursively retrieves the next to-be-loaded and parsed dependency on the
* stack, or pops the list off the stack when complete and continues on the
* top set.
* @param parser The parser that will translate the data into a usable
* resource.
*/
Loader.prototype._retrieveNext = function (parser) {
if (parser === void 0) { parser = null; }
if (this._currentDependency.dependencies.length) {
var next = this._currentDependency.dependencies.pop();
this._stack.push(this._currentDependency);
this._retrieveDependency(next);
}
else if (this._currentDependency.parser && this._currentDependency.parser.parsingPaused) {
this._currentDependency.parser.resumeParsing();
}
else if (this._stack && this._stack.length) {
var prev = this._currentDependency;
this._currentDependency = this._stack.pop();
if (prev.success)
prev.resolve();
this._retrieveNext(parser);
}
else {
this.dispatchEvent(new LoaderEvent(LoaderEvent.LOADER_COMPLETE, this._uri, this._baseDependency.parser.content, this._baseDependency.assets));
}
};
/**
* Retrieves a single dependency.
* @param parser The parser that will translate the data into a usable
* resource.
*/
Loader.prototype._retrieveDependency = function (dependency) {
var data;
if (this._context && this._context.materialMode != 0)
this._materialMode = this._context.materialMode;
this._currentDependency = dependency;
// Get already loaded (or mapped) data if available
data = dependency.data;
if (this._context && dependency.request) {
if (this._context._iHasDataForUrl(dependency.request.url)) {
data = this._context._iGetDataForUrl(dependency.request.url);
}
else if (this._context.externalAssetMode == LoaderContext.ON_DEMAND
&& (this.getSuffix(dependency.request.url) == 'jpg'
|| this.getSuffix(dependency.request.url) == 'png')) {
data = dependency.request;
if (!dependency.parser)
dependency.setParser(this.getParserFromSuffix(dependency.request.url));
}
}
if (data) {
if (data.constructor === Function)
data = new data();
dependency.setData(data);
if (dependency.retrieveAsRawData) {
// No need to parse. The parent parser is expecting this to be
// raw data so it can be passed directly.
dependency.resolve();
// Move on to next dependency
this._retrieveNext();
}
else {
this._parseDependency(dependency);
}
}
else {
//setup the loader on the dependency
dependency.loader = new URLLoader();
this._addEventListeners(dependency.loader);
// Resolve URL and start loading
dependency.request.url = this.resolveDependencyUrl(dependency);
if (dependency.retrieveAsRawData) {
// Always use binary for raw data loading
dependency.loader.dataFormat = URLLoaderDataFormat.BINARY;
}
else {
if (!dependency.parser)
dependency.setParser(this.getParserFromSuffix(dependency.request.url));
if (dependency.parser) {
dependency.loader.dataFormat = dependency.parser.dataFormat;
}
else {
// Always use BINARY for unknown file formats. The thorough
// file type check will determine format after load, and if
// binary, a text load will have broken the file data.
dependency.loader.dataFormat = URLLoaderDataFormat.BINARY;
}
}
dependency.loader.load(dependency.request);
}
};
Loader.prototype._joinUrl = function (base, end) {
if (end.charAt(0) == '/' || end.charAt(0) == '\\')
end = end.substr(1);
if (end.charAt(0) == '.')
end = end.substr(2);
if (base.length == 0)
return end;
if (base.charAt(base.length - 1) == '/' || base.charAt(base.length - 1) == '\\')
base = base.substr(0, base.length - 1);
return base.concat('/', end);
};
Loader.prototype.resolveDependencyUrl = function (dependency) {
var url = dependency.request.url;
// Has the user re-mapped this URL?
if (this._context && this._context._iHasMappingForUrl(url))
return this._context._iGetRemappedUrl(url);
// This is the "base" dependency, i.e. the actual requested asset. We
// will not try to resolve this since the user can probably be trusted
// to know this URL better than our automatic resolver. :)
if (url == this._uri)
return url;
// Absolute URL? Check if starts with slash or a URL scheme definition
// (e.g. ftp://, http://, file://)
var scheme_re = new RegExp('/^[a-zA-Z]{3,4}:///');
if (url.charAt(0) == '/') {
if (this._context && this._context.overrideAbsolutePaths)
return this._joinUrl(this._context.dependencyBaseUrl, url);
else
return url;
}
else if (scheme_re.test(url)) {
// If overriding full URLs, get rid of scheme (e.g. "http://") and
// replace with the dependencyBaseUrl defined by user.
if (this._context && this._context.overrideFullURLs) {
var noscheme_url = url.replace(scheme_re, ''); //url['replace'](scheme_re);
return this._joinUrl(this._context.dependencyBaseUrl, noscheme_url);
}
}
var base;
// Since not absolute, just get rid of base file name to find it's
// folder and then concatenate dynamic URL
if (this._context && this._context.dependencyBaseUrl) {
base = this._context.dependencyBaseUrl;
return this._joinUrl(base, url);
}
else {
base = this._uri.substring(0, this._uri.lastIndexOf('/') + 1);
return this._joinUrl(base, url);
}
};
Loader.prototype._retrieveParserDependencies = function () {
if (!this._currentDependency)
return;
var parserDependancies = this._currentDependency.parser.dependencies;
var len = parserDependancies.length;
for (var i = 0; i < len; i++)
this._currentDependency.dependencies[i] = parserDependancies[i];
// Since more dependencies might be added eventually, empty this list so
// that the same dependency isn't retrieved more than once.
parserDependancies.length = 0;
this._retrieveNext();
};
Loader.prototype._resolveParserDependencies = function () {
this._currentDependency.success = true;
// Retrieve any last dependencies remaining on this parser, or if none
// exists, just move on.
if (this._currentDependency.parser
&& this._currentDependency.parser.dependencies.length
&& (!this._context || this._context.includeDependencies)) //context may be null
this._retrieveParserDependencies();
else
this._retrieveNext();
};
/**
* Called when a single dependency loading failed, and pushes further
* dependencies onto the stack.
* @param event
*/
Loader.prototype._onLoadError = function (event) {
if (this.hasEventListener(URLLoaderEvent.LOAD_ERROR)) {
//pass on the error event for processing
this.dispatchEvent(event);
if (this._currentDependency != this._baseDependency) {
//execute any placeholder measure for failed loads
this._currentDependency.resolveFailure();
//goto the next dependency
this._retrieveNext();
}
else {
// Either this was the base file (last left in the stack) or
// default behavior was prevented by the handlers, and hence
// there is nothing more to do than clean up and bail.
this.dispose();
return;
}
}
else {
// Error event was not handled by listeners directly on Loader or on
// any of the subscribed loaders (in the list of error handlers.)
throw new Error();
}
};
/**
* Called when a dependency parsing failed, and dispatches a
* <code>ParserEvent.PARSE_ERROR</code>
* @param event
*/
Loader.prototype._onParseError = function (event) {
if (this.hasEventListener(ParserEvent.PARSE_ERROR)) {
//pass on the error event for processing
this.dispatchEvent(event);
//goto the next dependency
this._retrieveNext();
}
else {
// Error event was not handled by listeners directly on Loader or on
// any of the subscribed loaders (in the list of error handlers.)
throw new Error(event.message);
}
};
Loader.prototype._onAssetComplete = function (event) {
// Add loaded asset to list of assets retrieved as part of the current
// dependency. This list will be inspected by the parent parser when
// dependency is resolved
if (this._currentDependency)
this._currentDependency.assets.push(event.asset);
event.asset.resetAssetPath(event.asset.name, this._namespace);
if (!this._currentDependency.suppresAssetEvents)
this.dispatchEvent(event);
};
Loader.prototype._onReadyForDependencies = function (event) {
if (this._context && !this._context.includeDependencies)
event.target.resumeParsing();
else
this._retrieveParserDependencies();
};
Loader.prototype._onLoadProgress = function (event) {
this.dispatchEvent(event);
};
/**
* Called when a single dependency was loaded, and pushes further
* dependencies onto the stack.
* @param event
*/
Loader.prototype._onLoadComplete = function (event) {
var loader = event.urlLoader;
this.dispatchEvent(event);
this._removeEventListeners(loader);
// Resolve this dependency
this._currentDependency.setData(loader.data);
if (this._currentDependency.retrieveAsRawData) {
// No need to parse this data, which should be returned as is
this._resolveParserDependencies();
}
else {
this._parseDependency(this._currentDependency);
}
};
/**
* Called when parsing is complete.
*/
Loader.prototype._onParseComplete = function (event) {
var parser = event.target;
parser.removeEventListener(ParserEvent.READY_FOR_DEPENDENCIES, this._onReadyForDependenciesDelegate);
parser.removeEventListener(ParserEvent.PARSE_COMPLETE, this._onParseCompleteDelegate);
parser.removeEventListener(ParserEvent.PARSE_ERROR, this._onParseErrorDelegate);
parser.removeEventListener(AssetEvent.TEXTURE_SIZE_ERROR, this._onTextureSizeErrorDelegate);
parser.removeEventListener(AssetEvent.ASSET_COMPLETE, this._onAssetCompleteDelegate);
this._resolveParserDependencies();
};
/**
* Called when an image is too large or it's dimensions are not a power of 2
* @param event
*/
Loader.prototype._onTextureSizeError = function (event) {
event.asset.name = this._currentDependency.resolveName(event.asset);
this.dispatchEvent(event);
};
Loader.prototype._addEventListeners = function (loader) {
loader.addEventListener(URLLoaderEvent.LOAD_PROGRESS, this._onLoadProgressDelegate);
loader.addEventListener(URLLoaderEvent.LOAD_COMPLETE, this._onLoadCompleteDelegate);
loader.addEventListener(URLLoaderEvent.LOAD_ERROR, this._onLoadErrorDelegate);
};
Loader.prototype._removeEventListeners = function (loader) {
loader.removeEventListener(URLLoaderEvent.LOAD_PROGRESS, this._onLoadProgressDelegate);
loader.removeEventListener(URLLoaderEvent.LOAD_COMPLETE, this._onLoadCompleteDelegate);
loader.removeEventListener(URLLoaderEvent.LOAD_ERROR, this._onLoadErrorDelegate);
};
Loader.prototype.stop = function () {
this.dispose();
};
Loader.prototype.dispose = function () {
this._context = null;
this._stack = null;
if (this._currentDependency && this._currentDependency.loader)
this._removeEventListeners(this._currentDependency.loader);
this._currentDependency = null;
};
/**
* Guesses the parser to be used based on the file contents.
* @param data The data to be parsed.
* @param uri The url or id of the object to be parsed.
* @return An instance of the guessed parser.
*/
Loader.prototype._getParserFromData = function (data) {
var len = Loader._parsers.length;
// go in reverse order to allow application override of default parser
// added in away.proper
for (var i = len - 1; i >= 0; i--)
if (Loader._parsers[i].supportsData(data))
return new Loader._parsers[i]();
return null;
};
/**
* Initiates parsing of the loaded dependency.
*
* @param The dependency to be parsed.
*/
Loader.prototype._parseDependency = function (dependency) {
var parser = dependency.parser;
// If no parser has been defined, try to find one by letting all plugged
// in parsers inspect the actual data.
if (!parser)
dependency.setParser(parser = this._getParserFromData(dependency.data));
if (parser) {
parser.addEventListener(ParserEvent.READY_FOR_DEPENDENCIES, this._onReadyForDependenciesDelegate);
parser.addEventListener(ParserEvent.PARSE_COMPLETE, this._onParseCompleteDelegate);
parser.addEventListener(ParserEvent.PARSE_ERROR, this._onParseErrorDelegate);
parser.addEventListener(AssetEvent.TEXTURE_SIZE_ERROR, this._onTextureSizeErrorDelegate);
parser.addEventListener(AssetEvent.ASSET_COMPLETE, this._onAssetCompleteDelegate);
if (dependency.request && dependency.request.url)
parser.fileName = dependency.request.url;
parser.materialMode = this._materialMode;
parser.parseAsync(dependency.data);
}
else {
var message = 'No parser defined. To enable all parsers for auto-detection, use Parsers.enableAllBundled()';
if (this.hasEventListener(ParserEvent.PARSE_ERROR)) {
//pass on the error event for processing
this.dispatchEvent(new ParserEvent(ParserEvent.PARSE_ERROR, message));
//goto the next dependency
this._retrieveNext();
}
else {
// Error event was not handled by listeners directly on Loader
// or on any of the subscribed loaders (in the list of error
// handlers.)
throw new Error(message);
}
}
};
/**
* Guesses the parser to be used based on the file extension.
* @return An instance of the guessed parser.
*/
Loader.prototype.getParserFromSuffix = function (url) {
var fileExtension = this.getSuffix(url);
var len = Loader._parsers.length;
// go in reverse order to allow application override of default parser
// added in away.proper
for (var i = len - 1; i >= 0; i--) {
var parserClass = Loader._parsers[i];
if (parserClass.supportsType(fileExtension))
return new parserClass();
}
return null;
};
Loader.prototype.getSuffix = function (url) {
// Get rid of query string if any and extract extension
var base = (url.indexOf('?') > 0) ? url.split('?')[0] : url;
return base.substr(base.lastIndexOf('.') + 1).toLowerCase();
};
// Image parser only parser that is added by default, to save file size.
Loader._parsers = new Array();
return Loader;
}(EventDispatcher));
export { Loader };