import { IAsset } from '../library/IAsset';
import { URLRequest } from '../net/URLRequest';
import { AbstractMethodError } from '../errors/AbstractMethodError';
import { AssetEvent } from '../events/AssetEvent';
import { EventDispatcher } from '../events/EventDispatcher';
import { ParserEvent } from '../events/ParserEvent';
import { TimerEvent } from '../events/TimerEvent';
import { ParserUtils } from '../parsers/ParserUtils';
import { ResourceDependency } from '../parsers/ResourceDependency';
import { ByteArray } from '../utils/ByteArray';
import { getTimer } from '../utils/getTimer';
import { RequestAnimationFrame } from '../utils/RequestAnimationFrame';
/**
* ParserBase provides an abstract base export class for objects
* that convert blocks of data to data structures supported by away.
*
* If used by Loader to automatically determine the parser type,
* two public static methods should be implemented, with the following
* signatures:
*
* public static supportsType(extension : string) : boolean
* Indicates whether or not a given file extension is supported by the parser.
*
* public static supportsData(data : *) : boolean Tests whether a
* data block can be parsed by the parser.
*
* Furthermore, for any concrete subtype, the method initHandle
* should be overridden to immediately create the object that will contain the
* parsed data. This allows ResourceManager to return an object
* handle regardless of whether the object was loaded or not.
*
* @see Loader
*/
export class ParserBase extends EventDispatcher {
private _isParsing: boolean;
private _dataFormat: string;
private _data: any;
private _frameLimit: number;
private _onIntervalDelegate: (event: TimerEvent) => void;
private static _lastFrameTime: number = 0;
protected _content: IAsset;
public fileName: string;
public materialMode: number;
//------------------------------------------------------------------------------------------------------------------
// TODO: add error checking for the following ( could cause a problem if
// this function is not implemented )
//------------------------------------------------------------------------------------------------------------------
// Needs to be implemented in all Parsers (public static
//supportsType(extension : string) : boolean
//* Indicates whether or not a given file extension is supported by the
// parser.
// ----------------------------------------------------------------------------------------------------------------
public static supportsType(extension: string): boolean {
throw new AbstractMethodError();
}
private _dependencies: Array;
private _parsingPaused: boolean;
private _parsingComplete: boolean;
private _parsingFailure: boolean;
private _timer: RequestAnimationFrame;
public get content(): IAsset {
return this._content;
}
/**
* Creates a new ParserBase object
* @param format The data format of the file data to be parsed. Can be
* either ParserDataFormat.BINARY or
* ParserDataFormat.PLAIN_TEXT, and should be provided by the
* concrete subtype.
*
* @see away.loading.parsers.ParserDataFormat
*/
constructor(format: string) {
super();
this.materialMode = 0;
this._dataFormat = format;
this._dependencies = new Array();
this._onIntervalDelegate = (event: TimerEvent) => this._onInterval(event);
this._timer = new RequestAnimationFrame(this._onIntervalDelegate, this);
}
public set parsingFailure(b: boolean) {
this._parsingFailure = b;
}
public get parsingFailure(): boolean {
return this._parsingFailure;
}
public get parsingPaused(): boolean {
return this._parsingPaused;
}
public get parsingComplete(): boolean {
return this._parsingComplete;
}
public get data(): any {
return this._data;
}
/**
* The data format of the file data to be parsed. Options are
* URLLoaderDataFormat.BINARY,
* URLLoaderDataFormat.ARRAY_BUFFER,
* URLLoaderDataFormat.BLOB,
* URLLoaderDataFormat.VARIABLES or
* URLLoaderDataFormat.TEXT.
*/
public get dataFormat(): string {
return this._dataFormat;
}
/**
* Parse data (possibly containing bytearry, plain text or BitmapAsset)
* asynchronously, meaning that the parser will periodically stop parsing so
* that the AVM may proceed to the next frame.
*
* @param data The untyped data object in which the loaded data resides.
* @param frameLimit number of milliseconds of parsing allowed per frame.
* The actual time spent on a frame can exceed this number since time-checks
* can only be performed between logical sections of the parsing procedure.
*/
public parseAsync(data: any, frameLimit: number = 30): void {
this._data = data;
this.startParsing(frameLimit);
}
// public parseSync(data: any): IAsset {
// this._data = data;
// let state = ParserBase.MORE_TO_PARSE;
// while (state == ParserBase.MORE_TO_PARSE) {
// state = this._pProceedParsing();
// }
// return this._pContent;
// }
/**
* A list of dependencies that need to be loaded and resolved for the object
* being parsed.
*/
public get dependencies(): Array {
return this._dependencies;
}
/**
* Resolve a dependency when it's loaded. For example, a dependency
* containing an ImageResource would be assigned to a Mesh instance as a
* BitmapMaterial, a scene graph object would be added to its intended
* parent. The dependency should be a member of the dependencies property.
*
* @param resourceDependency The dependency to be resolved.
*/
public resolveDependency(resourceDependency: ResourceDependency): void {
throw new AbstractMethodError();
}
/**
* Resolve a dependency loading failure. Used by parser to eventually
* provide a default map
*
* @param resourceDependency The dependency to be resolved.
*/
public resolveDependencyFailure(resourceDependency: ResourceDependency): void {
throw new AbstractMethodError();
}
/**
* Resolve a dependency name
*
* @param resourceDependency The dependency to be resolved.
*/
public resolveDependencyName(resourceDependency: ResourceDependency, asset: IAsset): string {
return asset.name;
}
public resumeParsing(): void {
this._parsingPaused = false;
//get started!
if (this.hasTime())
this.proceedParsing();
}
protected finalizeAsset(asset: IAsset, name: string = null): void {
// If the asset has no name, give it a per-type default name.
asset.name = name || asset.name || asset.assetType;
this.dispatchEvent(new AssetEvent(AssetEvent.ASSET_COMPLETE, asset));
}
/**
* Parse the next block of data.
* @return Whether or not more data needs to be parsed. Can be
* ParserBase.ParserBase.PARSING_DONE or
* ParserBase.ParserBase.MORE_TO_PARSE.
*/
protected proceedParsing(): void {
throw new AbstractMethodError();
}
protected dieWithError(message: string = 'Unknown parsing error'): void {
this._parsingFailure = true;
this.dispatchEvent(new ParserEvent(ParserEvent.PARSE_ERROR, message));
}
protected addDependency(id: string,
req: URLRequest,
parser: ParserBase = null,
data: any = null,
retrieveAsRawData: boolean = false,
suppressErrorEvents: boolean = false,
sub_id: number = 0): ResourceDependency {
const dependency: ResourceDependency = new ResourceDependency(
id,
req,
data,
parser,
this,
retrieveAsRawData,
suppressErrorEvents,
sub_id
);
this._dependencies.push(dependency);
return dependency;
}
protected pauseAndRetrieveDependencies(): void {
this._parsingPaused = true;
this.dispatchEvent(new ParserEvent(ParserEvent.READY_FOR_DEPENDENCIES));
}
/**
* Tests whether or not there is still time left for parsing within the
* maximum allowed time frame per session.
* @return True if there is still time left, false if the maximum allotted
* time was exceeded and parsing should be interrupted.
*/
protected hasTime(): boolean {
this._isParsing = ((getTimer() - ParserBase._lastFrameTime) < this._frameLimit);
//If parsing has stopped due to framelimit, start RAF and wait for next animation frame
if (!this._isParsing)
this._timer.start();
return this._isParsing;
}
/**
* Called when the parsing pause interval has passed and parsing can
* proceed.
*/
private _onInterval(event: TimerEvent = null): void {
ParserBase._lastFrameTime = getTimer();
this._isParsing = true;
//stop RAF to continue parsing
this._timer.stop();
this.proceedParsing();
}
/**#
* Initializes the parsing of data.
* @param frameLimit The maximum duration of a parsing session.
*/
protected startParsing(frameLimit: number): void {
this._frameLimit = frameLimit;
//get started!
if (this.hasTime())
this.proceedParsing();
}
/**
* Finish parsing the data.
*/
protected finishParsing(): void {
if (this._parsingFailure)
return;
this._parsingComplete = true;
this._isParsing = false;
this.dispatchEvent(new ParserEvent(ParserEvent.PARSE_COMPLETE));
}
/**
*
* @returns {string}
* @private
*/
protected getTextData(): string {
return ParserUtils.toString(this._data);
}
/**
*
* @returns {ByteArray}
* @private
*/
protected getByteData(): ByteArray {
return ParserUtils.toByteArray(this._data);
}
/**
*
* @returns {any}
* @private
*/
protected getData(): any {
return this._data;
}
}