API Docs for: 1.4.0
Show:

File: src\time\Clock.ts

/**
* @module Kiwi
* @submodule Time
*/

module Kiwi.Time {

	/**
	* The Clock class offers a way of tracking time within a game.
	* When creating a new Clock you should NOT directly instantiate this class
	* but instead use the addClock method on a ClockManager.
	* - The MasterClock is a property of the Kiwi.Time.Manager class and tracks
	*   real world time in milliseconds elapsed since the application started.
	*   This happens automatically and there is no need to do anything to set
	*   this up.
	* - An instance of a clock is used to track time in arbitrary units
	*   (milliseconds by default)
	* - A clock can be started, paused, unpaused and stopped. Once stopped,
	*   re-starting the clock again will reset it. It can also have its time
	*   scale freely transformed.
	* - Any number of timers can be attached to a clock. See the Kiwi.Time.Timer
	*   class for timer details.
	* - If the clock is paused, any timers attached to the clock will take this
	*   into account and not continue to fire events until the clock is
	*   unpaused. (Note that this is not the same as pausing timers, which can
	*   be done manually and needs to be undone manually.)
	* - Animations and TweenManagers can use any Clock.
	* 
	* @class Clock
	* @namespace Kiwi.Time
	* @constructor
	* @param manager {ClockManager} ClockManager that this clock belongs to
	* @param master {Kiwi.Time.MasterClock} MasterClock that this is getting
	*	the time in relation to
	* @param name {String} Name of the clock
	* @param [units=1000] {Number} Units that this clock is to operate in
	*	per second
	* @return {Kiwi.Time.Clock} This Clock object
	*/
	export class Clock {

		constructor (manager:Kiwi.Time.ClockManager, master: Kiwi.Time.MasterClock, name: string, units: number = 1000) {

			this.manager = manager;
			this.master = master;
			this.name = name;
			this.units = units;
			this.timers = [];

			if (this.units < 1) {
				this.units = 1;
			}

			this._lastMasterElapsed = this.master.elapsed();
			this._currentMasterElapsed = this.master.elapsed();
		}

		/**
		* The type of object that this is
		* @method objType
		* @return {String} "Clock"
		* @public
		*/
		public objType() {
			return "Clock";
		}

		/**
		* Collection of Timer objects using this clock
		* @property timers
		* @type Timer[]
		* @private
		*/
		private timers: Kiwi.Time.Timer[];

		/**
		* Time the clock was first started relative to the master clock
		* @property _timeFirstStarted
		* @type Number
		* @default null
		* @private
		*/
		private _timeFirstStarted: number = null;

		/**
		* Number of clock units elapsed since the clock was first started
		* @method elapsedSinceFirstStarted
		* @return {Number} Number of clock units elapsed
		* @public
		*/
		public elapsedSinceFirstStarted(): number {

			return (this._timeLastStarted) ? (this.master.elapsed() - this._timeFirstStarted) / this.units : null;

		}

		/**
		* Most recent time the clock was started relative to the master clock
		* @property _timeLastStarted
		* @type Number
		* @default null
		* @private
		*/
		private _timeLastStarted: number = null;

		/**
		* Most recent time the clock was started relative to the master clock
		* @method started
		* @return {Number} Milliseconds
		* @public
		*/
		public started(): number {

			return this._timeLastStarted;

		}


		/**
		* Rate at which time passes on this clock.
		* 1 is normal speed. 1.5 is faster. 0 is no speed. -1 is backwards.
		* This mostly affects timers, animations and tweens.
		* @property timeScale
		* @type number
		* @default 1.0
		* @public
		* @since 1.2.0
		*/
		public timeScale: number = 1.0;

		/**
		* Clock units elapsed since the clock was most recently started,
		* not including paused time.
		* @property _elapsed
		* @type number
		* @private
		* @since 1.2.0
		*/
		private _elapsed: number = 0;

		/**
		* Master time on last frame
		* @property _lastMasterElapsed
		* @type number
		* @private
		* @since 1.2.0
		*/
		private _lastMasterElapsed: number;

