API Docs for: 1.4.0
Show:

File: src\core\StateManager.ts

/**
* 
* @module Kiwi
* 
*/

module Kiwi {

	/**
	* The State Manager handles the starting, parsing, looping and swapping of game States within a Kiwi Game
	* There is only ever one State Manager per game, but a single Game can contain multiple States.
	*
	* @class StateManager
	* @namespace Kiwi
	* @constructor
	* @param game {Kiwi.Game} The game that this statemanager belongs to.
	* @return {Kiwi.StateMananger} 
	*
	*/
	export class StateManager {

		constructor(game: Kiwi.Game) {

			this._game = game;

			this._states = [];

		}

		/**
		* The type of object this is.
		* @method objType
		* @return {string} "StateManager"
		* @public
		*/
		public objType() {
			return "StateManager";
		}

		/**
		* The game that this manager belongs to.
		* @property _game
		* @type Kiwi.Game
		* @private
		*/
		private _game: Kiwi.Game;

		/**
		* An array of all of the states that are contained within this manager.
		* @property _states
		* @type Array
		* @private
		*/
		private _states: Kiwi.State[];

		/**
		* The current State that the game is at.
		* @property current
		* @type Kiwi.State
		* @default null
		* @public
		*/
		public current: Kiwi.State = null;

		/**
		* The name of the new State that is to be switched to.
		* @property _newStateKey
		* @type string
		* @default null
		* @private
		*/
		private _newStateKey: string = null;

		/**
		* Checks to see if a key exists. Internal use only.
		* @method checkKeyExists
		* @param key {String}
		* @return {boolean}
		* @private
		*/
		private checkKeyExists(key: string): boolean {

			for (var i = 0; i < this._states.length; i++)
			{
				if (this._states[i].config.name === key)
				{
					return true;
				}
			}

			return false;

		}

		/**
		* Checks to see if the State passed is valid or not.
		* @method checkValidState
		* @param state {Kiwi.State}
		* @return {boolean}
		* @private
		*/
		private checkValidState(state: Kiwi.State): boolean {

			if (!state['game'] || !state['config']) {
				return false;
			}

			return true;

		}

		/**
		* Adds the given State to the StateManager.
		* The State must have a unique key set on it, or it will fail to be added to the manager.
		* Returns true if added successfully, otherwise false (can happen if State is already in the StateManager)
		* 
		* @method addState
		* @param state {Any} The Kiwi.State instance to add.
		* @param [switchTo=false] {boolean} If set to true automatically switch to the given state after adding it
		* @return {boolean} true if the State was added successfully, otherwise false
		* @public
		*/
		public addState(state: any, switchTo:boolean = false): boolean {
			Kiwi.Log.log('Kiwi.StateManager: Adding state.', '#state');
			var tempState;

			//What type is the state that was passed.
			if (typeof state === 'function') {
				tempState = new state();

			} else if (typeof state === 'string') {
				tempState = window[state];  

			} else {
				tempState = state;
			}

			//Does a state with that name already exist?
			if (tempState.config.name && this.checkKeyExists(tempState.config.name) === true) {

				Kiwi.Log.error('  Kiwi.StateManager: Could not add ' + tempState.config.name + ' as a State with that name already exists.', '#state');
				return false;
			}

			tempState.game = this._game;
			
			//Is it a valid state?
			if (this.checkValidState(tempState) === false) {

				Kiwi.Log.error('  Kiwi.StateManager: ' + tempState.config.name + ' isn\'t a valid state. Make sure you are using the Kiwi.State class!', '#state');

				return false;

			} else {

				this._states.push(tempState);
				Kiwi.Log.log('  Kiwi.StateManager: ' + tempState.config.name + ' was successfully added.', '#state');

				if (switchTo === true) {
					this.switchState( tempState.config.name );
				}

				return true;

			}

		}

