@awayjs/scene
Version:
AwayJS scene classes
441 lines (396 loc) • 14.8 kB
text/typescript
import { DisplayObject } from '../display/DisplayObject';
import { MovieClip } from '../display/MovieClip';
import { IMovieClipAdapter } from '../adapters/IMovieClipAdapter';
import { IDisplayObjectAdapter } from '../adapters/IDisplayObjectAdapter';
interface IInterval {
id: number;
f: Function;
t: number;
dt: number;
isTimeout: boolean;
isActive: boolean;
}
interface IScriptQueue {
queued_mcs: MovieClip[],
queued_scripts: any[],
queued_mcs_pass2: MovieClip[],
queued_scripts_pass2: any[],
constructors: MovieClip[],
}
export class FrameScriptManager {
public static invalidAS3Constructors: boolean = false;
// FrameScript debugging:
// the first line of a FrameScript should be a comment that represents the functions unique name
// the exporter creates a js file, containing a object that has the framescripts
// functions set as properties according to the unique names
// this object can be set as "frameScriptDebug" in order to enable debug mode
public static frameScriptDebug: Object = undefined;
public static useAVM1: boolean = false;
//queue of objects for disposal
private static _queued_dispose: DisplayObject[] = [];
private static _queue: IScriptQueue;
private static _active_intervals: IInterval[] = [];
private static _index: number = 0;
private static _intervalID: number = 0;
public static setInterval(fun: Function, time: number, isTimeout: boolean = false): number {
this._intervalID++;
// make sure we have at least 1ms intervals
if (time < 1) {
time = 1;
}
this._active_intervals.push({ 'id': this._intervalID, 'f': fun, 't': time, 'dt': 0, 'isTimeout': isTimeout, 'isActive': true });
return this._intervalID;
}
public static setTimeOut(fun: Function, time: number): number {
return this.setInterval(fun, time, true);
}
public static clearInterval(id: number): void {
let i: number = this._active_intervals.length;
while (i--) {
if (this._active_intervals[i].id == id) {
this._active_intervals[i].isActive = false;
this._active_intervals.splice(i, 1);
if (this._index > i) {
//edge case when in the middle of execute_intervals
this._index--;
}
break;
}
}
}
public static clearTimeout(id: number): void {
this.clearInterval(id);
}
public static execute_intervals(dt: number = 0): void {
let interval: IInterval;
this._index = this._active_intervals.length;
while (this._index--) {
interval = this._active_intervals[this._index];
interval.dt += dt;
// keep executing the setInterval for as many times as the dt allows
// a setInterval can delete itself, so we need to check if it still exists
while (interval.isActive && interval.dt >= interval.t) {
if (interval.isTimeout) {
interval.isActive = false;
this._active_intervals.splice(this._index, 1);
}
interval.dt -= interval.t;
interval.f();
}
}
}
public static add_child_to_dispose(child: DisplayObject): void {
this._queued_dispose.push(child);
}
public static get_queue(): IScriptQueue {
if (!FrameScriptManager._queue) {
FrameScriptManager._queue = {
queued_mcs: [],
queued_scripts: [],
queued_mcs_pass2: [],
queued_scripts_pass2: [],
constructors: [],
};
}
return FrameScriptManager._queue;
}
public static add_script_to_queue(mc: MovieClip, script: any): void {
//console.log("add_script_to_queue", mc.name);
const queue = FrameScriptManager.get_queue();
// whenever we queue scripts of new objects, we first inject the lists of pass2
const len = queue.queued_mcs_pass2.length;
let i = 0;
for (i = 0;i < len; i++) {
queue.queued_mcs.push(queue.queued_mcs_pass2[i]);
queue.queued_scripts.push(queue.queued_scripts_pass2[i]);
}
queue.queued_mcs_pass2.length = 0;
queue.queued_scripts_pass2.length = 0;
(<any>mc.adapter).allowScript = true;
queue.queued_mcs.push(mc);
queue.queued_scripts.push(script);
}
public static add_loaded_action_to_queue(mc: MovieClip): void {
//console.log("add_loaded_action_to_queue", mc.name);
const queue = FrameScriptManager.get_queue();
queue.queued_mcs.push(mc);
queue.queued_scripts.push(null);
}
public static add_script_to_queue_pass2(mc: MovieClip, script: any): void {
//console.log("add_script_to_queue_pass2", mc.name);
const queue = FrameScriptManager.get_queue();
(<any>mc.adapter).allowScript = true;
queue.queued_mcs_pass2.push(mc);
queue.queued_scripts_pass2.push(script);
}
public static execute_as3_constructors_finish_scene(mc: MovieClip): void {
/**
* this gets executed after a timeline-navigation
* first execute_as3_constructors_recursiv is called for the mc that was navigated
* after that execute_as3_constructors_finish_scene is called for the scene-mc
* so that we can continue with the next top-level child that has not yet been processed
*/
for (let i = 0, l = mc.numChildren; i < l; i++) {
const child: DisplayObject = mc.getChildAt(i);
if (child.parent && (<IDisplayObjectAdapter>child.adapter).executeConstructor)
FrameScriptManager.execute_as3_constructors_recursiv(<MovieClip>child);
}
}
public static execute_as3_constructors_enterFrame(mc: MovieClip): void {
if (!FrameScriptManager.invalidAS3Constructors)
return;
FrameScriptManager.execute_as3_constructors_recursiv(mc);
FrameScriptManager.invalidAS3Constructors = false;
}
public static execute_as3_constructors_recursiv(mc: MovieClip): void {
/**
* when called from advanceFrame, this should iterate all childs and execute constructors
*
* when called after a navigation command,
* this should iterate the navigated object first,
* than it should continue with unprocessed top-level children
*
* this scenegraph:
* scene
* - mc1
* - child1
* - mc2
* - child2
* - mc3
* - child3
*
* should normally be executed in the same order.
* the constructors of childs will be executed from within the super-calls in the parent-constructors
* this happens after the parent constructor has initialized the parent properties,
* but before the parents custom constructor code has run
* so when putting traces in the constructors, the order in which the traces appear is this:
* - child1 - start constructor
* - child1 - end constructor
* - mc1 - start constructor
* - mc1 - end constructor
* - chidl2 - start constructor
* - chidl2 - end constructor
* - mc2 - start constructor
* - mc2 - end constructor
* - child3 - start constructor
* - child3 - end constructor
* - mc3 - start constructor
* - mc3 - end constructor
*
* now when we have a timeline navigation called from for example constructor of mc1
*
* after the timeline navigation, it will first process new constructors
* added for the new frame we navigated too
* it will than continue to work of top-level childs that have not been processed yet
* so the order should look like this:
* - child1 - start constructor
* - child1 - end constructor
* - mc1 - start constructor (calls timeline navigation on itself - gotoAndStop(2))
* - child1.2 (on frame 2) - start constructor
* - child1.2 (on frame 2) - end constructor
* - chidl2 - start constructor
* - chidl2 - end constructor
* - mc2 - start constructor
* - mc2 - end constructor
* - child3 - start constructor
* - child3 - end constructor
* - mc3 - start constructor
* - mc3 - end constructor
* - mc1 - end constructor
*
*/
const mcadapter = mc.adapter;
const constructorFunc = (<IDisplayObjectAdapter>mcadapter).executeConstructor;
if (constructorFunc) {
// constructor has not run yet. will run constructors of all childs aswell
(<IDisplayObjectAdapter>mcadapter).executeConstructor = null;
constructorFunc();
} else {
// constructor already has run. we need to still do recursion on childs
for (let i = 0, l = mc.numChildren; i < l; i++) {
const child: DisplayObject = mc.getChildAt(i);
// because we iterate over mc, it already is parent
//if (child.parent) {
FrameScriptManager.execute_as3_constructors_recursiv(<MovieClip>child);
//}
}
}
// if mc was created by timeline, instanceID != ""
if ((<any>mc).just_added_to_timeline
&& mc._sessionID >= 0
&& mcadapter
&& (<any>mcadapter).dispatchStaticEvent) {
(<any>mc).just_added_to_timeline = false;
(<any>mcadapter).dispatchStaticEvent('added', mcadapter);
mc.hasDispatchedAddedToStage = mc.isOnDisplayList();
if (mc.hasDispatchedAddedToStage)
(<any>mcadapter).dispatchStaticEvent('addedToStage', mcadapter);
// todo: this does not dispatch ADDED and ADDED_TO_STAGE on SHAPE,
// because in awayjs timeline Shape are Sprite without any as3-adapter
}
}
// todo: better / faster way to check if a obj is currently on stage
public static isOnStage(mc: DisplayObject): boolean {
let parent = mc;
while (parent && !parent.isAVMScene) {
parent = parent.parent;
}
if (parent && parent.isAVMScene)
return true;
return false;
}
public static execute_avm1_constructors(): void {
const queue = FrameScriptManager.get_queue();
if (queue.queued_mcs.length == 0 && queue.queued_mcs_pass2.length == 0)
return;
let i = queue.queued_mcs_pass2.length;
while (i > 0) {
i--;
queue.queued_mcs.push(queue.queued_mcs_pass2[i]);
queue.queued_scripts.push(queue.queued_scripts_pass2[i]);
}
queue.queued_mcs_pass2.length = 0;
queue.queued_scripts_pass2.length = 0;
const queues_tmp: any[] = queue.queued_mcs;
let mc: MovieClip;
if (FrameScriptManager.useAVM1) {
i = queues_tmp.length;
while (i > 0) {
i--;
mc = queues_tmp[i];
if (!FrameScriptManager.isOnStage(mc))
continue;
if ((<any>mc).onInitialize) {
const myFunc = (<any>mc).onInitialize;
(<any>mc).onInitialize = null;
myFunc();
}
}
for (i = 0; i < queues_tmp.length; i++) {
mc = queues_tmp[i];
if (!FrameScriptManager.isOnStage(mc))
continue;
// onClipEvent (construct) comes before class-constructor
if ((<any>mc).onConstruct) {
const myFunc = (<any>mc).onConstruct;
(<any>mc).onConstruct = null;
myFunc();
}
// class-constructor
const constructorFunc = (<IDisplayObjectAdapter>mc.adapter).executeConstructor;
if (constructorFunc) {
(<IDisplayObjectAdapter>mc.adapter).executeConstructor = null;
//console.log(randomVal, "call constructor for ", mc.parent.name, mc.name);
constructorFunc();
}
}
}
}
public static execute_queue(): void {
const queue = FrameScriptManager.get_queue();
if (queue.queued_mcs.length == 0 && queue.queued_mcs_pass2.length == 0)
return;
const queues_tmp: any[] = queue.queued_mcs.concat();
const queues_scripts_tmp: any[] = queue.queued_scripts.concat();
queue.queued_mcs.length = 0;
queue.queued_scripts.length = 0;
const len = queue.queued_mcs_pass2.length;
let i = 0;
for (i = 0;i < len; i++) {
queues_tmp.push(queue.queued_mcs_pass2[i]);
queues_scripts_tmp.push(queue.queued_scripts_pass2[i]);
}
queue.queued_mcs_pass2.length = 0;
queue.queued_scripts_pass2.length = 0;
let mc: MovieClip;
if (FrameScriptManager.useAVM1) {
// first we need to execute all onclipEvent(initialize)
for (i = 0; i < queues_tmp.length; i++) {
mc = queues_tmp[i];
// ignore objects that are not on stage
if (!FrameScriptManager.isOnStage(mc))
continue;
if ((<any>mc).onInitialize) {
const myFunc = (<any>mc).onInitialize;
(<any>mc).onInitialize = null;
myFunc();
}
}
// second we execute onClipEvent (construct) and class-constructors
for (i = 0; i < queues_tmp.length; i++) {
mc = queues_tmp[i];
// ignore objects that are not on stage
if (!FrameScriptManager.isOnStage(mc))
continue;
if ((<any>mc).onConstruct) {
const myFunc = (<any>mc).onConstruct;
(<any>mc).onConstruct = null;
myFunc();
}
const constructorFunc = (<IDisplayObjectAdapter>mc.adapter).executeConstructor;
if (constructorFunc) {
(<IDisplayObjectAdapter>mc.adapter).executeConstructor = null;
//console.log(randomVal, "call constructor for ", mc.parent.name, mc.name);
constructorFunc();
}
}
}
//console.log("execute scripts")
for (i = 0; i < queues_tmp.length; i++) {
mc = queues_tmp[i];
//console.log("scriptqueue", mc.name);
if (FrameScriptManager.useAVM1) {
// ignore objects that are not on stage
if (!FrameScriptManager.isOnStage(mc))
continue;
// execute onclipEvent(loaded)
if ((<any>mc).onLoaded) {
const myFunc = (<any>mc).onLoaded;
(<any>mc).onLoaded = null;
myFunc();
}
// execute onLoad defined as class-property
if (!(<any>mc.adapter).hasOnLoadExecuted) {
// i wanted to delete the onLoad property after it has been executed
// but its a prototype-method, and not sure how to savly delete it
// thats why i am working with the "hasOnLoadExecuted" for now
(<any>mc.adapter).hasOnLoadExecuted = true;
const func = (<any>mc.adapter).alGet('onLoad');
if (func) {
func.alCall(mc.adapter);
}
}
}
if (queues_scripts_tmp[i] != null) {
mc = queues_tmp[i];
// only execute scripts for mcs that already had constructors run
// other framescripts get send back into the queue for being executed in next call to execute_queue
// in FP10 and above there should always come a execute_queue after processing constructors
// so after all constructors have been run, all objects should pass this test
// we cant check for if a executeConstructor exists here, because we only consider a constructor run,
// once it has processed the super-constructors. not when we actually call the constructor-function
// (this is because child-constructor do execute from within the super-constructor)
// for avm1 (<any>mc.adapter).constructorHasRun should always be true
if (mc && mc.adapter && !(<any>mc.adapter).constructorHasRun) {
//console.log("mc with contructor - queue script", mc.name)
queue.queued_mcs.push(mc);
queue.queued_scripts.push(queues_scripts_tmp[i]);
} else if (mc && mc.adapter && (<IMovieClipAdapter>mc.adapter).executeScript) {
//console.log("mc script", mc.name);
(<IMovieClipAdapter>mc.adapter).executeScript(queues_scripts_tmp[i]);
} else {
//console.log("mc ignored", mc.name)
}
} else {
//console.log("script is null", mc.name)
}
}
}
public static execute_dispose(): void {
const len: number = this._queued_dispose.length;
for (let i: number = 0; i < len; i++)
this._queued_dispose[i].dispose();
this._queued_dispose.length = 0;
}
}
export default FrameScriptManager;