		/**
		* Master time on current frame
		* @property _currentMasterElapsed
		* @type number
		* @private
		* @since 1.2.0
		*/
		private _currentMasterElapsed: number;


		/**
		* Rate of time passage, as modified by time scale and frame rate.
		* Under ideal conditions this should be 1.
		* If the frame rate drops, this will rise. Multiply transformations
		* by rate to get smooth change over time.
		* @property rate
		* @type number
		* @public
		* @since 1.2.0
		*/
		public rate: number = 1;

		/**
		* Maximum frame duration. If a frame takes longer than this to render,
		* the clock will only advance this far, in effect slowing down time.
		* If this value is 0 or less, it will not be checked and frames can
		* take any amount of time to render.
		* @property _maxFrameDuration
		* @type number
		* @default -1
		* @private
		*/
		private _maxFrameDuration: number = -1;

		/**
		* Maximum frame duration. If a frame takes longer than this to render,
		* the clock will only advance this far, in effect slowing down time.
		* If this value is 0 or less, it will not be checked and frames can
		* take any amount of time to render.
		* @property maxFrameDuration
		* @type number
		* @default -1
		* @public
		*/
		public get maxFrameDuration(): number {
			return this._maxFrameDuration;
		}

		public set maxFrameDuration( value: number ) {
			this._maxFrameDuration = value;
		}


		/**
		* Number of clock units elapsed since the clock was most recently
		* started (not including time spent paused)
		* @method elapsed
		* @return {Number} Number of clock units
		* @public
		*/
		public elapsed(): number {
			return this._elapsed;
		}

		/**
		* Time the clock was most recently stopped relative to the
		* master clock.
		* @property _timeLastStopped
		* @type Number
		* @default null
		* @private
		*/
		private _timeLastStopped: number = null;

		/**
		* Number of clock units elapsed since the clock was most recently
		* stopped.
		* @method elapsedSinceLastStopped
		* @return {Number} Number of clock units
		* @public
		*/
		public elapsedSinceLastStopped(): number {

			return (this._timeLastStarted) ? (this.master.elapsed() - this._timeLastStopped) / this.units : null;

		}

		/**
		* Time the clock was most receently paused relative to the
		* master clock.
		* @property _timeLastPaused
		* @private
		* @type Number
		* @default null
		* @private
		*/
		private _timeLastPaused: number = null;

		/**
		* Number of clock units elapsed since the clock was most recently paused.
		* @method elapsedSinceLastPaused
		* @return {Number} Number of clock units
		* @public
		*/
		public elapsedSinceLastPaused(): number {

			return (this._timeLastStarted) ? (this.master.elapsed() - this._timeLastPaused) / this.units : null;

		}

		/**
		* Time the clock was most recently unpaused relative to the
		* master clock.
		* @property _timeLastUnpaused
		* @private
		* @type Number
		* @default null
		* @private
		*/
		private _timeLastUnpaused: number = null;

		/**
		* Number of clock units elapsed since the clock was most recently
		* unpaused.
		* @method elapsedSinceLastUnpaused
		* @return {Number} Number of clock units
		* @public
		*/
		public elapsedSinceLastUnpaused(): number {

			return (this._timeLastStarted) ? (this.master.elapsed() - this._timeLastUnpaused) / this.units : null;

		}

		/**
		* Total number of milliseconds the clock has been paused
		* since it was last started
		* @property _totalPaused
		* @private
		* @type Number
		* @default 0
		* @private
		*/
		private _totalPaused: number = 0;

		/**
		* Whether the clock is in a running state
		* @property _isRunning
		* @type boolean
		* @default false
		* @private
		*/
		private _isRunning: boolean = false;

		/**
		* Check if the clock is currently running
		* @method isRunning
		* @return {boolean} `true` if running
		* @public
		*/
		public isRunning(): boolean {

			return this._isRunning;

		}