		/**
		* Is executed once the DOM has finished loading.
		* This is an INTERNAL Kiwi method. 
		* @method boot
		* @public
		*/
		boot() {

		}

		/**
		* Switches to the name (key) of the state that you pass. 
		* Does not work if the state you are switching to is already the current state OR if that state does not exist yet.
		* @method setCurrentState
		* @param key {String}
		* @return {boolean}
		* @private
		*/
		private setCurrentState(key: string): boolean {

			//  Bail out if they are trying to switch to the already current state
			if (this.current !== null && this.current.config.name === key || this.checkKeyExists(key) === false) {
				return false;
			}

			Kiwi.Log.log('Kiwi.StateManager: Switching to "' + key + '" State.', '#state'); 

			this._newStateKey = key;
			return true;
		}

		/**
		* Actually switches to a state that is stored in the 'newStateKey' property. This method is executed after the update loops have been executed to help prevent developer errors. 
		* @method bootNewState
		* @private
		*/
        private bootNewState() {
			
            // Destroy the current if there is one.
            if (this.current !== null) {

                this.current.shutDown();


                this._game.input.reset();   //Reset the input component
                this.current.destroy(true); //Destroy ALL IChildren ever created on that state.
                this._game.fileStore.removeStateFiles(this.current); //Clear the fileStore of not global files.
                this.current.config.reset(); //Reset the config setting
                this._game.cameras.zeroAllCameras(); // Reset cameras
            } 


            //Set the current state, reset the key
            this.current = this.getState(this._newStateKey);
            this._newStateKey = null;


            //Initalise the state and execute the preload method?
            this.checkInit();
            this.checkPreload();

        }

		/**
		* Swaps the current state.
		* If the state has already been loaded (via addState) then you can just pass the key.
		* Otherwise you can pass the state object as well and it will load it then swap to it.
		*
		* @method switchState
		* @param key {String} The name/key of the state you would like to switch to.
		* @param [state=null] {Any} The state that you want to switch to. This is only used to create the state if it doesn't exist already.
		* @param [initParams=null] {Object} Any parameters that you would like to pass to the init method of that new state.
		* @param [createParams=null] {Object} Any parameters that you would like to pass to the create method of that new state.
        * @param [preloadParams=null] {Object} Any parameters that you would like to pass to the preload method. Since 1.3.0 of Kiwi.JS
		* @return {boolean} Whether the State is going to be switched to or not.
		* @public
		*/
		public switchState(key: string, state: any = null, initParams = null, createParams = null, preloadParams=null): boolean {

			//  If we have a current state that isn't yet ready (preload hasn't finished) then abort now
			if (this.current !== null && this.current.config.isReady === false) {

				Kiwi.Log.error('Kiwi.StateManager: Cannot change to a new state till the current state has finished loading!', '#state');

				return false;
			}

			// If state key doesn't exist then lets add it.
			if (this.checkKeyExists(key) === false && state !== null) {

				if (this.addState(state, false) === false) {
					return false;
				}
			}

			// Store the parameters (if any)
            if (initParams !== null || createParams !== null || preloadParams !== null) {

				var newState = this.getState(key);
				newState.config.initParams = [];
                newState.config.createParams = [];
                newState.config.preloadParams = [];

				for (var initParameter in initParams) {
					newState.config.initParams.push(initParams[initParameter]);
				}

				for (var createParameter in createParams) {
					newState.config.createParams.push(createParams[createParameter]);
                }

                for (var preloadParameter in preloadParams) {
                    newState.config.preloadParams.push(preloadParams[preloadParameter]);
                }

			}
			
			return this.setCurrentState(key);

		}

		/**
		* Gets a state by the key that is passed.
		* @method getState
		* @param key {String}
		* @return {Kiwi.State}
		* @private
		*/
		private getState(key: string): Kiwi.State {

			for (var i = 0; i < this._states.length; i++)
			{
				if (this._states[i].config.name === key)
				{
					return this._states[i];
				}
			}

			return null;

		}