		/**
		* Whether the clock is in a stopped state
		* @property _isStopped
		* @type boolean
		* @default true
		* @private
		*/
		private _isStopped: boolean = true;

		/**
		* Check if the clock is in the stopped state
		* @method isStopped
		* @return {boolean} `true` if stopped
		* @public
		*/
		public isStopped(): boolean {

			return this._isStopped;

		}

		/**
		* Whether the clock is in a paused state
		* @property _isPaused
		* @type boolean
		* @default false
		* @private
		*/
		private _isPaused: boolean = false;

		/**
		* Check if the clock is in the paused state
		* @method isPaused
		* @return {boolean} `true` if paused
		* @public
		*/
		public isPaused(): boolean {

			return this._isPaused;

		}

		/**
		* Internal reference to the state of the elapsed timer
		* @property _elapsedState
		* @type Number
		* @private
		*/
		private _elapsedState: number = Kiwi.Time.Clock._RUNNING;

		/**
		* Time manager that this clock belongs to
		* @property manager
		* @type ClockManager 
		* @public
		*/
		public manager: Kiwi.Time.ClockManager = null;

		/**
		* Master clock from which time is derived
		* @property master
		* @type Kiwi.Time.MasterClock
		* @public
		*/
		public master: Kiwi.Time.MasterClock = null;

        /**
        * The time it takes for the time to update. Using this you can calculate the fps.
        * @property delta
        * @type number
        * @since 1.3.0
        * @readOnly
        * @public
        */
        public delta: number = 0;

		/**
		* Name of the clock
		* @property name
		* @type string
		* @public
		*/
		public name: string = null;

		/**
		* Number of milliseconds counted as one unit of time by the clock
		* @property units
		* @type Number
		* @default 0
		* @public
		*/
		public units: number = 0;

		/**
		* Constant indicating that the Clock is running
		* (and has not yet been paused and resumed)
		* @property _RUNNING
		* @static
		* @type number
		* @default 0
		* @private
		*/
		private static _RUNNING: number = 0;

		/**
		* Constant indicating that the Clock is paused
		* @property _PAUSED
		* @static
		* @type number
		* @default 1
		* @private
		*/
		private static _PAUSED: number = 1;

		/**
		* Constant indicating that the Clock is running
		* (and has been paused then resumed)
		* @property _RESUMED
		* @static
		* @type number
		* @default 2
		* @private
		*/
		private static _RESUMED: number = 2;

		/**
		* Constant indicating that the Clock is stopped
		* @property _STOPPED
		* @static
		* @type number
		* @default 3
		* @private
		*/
		private static _STOPPED: number = 3;

		/**
		* Add an existing Timer to the Clock.
		* @method addTimer
		* @param timer {Timer} Timer object instance to be added to this Clock
		* @return {Kiwi.Time.Clock} This Clock object
		* @public
		*/
		public addTimer(timer: Timer): Clock {

			this.timers.push(timer);

			return this;
		}

		/**
		* Create a new Timer and add it to this Clock.
		* @method createTimer
		* @param name {string} Name of the Timer (must be unique on this Clock)
		* @param [delay=1] {Number} Number of clock units to wait between
		*	firing events
		* @param [repeatCount=0] {Number} Number of times to repeat the Timer 
		*	(default 0)
		* @param [start=true] {Boolean} If the timer should start
		* @return {Kiwi.Time.Timer} The newly created Timer
		* @public
		*/
		public createTimer(name: string, delay: number = 1, repeatCount: number = 0, start: boolean=true): Timer {

			this.timers.push(new Timer(name, this, delay, repeatCount));

			if (start === true) {
				this.timers[this.timers.length - 1].start();
			}

			return this.timers[this.timers.length - 1];

		}

		/**
		* Remove a Timer from this Clock based on either the Timer object
		* or its name.
		* @method removeTimer
		* @param [timer=null] {Timer} Timer object you wish to remove.
		*	If you wish to delete by Timer Name set this to null.
		* @param [timerName=''] {string} Name of the Timer object to remove
		* @return {boolean} `true` if the Timer was successfully removed
		* @public
		*/
		public removeTimer(timer: Timer = null, timerName:string = ""): boolean {
			var index;

			//  Timer object given?
			if ( timer !== null ) {
				index = this.timers.indexOf( timer );
				if ( index !== -1 ) {
					this.timers.splice( index, 1 );
					return true;
				}
			} else if ( timerName !== "" ) {
				for ( index = 0; index < this.timers.length; index++ ) {
					if ( this.timers[ index ].name === timerName ) {
						this.timers.splice( index, 1 );
						return true;
					}
				}
			}

			return false;
		}

		/**
		* Check if the Timer already exists on this Clock.
		* @method checkExists
		* @param name {string} Name of the Timer
		* @return {boolean} `true` if the Timer exists
		* @public
		*/
		public checkExists(name: string): boolean {

			if (this.timers[name])
			{
				return true;
			}
			else
			{
				return false;
			}

		}

		/**
		* Stop all timers attached to the clock.
		* @method stopAllTimers
		* @return {Clock} This Clock object
		* @public
		*/
		public stopAllTimers(): Clock {

			for (var i = 0; i < this.timers.length; i++)
			{
				this.timers[i].stop();
			}

			return this;

		}

		/**
		* Convert a number to milliseconds based on clock units.
		* @method toMilliseconds
		* @param time {number} Seconds
		* @return {Number} Milliseconds
		* @public
		*/
		public convertToMilliseconds(time: number): number {

			return time * this.units;

		}

		/**
		* Update all Timers linked to this Clock.
		* @method update
		* @public
		*/
		public update() {
			var frameLength = this._currentMasterElapsed - this._lastMasterElapsed;

			if ( this._maxFrameDuration > 0 ) {
				frameLength = Math.min( frameLength, this._maxFrameDuration );
			}

			for (var i = 0; i < this.timers.length; i++) {
				this.timers[i].update();
			}

			// Compute difference between last master value and this
			// If clock is running, add that value to the current time
			this._lastMasterElapsed = this._currentMasterElapsed;
            this._currentMasterElapsed = this.master.elapsed();
            this.delta = 0;

			if ( this._elapsedState === Kiwi.Time.Clock._RUNNING || this._elapsedState === Kiwi.Time.Clock._RESUMED ) {
			    // Scale that difference by timeScale. Set "rate" as per running type
                this.delta = this.timeScale * frameLength / this.units;
				this._elapsed += this.delta;
                this.rate = this.timeScale * frameLength / this.master.idealDelta;

			} else if ( this._elapsedState === Kiwi.Time.Clock._PAUSED ) {
				this._totalPaused += frameLength;
                this.rate = 0;

			} else if ( this._elapsedState === Kiwi.Time.Clock._STOPPED ) {
                this.rate = 0;

			}
		}

		/**
		* Start the clock. This resets the clock and starts it running.
		* @method start
		* @return {Clock} This Clock object
		* @public
		*/
		public start(): Clock {

			this._timeLastStarted = this.master.elapsed();
			this._totalPaused = 0;

			if (!this._timeFirstStarted)
			{
				this._timeFirstStarted = this._timeLastStarted;
			}

			this._isRunning = true;
			this._isPaused = false;
			this._isStopped = false;

			this._elapsedState = Kiwi.Time.Clock._RUNNING;

			this._elapsed = 0;
			this._lastMasterElapsed = this.master.elapsed();
			this._currentMasterElapsed = this.master.elapsed();

			return this;

		}

		/**
		* Pause the clock. This can only be paused if it is already running.
		* @method pause
		* @return {Kiwi.Time.Clock} This Clock object
		* @public
		*/
		public pause(): Clock {

			if (this._isRunning === true)
			{
				this._timeLastPaused = this.master.elapsed();
				this._isRunning = false;
				this._isPaused = true;
				this._isStopped = false;

				this._elapsedState = Kiwi.Time.Clock._PAUSED;
			}

			return this;
		}