		/*
		*----------------
		* Check Methods 
		*----------------
		*/


		/**
		* Checks to see if the state that is being switched to needs to load some files or not.
		* If it does it loads the file, if it does not it runs the create method.
		* @method checkPreload
		* @private
		*/
		private checkPreload() {

			//Rebuild the Libraries before the preload is executed
			this.rebuildLibraries();

			this._game.loader.onQueueProgress.add(this.onLoadProgress, this);
            this._game.loader.onQueueComplete.add(this.onLoadComplete, this);

            this.current.preload.apply(this.current, this.current.config.preloadParams);

            this._game.loader.start();
		}

		/**
		* Checks to see if the state being switched to contains a create method.
		* If it does then it calls the create method.
		* @method callCreate
		* @private
		*/
		private callCreate() {

			Kiwi.Log.log("Kiwi.StateManager: Calling " + this.current.name + ":Create", '#state');

			this.current.create.apply(this.current, this.current.config.createParams);

			this.current.config.runCount++;
			this.current.config.isCreated = true;
		}

		/**
		* Checks to see if the state has a init method and then executes that method if it is found.
		* @method checkInit
		* @private
		*/
		private checkInit() {
			
			//Has the state already been initialised?
			if (this.current.config.isInitialised === false) {

				//Boot the state.
				this.current.boot();

				//Execute the Init method with params
				this.current.init.apply(this.current, this.current.config.initParams);

				this.current.config.isInitialised = true;
			}

		}

		/**
		* Is execute whilst files are being loaded by the state.
		* @method onLoadProgress
		* @param percent {Number} The current percentage of files that have been loaded. Ranging from 0 - 1.
		* @param bytesLoaded {Number} The number of bytes that have been loaded so far.
		* @param file {Kiwi.Files.File} The last file that has been loaded.
		* @private
		*/
		private onLoadProgress(percent: number, bytesLoaded: number, file: Kiwi.Files.File) {

			this.current.loadProgress(percent, bytesLoaded, file);
		}

		/**
		* Executed when the preloading has completed. Then executes the loadComplete and create methods of the new State.
		* @method onLoadComplete
		* @private
		*/
		private onLoadComplete() {

			this.current.loadComplete();
			this._game.loader.onQueueProgress.remove(this.onLoadProgress, this);
			this._game.loader.onQueueComplete.remove(this.onLoadComplete, this);
			
			//Rebuild the Libraries again to have access the new files that were loaded.
			this.rebuildLibraries();
			this.current.config.isReady = true;
			this.callCreate();

		}

		/**
		* Rebuilds the texture, audio and data libraries that are on the current state. Thus updating what files the user has access to.
		* @method rebuildLibraries
		* @public
		*/
		public rebuildLibraries() {
			
			this.current.audioLibrary.rebuild(this._game.fileStore, this.current);
			this.current.dataLibrary.rebuild(this._game.fileStore, this.current);
			this.current.textureLibrary.rebuild(this._game.fileStore, this.current);
			if (this._game.renderOption == Kiwi.RENDERER_WEBGL) {
				this._game.renderer.initState(this.current);
			}
		}

		/**
		* The update loop that is accessible on the StateManager.
		* @method update
		* @public
		*/
		public update() {

			if (this.current !== null)
			{
				//Is the state ready?
				if (this.current.config.isReady === true)
				{
					this.current.preUpdate();
					this.current.update();
					this.current.postUpdate();
				}
				else
				{
					this.current.loadUpdate();
				}
			}

			//Do we need to switch states?
			if (this._newStateKey !== null) {
				this.bootNewState();
			}

		}

		/**
		* PostRender - Called after all of the rendering has been executed in a frame.
		* @method postRender
		* @public
		*/
		public postRender() {

			if (this.current !== null)
			{
				if (this.current.config.isReady === true)
				{
					this.current.postRender();
				}
			}

		}

	}

}