		/**
		* Resume the clock. This can only be resumed if it is already paused.
		* @method resume
		* @return {Kiwi.Time.Clock} This Clock object
		* @public
		*/
		public resume(): Clock {

			if (this._isPaused === true)
			{
				this._timeLastUnpaused = this.master.elapsed();
				this._totalPaused += this._timeLastUnpaused - this._timeLastPaused;
				this._isRunning = true;
				this._isPaused = false;
				this._isStopped = false;

				this._elapsedState = Kiwi.Time.Clock._RESUMED;
			}

			return this;

		}

		/**
		* Stop the clock. This can only be stopped if it is already running
		*	or is paused.
		* @method stop
		* @return {Kiwi.Time.Clock} This Clock object
		* @public
		*/
		public stop(): Clock {

			if (this._isStopped === false)
			{
				this._timeLastStopped = this.master.elapsed();

				if (this._isPaused === true)
				{
					this._totalPaused += this._timeLastStopped - this._timeLastPaused;
				}

				this._isRunning = false;
				this._isPaused = false;
				this._isStopped = true;

				this._elapsedState = Kiwi.Time.Clock._STOPPED;
			}

			return this;

		}

		/**
		* Return a string representation of this object.
		* @method toString
		* @return {string} String representation of the instance
		* @public
		*/
		public toString(): string {

			return "[{Clock (name=" + this.name + " units=" + this.units + " running=" + this._isRunning + ")}]";

		}


		/**
		* Set a function to execute after a certain time interval.
		* Emulates `window.setTimeout`, except attached to a `Kiwi.Time.Clock`.
		* This allows you to pause and manipulate time, and the timeout will
		* respect the clock on which it is created.
		*
		* No `clearTimeout` is provided; you should use `Kiwi.Time.Timer`
		* functions to achieve further control.
		*
		* Any parameters after `context` will be passed as parameters to the
		* callback function. Note that you must specify `context` in order for
		* this to work. You may specify `null`, in which case it will default
		* to the global scope `window`.
		*
		* @method setTimeout
		* @param callback {function} Function to execute
		* @param timeout {number} Milliseconds before execution
		* @param [context] {object} Object to be `this` for the callback
		* @return {Kiwi.Time.Timer} Kiwi.Time.Timer object which can be used
		*	to further manipulate the timer
		* @public
		*/
		public setTimeout( callback, timeout: number, context, ...args ): Timer {
			var clock = this,
				timer = this.createTimer( "timeoutTimer", timeout / this.units );

			if ( !context ) {
				context = this;
			}

			timer.createTimerEvent( TimerEvent.TIMER_STOP,
				function() {
					callback.apply( context, args );
					clock.removeTimer( timer );
				},
				context );

			timer.start();

			return timer;
		}


		/**
		* Set a function to repeatedly execute at fixed time intervals.
		* Emulates `window.setInterval`, except attached to a `Kiwi.Time.Clock`.
		* This allows you to pause and manipulate time, and the timeout will
		* respect the clock on which it is created.
		*
		* No `clearInterval` is provided; you should use `Kiwi.Time.Timer`
		* functions to achieve further control.
		*
		* Any parameters after `context` will be passed as parameters to the
		* callback function. Note that you must specify `context` in order for
		* this to work. You may specify `null`, in which case it will default
		* to the global scope `window`.
		*
		* @method setInterval
		* @param callback {function} Function to execute
		* @param timeout {number} Milliseconds between executions
		* @param [context=window] {object} Object to be `this` for the callback
		* @return {Kiwi.Time.Timer} Kiwi.Time.Timer object
		*   which can be used to further manipulate the timer
		* @public
		*/
		public setInterval( callback, timeout: number, context, ...args ): Timer {
			var timer = this.createTimer( "intervalTimer", timeout / this.units, -1 );

			if ( !context ) {
				context = this;
			}

			timer.createTimerEvent( TimerEvent.TIMER_COUNT,
				function() {
					callback.apply( context, args );
				},
				context );

			timer.start();

			return timer;
		}

	}

}