[Golist] [goasap commit] r59 - 0.5.2 branch initial commit

codesite-noreply at google.com codesite-noreply at google.com
Sun Mar 15 17:48:54 PDT 2009


Author: mosesoak
Date: Sun Mar 15 17:46:33 2009
New Revision: 59

Added:
     
branches/goasap0.5.2/src_go/org/goasap/utils/customadvance/ImmediateAdvance.as
Modified:
    branches/goasap0.5.2/src_go/org/goasap/GoEngine.as
    branches/goasap0.5.2/src_go/org/goasap/items/LinearGo.as
    branches/goasap0.5.2/src_go/org/goasap/utils/PlayableGroup.as
    branches/goasap0.5.2/src_go/org/goasap/utils/Sequence.as
    branches/goasap0.5.2/src_go/org/goasap/utils/SequenceBase.as
    branches/goasap0.5.2/src_go/org/goasap/utils/SequenceCA.as
    branches/goasap0.5.2/src_go/org/goasap/utils/SequenceStep.as
    branches/goasap0.5.2/src_go/org/goasap/utils/SequenceStepCA.as
     
branches/goasap0.5.2/src_go/org/goasap/utils/customadvance/OnPlayableComplete.as

Log:
0.5.2 branch initial commit

Modified: branches/goasap0.5.2/src_go/org/goasap/GoEngine.as
==============================================================================
--- branches/goasap0.5.2/src_go/org/goasap/GoEngine.as	(original)
+++ branches/goasap0.5.2/src_go/org/goasap/GoEngine.as	Sun Mar 15 17:46:33  
2009
@@ -1 +1 @@
-/**
  * Copyright (c) 2007 Moses Gunesch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a  
copy
  * of this software and associated documentation files (the "Software"), to  
deal
  * in the Software without restriction, including without limitation the  
rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included  
in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  
OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  
THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  
FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
package org.goasap {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.Dictionary;
	import flash.utils.Timer;
	import flash.utils.getQualifiedClassName;
	import flash.utils.getTimer;
	
	import org.goasap.errors.DuplicateManagerError;
	import org.goasap.interfaces.IManageable;
	import org.goasap.interfaces.IManager;
	import org.goasap.interfaces.IUpdatable;
	import org.goasap.interfaces.ILiveManager;	

	/**
	 * Provides <code>update</code> calls to <code>IUpdatable</code> instances  
on their specified <code>pulseInterval</code>.
	 *
	 * <blockquote><blockquote>
	 * <p><b>Using these Docs</b></p>
	 *
	 * <p><i>Protected methods and properties have been excluded in almost all
	 * cases, but are documented in the classes. Exceptions include key  
protected
	 * methods or properties that are integral for writing subclasses or  
understanding
	 * the basic mechanics of the system. Many Go classes can be used as is  
without
	 * subclassing, so the documentation offers an uncluttered view of their  
public
	 * usage.</i></p>
	 *
	 * <p><b>Introduction to Go</b> <font color="#CC0000">[This section  
updated recently!]</font></p>
	 *
	 * <p>The Go ActionScript Animation Platform ("GOASAP") is a lightweight,  
portable
	 * set of generic base classes for buliding AS3 animation tools. It  
provides structure
	 * and core functionality, but does not define the specifics of  
animation-handling
	 * classes like tweens.</p>
	 *
	 * <p>GoASAP could be broken up into the following general layers:
	 * <ul>
	 * <li><i>Compatibility:</i> In general, this layer can be used in about  
any animation
	 * system. GoEngine, GoEvent, PlayStates and IPlayable.</li>
	 * <li><i>Items:</i> Base classes for utilities and animation items:  
PlayableBase, GoItem,
	 * LinearGo and PhysicsGo.</li>
	 * <li><i>Utilities:</i> You can write utility classes to manage items. Go  
ships with a
	 * few common ones: a parallel item class called PlayableGroup, and  
several Sequence classes.</li>
	 * <li><i>Automation/Management:</i> GoEngine provides a simple,  
centralized and fully extensible
	 * way for you to automate any time-based process and manage multiple  
items at once.</li>
	 * </ul></p>
	 *
	 * <p>GoASAP provides an intentionally loose standard, in that it does not  
intend to limit
	 * the possibilities of what can be built with it. Its primary benefits  
are compatibility and
	 * synchronicity between animation systems, absolute extensibility into  
any time-based process,
	 * and a much faster and easier way to build your own animation tools from  
scratch.</p>
	 *
	 * <p><i>Important: Store your custom Go classes in a package bearing your  
own classpath, not
	 * in the core package! This will help avoid confusion with other authors'  
work.</i></p>
	 *
	 * <p><font size="-2">You may modify any class in the goasap package to  
suit your project's needs. Your input
	 * is valuable! Please join the mailing list and share your Go-based  
animation tools at the
	 * GoPlayground repository. The GoASAP initiative is led by Moses Gunesch  
at
	 * <a href="http://www.mosessupposes.com/"  
target="_top">MosesSupposes.com</a>. Please visit the
	 * <a href="http://www.goasap.org/" target="_top">Go website</a> for more  
information.</font></p>
	 * </blockquote></blockquote>
	 *
	 * <p><b>GoEngine</b> <font color="#CC0000">[This section updated  
recently!]</font></p>
	 *
	 * <p>GoEngine sits at the center of the Go system, and along with the  
IUpdatable
	 * interface is the only required element for using GoASAP. GoEngine  
manages tightly
	 * synchronized item lists, since updating items in groups enhances  
efficiency. An
	 * advantage of GoASAP is that wildly different animation systems can be  
used together
	 * in the same project. Their synchronous updates will remain as efficient  
as possible,
	 * instead of fighting one another for processor cycles.</p>
	 *
	 * <p>GoEngine's default pulse rate is ENTER_FRAME which yields the  
smoothest processing in the
	 * Flash Player. However, it does not run on any one specific pulse.  
Instead, any object that is
	 * IUpdatable may specify its own pulse rate, and items with matching  
pulses are automatically
	 * grouped into update lists for efficiency. On a fine-tuning level,  
GoEngine uses a few other
	 * tricks to try and provide the tightest possible visual synchronization  
for larger batches of
	 * animation items. It passes the clock time at the start of each update  
cycle to each item in
	 * that list, which can be used in place of realtime to counteract any  
offset due to processing
	 * lag during the cycle. Additionally, items that get added <i>during</i>  
an update cycle are
	 * queued until the next update.</p>
	 *
	 * <p>GoASAP's management layer is made up of three interfaces that are  
referenced by GoEngine:
	 * IManager, ILiveManager and IManageable. Managers are always optional in  
GoASAP, and are only
	 * activated by calling <code>GoEngine.addManager()</code>. Managers can  
automate processes
	 * as items are added and removed, such as the included OverlapMonitor  
class which prevents
	 * property conflicts between items, or they can automate "live" processes  
that occur on each
	 * pulse. No live managers are included but an example might be a class  
that re-renders a 3D
	 * viewport after all 3D tweens have been processed. This can of course be  
done without a custom
	 * manager, but by using GoASAP you gain a unique ability to very cleanly  
and simply tie any
	 * custom routines in your project right into your animation processing,  
in perfect sync and
	 * with maximum efficiency.</p>
	 *
	 * <p></i>{In the game of Go, the wooden playing board, or Goban, features  
a grid
	 *  on which black & white go-ishi stones are laid at its  
intersections.}</i></p>
	 *
	 * @see org.goasap.items.LinearGo LinearGo
	 * @see org.goasap.interfaces.IManager IManager
	 * @author Moses Gunesch
	 */
	public class GoEngine
	{
		// -== Constants ==-
		
		public static const INFO:String = "GoASAP 0.5.1e (c) Moses Gunesch, MIT  
Licensed.";
		
		// -== Settable Class Defaults ==-
		
		/**
		 * A pulseInterval that runs on the player's natural framerate,
		 * which is often most efficient.
		 */
		public static const ENTER_FRAME	: int = -1;

		// -== Protected Properties ==-
		
		// Note: Various formats for item data have been experimented with  
including breaking the item lists out into
		// a GoEngineList class, which was nicer-looking but did not perform  
well. Since GoEngine doesn't normally
		// require active work, this less-pretty but efficient flat-data format  
was opted for. A minor weakness of this
		// format is its use of a Dictionary, which means update calls are not  
ordered like they would be with an Array.
		// The Dictionary stores items' pulseInterval values, which is safer than  
relying on items to not change them.
		// Tests also show that Dictionary performs faster than Array for  
accessing and deleting items.
		private static var managerTable : Object = new Object(); // registration  
list of IManager instances
		private static var managers : Array = new Array(); // ordered  
registration list of IManager instances
		private static var liveManagers : uint = 0;
		private static var timers : Dictionary = new Dictionary(false); // key:  
pulseInterval, value: Timer for that pulse
		private static var items : Dictionary = new Dictionary(false); // key:  
IUpdatable item, value: pulseInterval at add.
		private static var itemCounts : Dictionary = new Dictionary(false); //  
key: pulseInterval, value: item count for that pulse
		private static var pulseSprite : Sprite; // used for ENTER_FRAME pulse
		private static var paused : Boolean = false;
		
		// These additional lists enables caching of items that are added during  
the update cycle for the same pulse.
		// This prevents groups & sequences from going out of sync by ensuring  
that each cycle completes before new items are added.
		private static var lockedPulses : Dictionary = new Dictionary(false); //  
key: pulseInterval, value: true
		private static var delayedPulses : Dictionary = new Dictionary(false); //  
key: pulseInterval, value: true
		private static var addQueue : Dictionary = new Dictionary(false); // key:  
IUpdatable item, value: true
		
		// -== Public Class Methods ==-
		
		/**
		 * @param className		A string naming the manager class, such  
as "OverlapMonitor".
		 * @return				The manager instance, if registered.
		 * @see #addManager()
		 * @see #removeManager()
		 */
		public static function getManager(className:String) : IManager
		{
			return managerTable[ className ];
		}
		
		/**
		 * Enables the extending of this class' functionality with a tight
		 * coupling to an IManager.
		 *
		 * <p>Tight coupling is crucial in such a time-sensitive context;
		 * standard events are too asynchronous. All items that implement
		 * IManageable are reported to registered managers as they add and
		 * remove themselves from GoEngine.</p>
		 *
		 * <p>Managers normally act as singletons within the Go system (which
		 * you are welcome to modify). This method throws a DuplicateManagerError
		 * if an instance of the same manager class is already registered. Use a
		 * try/catch block when calling this method if your program might  
duplicate
		 * managers, or use getManager() to check for prior registration.</p>
		 *
		 * @param instance	An instance of a manager you wish to add.
		 * @see #getManager()
		 * @see #removeManager()
		 */
		public static function addManager( instance:IManager ):void
		{
			var className:String = getQualifiedClassName(instance);
			className = className.slice(className.lastIndexOf("::")+2);
			if (managerTable[ className ]) {
				throw new DuplicateManagerError( className );
				return;
			}
			managerTable[ className ] = instance;
			managers.push(instance);
			if (instance is ILiveManager) liveManagers++;
		}
		
		/**
		 * Unregisters any manager set in <code>addManager</code>.
		 *
		 * @param className		A string naming the manager class, such  
as "OverlapMonitor".
		 * @see #getManager()
		 * @see #addManager()
		 */
		public static function removeManager( className:String ):void
		{
			managers.splice(managers.indexOf(managerTable[ className ]), 1);
			if (managerTable[ className ] is ILiveManager)
				liveManagers--;
			delete managerTable[ className ]; // leave last
		}
		
		/**
		 * Test whether an item is currently stored and being updated by the  
engine.
		 *
		 * @param item		Any object implementing IUpdatable
		 * @return			Whether the IUpdatable is in the engine
		 */
		public static function hasItem( item:IUpdatable ):Boolean
		{
			return (items[ item ]!=null);
		}
		
		/**
		 * Adds an IUpdatable instance to an update-queue corresponding to
		 * the item's pulseInterval property.
		 *
		 * @param item		Any object implementing IUpdatable that wishes
		 * 					to receive update calls on a pulse.
		 * 					
		 * @return			Returns false only if this item was already in the
		 * 					engine under the same pulse. (If an existing item is added
		 * 					but the pulseInterval has changed it will be removed,
		 * 					re-added, and true will be returned.)
		 * 					
		 * @see #removeItem()
		 */
		public static function addItem( item:IUpdatable ):Boolean
		{
			// Group items by pulse for efficient update cycles.
			var interval:int = item.pulseInterval;
			if (items[ item ]) {
				if (items[ item ] == item.pulseInterval)
					return false;
				else
					removeItem(item);
			}
			if (lockedPulses[ interval ]==true) { // this prevents items from being  
added during an update loop in progress.
				delayedPulses[ interval ] = true; // flags update to clear the queue  
when the in-progress loop completes.
				addQueue[ item ] = true; // for tightest syncing of item groups, read  
the documentation under GoItem.update().
			}
			items[ item ] = interval; // Tether item to original pulseint. Used in  
removeItem & setPaused(false).
			if (!timers[ interval ]) {
				addPulse( interval );
				itemCounts[ interval ] = 1;
			}
			else {
				itemCounts[ interval ] ++;
			}
			// Report IManageable instances to registered managers
			if (item is IManageable) {
				for each (var manager:IManager in managers)
					manager.reserve( item as IManageable );
			}
			return true;
		}
		
		/**
		 * Removes an item from the queue and removes its pulse timer if
		 * the queue is depleted.
		 *
		 * @param item		Any IUpdatable previously added that wishes
		 * 					to stop receiving update calls.
		 * 					
		 * @return			Returns false if the item was not in the engine.
		 *
		 * @see #addItem()
		 */
		public static function removeItem( item:IUpdatable ):Boolean
		{
			if (items[ item ]==null)
				return false;
			var interval: int = items[ item ];
			if ( -- itemCounts[ interval ] == 0 ) {
				removePulse( interval );
				delete itemCounts[ interval ];
			}
			delete items[ item ];
			delete addQueue[ item ]; // * see note following update
			// Report IManageable item removal to registered managers.
			if (item is IManageable) {
				for each (var manager:IManager in managers)
					manager.release( item as IManageable );
			}
			return true;
		}
		
		/**
		 * Removes all items and resets the engine,
		 * or removes just items running on a specific pulse.
		 *
		 * @param pulseInterval		Optionally filter by a specific pulse
		 * 							such as ENTER_FRAME or a number of milliseconds.
		 * @return					The number of items successfully removed.
		 * @see #removeItem()
		 */
		public static function clear(pulseInterval:Number = NaN) : uint
		{
			var all:Boolean = (isNaN(pulseInterval));
			var n:Number = 0;
			for (var item:Object in items) {
				if (all || items[ item ]==pulseInterval)
					if (removeItem(item as IUpdatable)==true)
						n++;
			}
			return n;
		}
		
		/**
		 * Retrieves number of active items in the engine
		 * or active items running on a specific pulse.
		 *
		 * @param pulseInterval		Optionally filter by a specific pulseInterval
		 *							such as ENTER_FRAME or a number of milliseconds.
		 *
		 * @return					Number of active items in the Engine.
		 */
		public static function getCount(pulseInterval:Number = NaN) : uint
		{
			if (!isNaN(pulseInterval))
				return (itemCounts[pulseInterval]);
			var n:Number = 0;
			for each (var count: int in itemCounts)
				n += count;
			return n;
		}
		
		/**
		 * @return			The paused state of engine.
		 * @see #setPaused()
		 */
		public static function getPaused() : Boolean {
			return paused;
		}
		
		/**
		 * Pauses or resumes all animation globally by suspending processing,
		 * and calls pause() or resume() on each item with those methods.
		 *
		 * <p>The return value only reflects how many items had pause() or  
resume()
		 * called on them, but the GoEngine.getPaused() state will change if any
		 * pulses are suspended or resumed.</p>
		 *
		 * @param pause				Pass false to resume if currently paused.
		 * @param pulseInterval		Optionally filter by a specific pulse
		 * 							such as ENTER_FRAME or a number of milliseconds.
		 * @return					The number of items on which a pause() or resume()
		 * 							method was called (0 doesn't necessarily reflect
		 * 							whether the GoEngine.getPaused() state changed, it
		 * 							may simply indicate that no items had that method).
		 * @see #resume()
		 */
		public static function setPaused(pause:Boolean=true, pulseInterval:Number  
= NaN) : uint
		{
			if (paused==pause) return 0;
			var n:Number = 0;
			var pulseChanged:Boolean = false;
			var all:Boolean = (isNaN(pulseInterval));
			var method:String = (pause ? "pause" : "resume");
			for (var item:Object in items) {
				var pulse:int = (items[item] as int);
				if (all || pulse==pulseInterval) {
					pulseChanged = (pulseChanged || (pause ? removePulse(pulse) :  
addPulse(pulse)));
					// call pause or resume on the item if it has such a method.
					if (item.hasOwnProperty(method)) {
						if (item[method] is Function) {
							item[method].apply(item);
							n++;
						}
					}
				}
			}
			if (pulseChanged)
				paused = pause;
			return n;
		}
		
		// -== Private Class Methods ==-
		
		/**
		 * Executes the update queue corresponding to the dispatcher's interval.
		 *
		 * @param event			TimerEvent or Sprite ENTER_FRAME Event
		 */
		private static function update(event:Event) : void
		{
			var currentTime:Number = getTimer();
			var pulse:int = (event is TimerEvent ? ( event.target as Timer ).delay :  
ENTER_FRAME);
			lockedPulses[ pulse ] = true;
			var doLiveUpdate:Boolean = (liveManagers > 0);
			var updated:Array;
			if (doLiveUpdate) updated = []; // syncs the live manager list to items  
actually updated
			for (var item:* in items) {
				if (items[ item ]==pulse && !addQueue[ item ]) {
					(item as IUpdatable).update(currentTime);
					if (doLiveUpdate) updated.push(item);
				}
			}
			lockedPulses[ pulse ] = false;
			if (delayedPulses[ pulse ]) {
				for (item in addQueue)
					delete addQueue[ item ];
				delete delayedPulses[ pulse ];
			}
// updateAfterEvent() should not be needed as long as items follow  
tight-syncing instructions in GoItem.update() documentation.
//			if (pulse!=ENTER_FRAME) (event as TimerEvent).updateAfterEvent();
			if (doLiveUpdate)
				for each (var manager:Object in managers)
					if (manager is ILiveManager)
						(manager as ILiveManager).onUpdate(pulse, updated, currentTime);  //  
* see note
		}
// * note: In one rare case that has not been reported yet but is  
theoretically possible, the 'updated' list
// passed could contain already-released items. This could only happen if  
the item is removed & released
// just after the main update cycle but before the the doLiveUpdate()  
routine runs. If you encounter this issue
// please report it to the GoASAP mailing list, it's too involved to bother  
with before it's a problem.

		/**
		 * Creates new timers when a previously unused interval is specified,
		 * and tracks the number of items associated with that interval.
		 *
		 * @param pulse			The pulseInterval requested
		 * @return				Whether a pulse was added
		 */
		private static function addPulse(pulse : int) : Boolean
		{
			if (pulse==ENTER_FRAME) {
				if (!pulseSprite) {
					timers[ENTER_FRAME] = pulseSprite = new Sprite();
					pulseSprite.addEventListener(Event.ENTER_FRAME, update);
				}
				return true;
			}
			var t:Timer = timers[ pulse ] as Timer;
			if (!t) {
				t = timers[ pulse ] = new Timer(pulse);
				(timers[ pulse ] as Timer).addEventListener(TimerEvent.TIMER, update);
				t.start();
				return true;
			}
			return false;
		}
		
		/**
		 * Tracks whether a removed item was the last one using a timer
		 * and if so, removes that timer.
		 *
		 * @param pulse			The pulseInterval corresponding to an item being  
removed.
		 * @return				Whether a pulse was removed
		 */
		private static function removePulse(pulse : int) : Boolean
		{
			if (pulse==ENTER_FRAME) {
				if (pulseSprite) {
					pulseSprite.removeEventListener(Event.ENTER_FRAME, update);
					delete timers[ ENTER_FRAME ];
					pulseSprite = null;
					return true;
				}
			}
			var t:Timer = timers[ pulse ] as Timer;
			if (t) {
				t.stop();
				t.removeEventListener(TimerEvent.TIMER, update);
				delete timers[ pulse ];
				return true;
			}
			return false;
		}
	}
}
\ No newline at end of file
+/**
  * Copyright (c) 2007 Moses Gunesch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a  
copy
  * of this software and associated documentation files (the "Software"), to  
deal
  * in the Software without restriction, including without limitation the  
rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included  
in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  
OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  
THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  
FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
package org.goasap {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.Dictionary;
	import flash.utils.Timer;
	import flash.utils.getQualifiedClassName;
	import flash.utils.getTimer;
	
	import org.goasap.errors.DuplicateManagerError;
	import org.goasap.interfaces.IManageable;
	import org.goasap.interfaces.IManager;
	import org.goasap.interfaces.IUpdatable;
	import org.goasap.interfaces.ILiveManager;	

	/**
	 * Provides <code>update</code> calls to <code>IUpdatable</code> instances  
on their specified <code>pulseInterval</code>.
	 *
	 * <blockquote><blockquote>
	 * <p><b>Using these Docs</b></p>
	 *
	 * <p><i>Protected methods and properties have been excluded in almost all
	 * cases, but are documented in the classes. Exceptions include key  
protected
	 * methods or properties that are integral for writing subclasses or  
understanding
	 * the basic mechanics of the system. Many Go classes can be used as is  
without
	 * subclassing, so the documentation offers an uncluttered view of their  
public
	 * usage.</i></p>
	 *
	 * <p><b>Introduction to Go</b> <font color="#CC0000">[This section  
updated recently!]</font></p>
	 *
	 * <p>The Go ActionScript Animation Platform ("GOASAP") is a lightweight,  
portable
	 * set of generic base classes for buliding AS3 animation tools. It  
provides structure
	 * and core functionality, but does not define the specifics of  
animation-handling
	 * classes like tweens.</p>
	 *
	 * <p>GoASAP could be broken up into the following general layers:
	 * <ul>
	 * <li><i>Compatibility:</i> In general, this layer can be used in about  
any animation
	 * system. GoEngine, GoEvent, PlayStates and IPlayable.</li>
	 * <li><i>Items:</i> Base classes for utilities and animation items:  
PlayableBase, GoItem,
	 * LinearGo and PhysicsGo.</li>
	 * <li><i>Utilities:</i> You can write utility classes to manage items. Go  
ships with a
	 * few common ones: a parallel item class called PlayableGroup, and  
several Sequence classes.</li>
	 * <li><i>Automation/Management:</i> GoEngine provides a simple,  
centralized and fully extensible
	 * way for you to automate any time-based process and manage multiple  
items at once.</li>
	 * </ul></p>
	 *
	 * <p>GoASAP provides an intentionally loose standard, in that it does not  
intend to limit
	 * the possibilities of what can be built with it. Its primary benefits  
are compatibility and
	 * synchronicity between animation systems, absolute extensibility into  
any time-based process,
	 * and a much faster and easier way to build your own animation tools from  
scratch.</p>
	 *
	 * <p><i>Important: Store your custom Go classes in a package bearing your  
own classpath, not
	 * in the core package! This will help avoid confusion with other authors'  
work.</i></p>
	 *
	 * <p><font size="-2">You may modify any class in the goasap package to  
suit your project's needs. Your input
	 * is valuable! Please join the mailing list and share your Go-based  
animation tools at the
	 * GoPlayground repository. The GoASAP initiative is led by Moses Gunesch  
at
	 * <a href="http://www.mosessupposes.com/"  
target="_top">MosesSupposes.com</a>. Please visit the
	 * <a href="http://www.goasap.org/" target="_top">Go website</a> for more  
information.</font></p>
	 * </blockquote></blockquote>
	 *
	 * <p><b>GoEngine</b> <font color="#CC0000">[This section updated  
recently!]</font></p>
	 *
	 * <p>GoEngine sits at the center of the Go system, and along with the  
IUpdatable
	 * interface is the only required element for using GoASAP. GoEngine  
manages tightly
	 * synchronized item lists, since updating items in groups enhances  
efficiency. An
	 * advantage of GoASAP is that wildly different animation systems can be  
used together
	 * in the same project. Their synchronous updates will remain as efficient  
as possible,
	 * instead of fighting one another for processor cycles.</p>
	 *
	 * <p>GoEngine's default pulse rate is ENTER_FRAME which yields the  
smoothest processing in the
	 * Flash Player. However, it does not run on any one specific pulse.  
Instead, any object that is
	 * IUpdatable may specify its own pulse rate, and items with matching  
pulses are automatically
	 * grouped into update lists for efficiency. On a fine-tuning level,  
GoEngine uses a few other
	 * tricks to try and provide the tightest possible visual synchronization  
for larger batches of
	 * animation items. It passes the clock time at the start of each update  
cycle to each item in
	 * that list, which can be used in place of realtime to counteract any  
offset due to processing
	 * lag during the cycle. Additionally, items that get added <i>during</i>  
an update cycle are
	 * queued until the next update.</p>
	 *
	 * <p>GoASAP's management layer is made up of three interfaces that are  
referenced by GoEngine:
	 * IManager, ILiveManager and IManageable. Managers are always optional in  
GoASAP, and are only
	 * activated by calling <code>GoEngine.addManager()</code>. Managers can  
automate processes
	 * as items are added and removed, such as the included OverlapMonitor  
class which prevents
	 * property conflicts between items, or they can automate "live" processes  
that occur on each
	 * pulse. No live managers are included but an example might be a class  
that re-renders a 3D
	 * viewport after all 3D tweens have been processed. This can of course be  
done without a custom
	 * manager, but by using GoASAP you gain a unique ability to very cleanly  
and simply tie any
	 * custom routines in your project right into your animation processing,  
in perfect sync and
	 * with maximum efficiency.</p>
	 *
	 * <p></i>{In the game of Go, the wooden playing board, or Goban, features  
a grid
	 *  on which black & white go-ishi stones are laid at its  
intersections.}</i></p>
	 *
	 * @see org.goasap.items.LinearGo LinearGo
	 * @see org.goasap.interfaces.IManager IManager
	 * @author Moses Gunesch
	 */
	public class GoEngine
	{
		// -== Constants ==-
		
		public static const INFO:String = "GoASAP 0.5.2 (c) Moses Gunesch, MIT  
Licensed.";
		
		// -== Settable Class Defaults ==-
		
		/**
		 * A pulseInterval that runs on the player's natural framerate,
		 * which is often most efficient.
		 */
		public static const ENTER_FRAME	: int = -1;

		// -== Protected Properties ==-
		
		// Note: Various formats for item data have been experimented with  
including breaking the item lists out into
		// a GoEngineList class, which was nicer-looking but did not perform  
well. Since GoEngine doesn't normally
		// require active work, this less-pretty but efficient flat-data format  
was opted for. A minor weakness of this
		// format is its use of a Dictionary, which means update calls are not  
ordered like they would be with an Array.
		// The Dictionary stores items' pulseInterval values, which is safer than  
relying on items to not change them.
		// Tests also show that Dictionary performs faster than Array for  
accessing and deleting items.
		private static var managerTable : Object = new Object(); // registration  
list of IManager instances
		private static var managers : Array = new Array(); // ordered  
registration list of IManager instances
		private static var liveManagers : uint = 0;
		private static var timers : Dictionary = new Dictionary(false); // key:  
pulseInterval, value: Timer for that pulse
		private static var items : Dictionary = new Dictionary(false); // key:  
IUpdatable item, value: pulseInterval at add.
		private static var itemCounts : Dictionary = new Dictionary(false); //  
key: pulseInterval, value: item count for that pulse
		private static var pulseSprite : Sprite; // used for ENTER_FRAME pulse
		private static var paused : Boolean = false;
		
		// These additional lists enables caching of items that are added during  
the update cycle for the same pulse.
		// This prevents groups & sequences from going out of sync by ensuring  
that each cycle completes before new items are added.
		private static var lockedPulses : Dictionary = new Dictionary(false); //  
key: pulseInterval, value: true
		private static var delayedPulses : Dictionary = new Dictionary(false); //  
key: pulseInterval, value: true
		private static var addQueue : Dictionary = new Dictionary(false); // key:  
IUpdatable item, value: true
		
		// -== Public Class Methods ==-
		
		/**
		 * @param className		A string naming the manager class, such  
as "OverlapMonitor".
		 * @return				The manager instance, if registered.
		 * @see #addManager()
		 * @see #removeManager()
		 */
		public static function getManager(className:String) : IManager
		{
			return managerTable[ className ];
		}
		
		/**
		 * Enables the extending of this class' functionality with a tight
		 * coupling to an IManager.
		 *
		 * <p>Tight coupling is crucial in such a time-sensitive context;
		 * standard events are too asynchronous. All items that implement
		 * IManageable are reported to registered managers as they add and
		 * remove themselves from GoEngine.</p>
		 *
		 * <p>Managers normally act as singletons within the Go system (which
		 * you are welcome to modify). This method throws a DuplicateManagerError
		 * if an instance of the same manager class is already registered. Use a
		 * try/catch block when calling this method if your program might  
duplicate
		 * managers, or use getManager() to check for prior registration.</p>
		 *
		 * @param instance	An instance of a manager you wish to add.
		 * @see #getManager()
		 * @see #removeManager()
		 */
		public static function addManager( instance:IManager ):void
		{
			var className:String = getQualifiedClassName(instance);
			className = className.slice(className.lastIndexOf("::")+2);
			if (managerTable[ className ]) {
				throw new DuplicateManagerError( className );
				return;
			}
			managerTable[ className ] = instance;
			managers.push(instance);
			if (instance is ILiveManager) liveManagers++;
		}
		
		/**
		 * Unregisters any manager set in <code>addManager</code>.
		 *
		 * @param className		A string naming the manager class, such  
as "OverlapMonitor".
		 * @see #getManager()
		 * @see #addManager()
		 */
		public static function removeManager( className:String ):void
		{
			managers.splice(managers.indexOf(managerTable[ className ]), 1);
			if (managerTable[ className ] is ILiveManager)
				liveManagers--;
			delete managerTable[ className ]; // leave last
		}
		
		/**
		 * Test whether an item is currently stored and being updated by the  
engine.
		 *
		 * @param item		Any object implementing IUpdatable
		 * @return			Whether the IUpdatable is in the engine
		 */
		public static function hasItem( item:IUpdatable ):Boolean
		{
			return (items[ item ]!=null);
		}
		
		/**
		 * Adds an IUpdatable instance to an update-queue corresponding to
		 * the item's pulseInterval property.
		 *
		 * @param item		Any object implementing IUpdatable that wishes
		 * 					to receive update calls on a pulse.
		 * 					
		 * @return			Returns false only if this item was already in the
		 * 					engine under the same pulse. (If an existing item is added
		 * 					but the pulseInterval has changed it will be removed,
		 * 					re-added, and true will be returned.)
		 * 					
		 * @see #removeItem()
		 */
		public static function addItem( item:IUpdatable ):Boolean
		{
			// Group items by pulse for efficient update cycles.
			var interval:int = item.pulseInterval;
			if (items[ item ]) {
				if (items[ item ] == item.pulseInterval)
					return false;
				else
					removeItem(item);
			}
			if (lockedPulses[ interval ]==true) { // this prevents items from being  
added during an update loop in progress.
				delayedPulses[ interval ] = true; // flags update to clear the queue  
when the in-progress loop completes.
				addQueue[ item ] = true; // for tightest syncing of item groups, read  
the documentation under GoItem.update().
			}
			items[ item ] = interval; // Tether item to original pulseint. Used in  
removeItem & setPaused(false).
			if (!timers[ interval ]) {
				addPulse( interval );
				itemCounts[ interval ] = 1;
			}
			else {
				itemCounts[ interval ] ++;
			}
			// Report IManageable instances to registered managers
			if (item is IManageable) {
				for each (var manager:IManager in managers)
					manager.reserve( item as IManageable );
			}
			return true;
		}
		
		/**
		 * Removes an item from the queue and removes its pulse timer if
		 * the queue is depleted.
		 *
		 * @param item		Any IUpdatable previously added that wishes
		 * 					to stop receiving update calls.
		 * 					
		 * @return			Returns false if the item was not in the engine.
		 *
		 * @see #addItem()
		 */
		public static function removeItem( item:IUpdatable ):Boolean
		{
			if (items[ item ]==null)
				return false;
			var interval: int = items[ item ];
			if ( -- itemCounts[ interval ] == 0 ) {
				removePulse( interval );
				delete itemCounts[ interval ];
			}
			delete items[ item ];
			delete addQueue[ item ]; // * see note following update
			// Report IManageable item removal to registered managers.
			if (item is IManageable) {
				for each (var manager:IManager in managers)
					manager.release( item as IManageable );
			}
			return true;
		}
		
		/**
		 * Removes all items and resets the engine,
		 * or removes just items running on a specific pulse.
		 *
		 * @param pulseInterval		Optionally filter by a specific pulse
		 * 							such as ENTER_FRAME or a number of milliseconds.
		 * @return					The number of items successfully removed.
		 * @see #removeItem()
		 */
		public static function clear(pulseInterval:Number = NaN) : uint
		{
			var all:Boolean = (isNaN(pulseInterval));
			var n:Number = 0;
			for (var item:Object in items) {
				if (all || items[ item ]==pulseInterval)
					if (removeItem(item as IUpdatable)==true)
						n++;
			}
			return n;
		}
		
		/**
		 * Retrieves number of active items in the engine
		 * or active items running on a specific pulse.
		 *
		 * @param pulseInterval		Optionally filter by a specific pulseInterval
		 *							such as ENTER_FRAME or a number of milliseconds.
		 *
		 * @return					Number of active items in the Engine.
		 */
		public static function getCount(pulseInterval:Number = NaN) : uint
		{
			if (!isNaN(pulseInterval))
				return (itemCounts[pulseInterval]);
			var n:Number = 0;
			for each (var count: int in itemCounts)
				n += count;
			return n;
		}
		
		/**
		 * @return			The paused state of engine.
		 * @see #setPaused()
		 */
		public static function getPaused() : Boolean {
			return paused;
		}
		
		/**
		 * Pauses or resumes all animation globally by suspending processing,
		 * and calls pause() or resume() on each item with those methods.
		 *
		 * <p>The return value only reflects how many items had pause() or  
resume()
		 * called on them, but the GoEngine.getPaused() state will change if any
		 * pulses are suspended or resumed.</p>
		 *
		 * @param pause				Pass false to resume if currently paused.
		 * @param pulseInterval		Optionally filter by a specific pulse
		 * 							such as ENTER_FRAME or a number of milliseconds.
		 * @return					The number of items on which a pause() or resume()
		 * 							method was called (0 doesn't necessarily reflect
		 * 							whether the GoEngine.getPaused() state changed, it
		 * 							may simply indicate that no items had that method).
		 * @see #resume()
		 */
		public static function setPaused(pause:Boolean=true, pulseInterval:Number  
= NaN) : uint
		{
			if (paused==pause) return 0;
			var n:Number = 0;
			var pulseChanged:Boolean = false;
			var all:Boolean = (isNaN(pulseInterval));
			var method:String = (pause ? "pause" : "resume");
			for (var item:Object in items) {
				var pulse:int = (items[item] as int);
				if (all || pulse==pulseInterval) {
					pulseChanged = (pulseChanged || (pause ? removePulse(pulse) :  
addPulse(pulse)));
					// call pause or resume on the item if it has such a method.
					if (item.hasOwnProperty(method)) {
						if (item[method] is Function) {
							item[method].apply(item);
							n++;
						}
					}
				}
			}
			if (pulseChanged)
				paused = pause;
			return n;
		}
		
		// -== Private Class Methods ==-
		
		/**
		 * Executes the update queue corresponding to the dispatcher's interval.
		 *
		 * @param event			TimerEvent or Sprite ENTER_FRAME Event
		 */
		private static function update(event:Event) : void
		{
			var currentTime:Number = getTimer();
			var pulse:int = (event is TimerEvent ? ( event.target as Timer ).delay :  
ENTER_FRAME);
			lockedPulses[ pulse ] = true;
			var doLiveUpdate:Boolean = (liveManagers > 0);
			var updated:Array;
			if (doLiveUpdate) updated = []; // syncs the live manager list to items  
actually updated
			for (var item:* in items) {
				if (items[ item ]==pulse && !addQueue[ item ]) {
					(item as IUpdatable).update(currentTime);
					if (doLiveUpdate) updated.push(item);
				}
			}
			lockedPulses[ pulse ] = false;
			if (delayedPulses[ pulse ]) {
				for (item in addQueue)
					delete addQueue[ item ];
				delete delayedPulses[ pulse ];
			}
// updateAfterEvent() should not be needed as long as items follow  
tight-syncing instructions in GoItem.update() documentation.
//			if (pulse!=ENTER_FRAME) (event as TimerEvent).updateAfterEvent();
			if (doLiveUpdate)
				for each (var manager:Object in managers)
					if (manager is ILiveManager)
						(manager as ILiveManager).onUpdate(pulse, updated, currentTime);  //  
* see note
		}
// * note: In one rare case that has not been reported yet but is  
theoretically possible, the 'updated' list
// passed could contain already-released items. This could only happen if  
the item is removed & released
// just after the main update cycle but before the the doLiveUpdate()  
routine runs. If you encounter this issue
// please report it to the GoASAP mailing list, it's too involved to bother  
with before it's a problem.

		/**
		 * Creates new timers when a previously unused interval is specified,
		 * and tracks the number of items associated with that interval.
		 *
		 * @param pulse			The pulseInterval requested
		 * @return				Whether a pulse was added
		 */
		private static function addPulse(pulse : int) : Boolean
		{
			if (pulse==ENTER_FRAME) {
				if (!pulseSprite) {
					timers[ENTER_FRAME] = pulseSprite = new Sprite();
					pulseSprite.addEventListener(Event.ENTER_FRAME, update);
				}
				return true;
			}
			var t:Timer = timers[ pulse ] as Timer;
			if (!t) {
				t = timers[ pulse ] = new Timer(pulse);
				(timers[ pulse ] as Timer).addEventListener(TimerEvent.TIMER, update);
				t.start();
				return true;
			}
			return false;
		}
		
		/**
		 * Tracks whether a removed item was the last one using a timer
		 * and if so, removes that timer.
		 *
		 * @param pulse			The pulseInterval corresponding to an item being  
removed.
		 * @return				Whether a pulse was removed
		 */
		private static function removePulse(pulse : int) : Boolean
		{
			if (pulse==ENTER_FRAME) {
				if (pulseSprite) {
					pulseSprite.removeEventListener(Event.ENTER_FRAME, update);
					delete timers[ ENTER_FRAME ];
					pulseSprite = null;
					return true;
				}
			}
			var t:Timer = timers[ pulse ] as Timer;
			if (t) {
				t.stop();
				t.removeEventListener(TimerEvent.TIMER, update);
				delete timers[ pulse ];
				return true;
			}
			return false;
		}
	}
}
\ No newline at end of file

Modified: branches/goasap0.5.2/src_go/org/goasap/items/LinearGo.as
==============================================================================
--- branches/goasap0.5.2/src_go/org/goasap/items/LinearGo.as	(original)
+++ branches/goasap0.5.2/src_go/org/goasap/items/LinearGo.as	Sun Mar 15  
17:46:33 2009
@@ -1 +1 @@
-/**
  * Copyright (c) 2007 Moses Gunesch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a  
copy
  * of this software and associated documentation files (the "Software"), to  
deal
  * in the Software without restriction, including without limitation the  
rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included  
in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  
OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  
THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  
FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
package org.goasap.items {
	import flash.utils.getTimer;
	
	import org.goasap.GoEngine;
	import org.goasap.PlayStates;
	import org.goasap.errors.EasingFormatError;
	import org.goasap.events.GoEvent;
	import org.goasap.interfaces.IPlayable;
	import org.goasap.managers.LinearGoRepeater;	

	/**
	 * Dispatched during an animation's first update after the delay
	 * has completed, if one was set. Any number of callbacks may also be
	 * associated with this event using <code>addCallback</code>.
	 * @eventType org.goasap.events.START
	 */
	[Event(name="START", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched on the animation's update pulse. Any number of callbacks
	 * may also be associated with this event using <code>addCallback</code>.
	 * @eventType org.goasap.events.UPDATE
	 */
	[Event(name="UPDATE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when pause() is called successfully.  Any number of callbacks
	 * may also be associated with this event using <code>addCallback</code>.
	 * @eventType org.goasap.events.PAUSE
	 */
	[Event(name="PAUSE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when resume() is called successfully. Any number of callbacks
	 * may also be associated with this event using <code>addCallback</code>.
	 * @eventType org.goasap.events.RESUME
	 */
	[Event(name="RESUME", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched at the end of each cycle if the tween has more than one.
	 * Any number of callbacks may also be associated with this event using
	 * <code>addCallback</code>.
	 * @eventType org.goasap.events.CYCLE
	 */
	[Event(name="CYCLE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched if an animation is manually stopped. Any number of callbacks
	 * may also be associated with this event using <code>addCallback</code>.
	 * @eventType org.goasap.events.STOP
	 */
	[Event(name="STOP", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched on an animation's final update, just after the last update  
event.
	 * Any number of callbacks may also be associated with this event using
	 * <code>addCallback</code>.
	 * @eventType org.goasap.events.COMPLETE
	 */
	[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]

	/**
	 * LinearGo extends the base class GoItem to define a playable A-to-B  
animation.
	 *
	 * <p><b>LinearGo: A very simple tween</b></p>
	 *
	 * <p>A LinearGo instance is a playable object that animates a single  
number. It dispatches events
	 * and callbacks associated with the animation's start, update and  
completion. Instances can be used
	 * directly, or easily subclassed to build custom tweening APIs. LinearGo  
extends GoItem, which
	 * provides basic settings shared by physics and tween items. These  
include a <code>state</code> property,
	 * <code>pulseInterval</code>, and the two common animation options  
<code>useRounding</code> and
	 * <code>useRelative</code>.</p>
	 *
	 * <p>The tween can be customized using the instance properties  
<code>duration</code>, <code>easing</code>
	 * and <code>delay</code>. The number crunched by a LinearGo is readable  
in its <code>position</code>
	 * property. This number always starts at 0 and completes at 1, regardless  
of the tween's duration
	 * or easing (those parameters are factored in to produce accurate  
fractional in-between values).
	 * As the tween runs, you can use <code>position</code> as a multiplier to  
animate virtually anything:
	 * motion, alpha, a sound level, the values in a ColorTransform,  
BitmapFilter, a 3D scene, and so on.
	 * Note that at times position may be less than 0 or greater than 1  
depending on the easing function.</p>
	 *
	 * <p>The START event occurs just before the first update (after the  
delay). UPDATE is fired once on
	 * <i>every</i> update pulse, and COMPLETE just after the final update.  
The STOP event is fired by LinearGo
	 * only if a tween is stopped before it completes. Additional events are  
fired on PAUSE, RESUME and at
	 * the end of each CYCLE if the tween plays more than one cycle. Besides  
standard events, you can store
	 * callback functions (method-closures) using <code>addCallback</code>.  
Any number of callbacks can be
	 * associated with each GoEvent type. This alternative to the standard  
event model was included in
	 * LinearGo since it's a common feature of many modern tweening APIs, and  
very slightly more efficient
	 * than standard events.</p>
	 *
	 * <p>LinearGo can play multiple back-and-forth tween cycles or repeat  
forward-play any number of times.
	 * This functionality is handled by the LinearGo's <code>repeater</code>  
instance, which has settings for
	 * alternate easing on reverse-cycles, infinite cycling, plus  
<code>currentCycle</code> and <code>done</code>
	 * state properties.</p>
	 *
	 * <p><b>Subclassing to create custom tweens</b></p>
	 *
	 * <p><i>Important: Store your custom tween classes in a package bearing  
your own classpath, not in the core
	 * package! This will help avoid confusion with other authors'  
work.</i></p>
	 *
	 * <p>It's possible to build virtually any tweening API over LinearGo  
because all of the specifics are left
	 * up to you: target objects, tweenable properties, tween values — and  
importantly, the datatypes of all of these.</p>
	 *
	 * <p>A basic subclass can be created in three steps: Gathering target &  
property information, subclassing the
	 * <code>start</code> method to set up the tween, and finally subclassing  
the <code>onUpdate</code> method
	 * to affect the tween. The first step, gathering tween target and  
property information, can be done by writing
	 * getter/setter properties, customizing the constructor, or both.  
Consider various options such as allowing for
	 * single vs. multiple target objects, open vs. specific tween properties,  
and so on. The next step, subclassing
	 * <code>start</code>, involves figuring the tween's amount of change and  
implementing a standard Go convention,
	 * <code>useRelative</code>. This option should enable the user to declare  
tween values as relative to existing
	 * values instead of as fixed absolutes. In the final step, you subclass  
<code>onUpdate</code> to apply the tween,
	 * using the <code>_position</code> calculated by this base class:</p>
	 *
	 * <pre>target[ propName ] = super.correctValue(start + change *  
_position);</pre>
	 *
	 * <p>The helper method <code>correctValue</code> is provided in the  
superclass GoItem, to clean up NaN values
	 * and apply rounding when <code>useRounding</code> is activated. That's  
it — events and callbacks are
	 * dispatched by LinearGo, so subclasses can remain simple.</p>
	 *
	 * <p>An optional fourth step will make your custom tween compatible with  
Go managers. To do this, implement
	 * the IManageable interface. (OverlapMonitor prevents different tween  
instances from handling the same
	 * property at once; you can build other managers as well.)</p>
	 *
	 * {In the game of Go a black or white stone is called a go-ishi.}
	 *
	 * @author Moses Gunesch
	 */
	public class LinearGo extends GoItem implements IPlayable
	{
		// -== Settable Class Defaults ==-
		
		/**
		 * Class default for the instance property delay.
		 * @default 0
		 * @see #delay
		 */
		public static var defaultDelay : Number = 0;
		
		/**
		 * Class default for the instance property duration.
		 * @default 1
		 * @see #duration
		 */
		public static var defaultDuration : Number = 1;
		
		/**
		 * Class default for the instance property easing.
		 * Note that this property is left null until the first LinearGo
		 * is instantiated, at which time it is set to Quintic.easeOut.
		 * @default fl.motion.easing.Quintic.easeOut
		 * @see #easing
		 */
		public static var defaultEasing:Function;
		
		/**
		 * Normal default easing, this is Quintic.easeOut.
		 * (The two default easings in this class are included because there's
		 * currently no single easing classpath shared between Flash & Flex.)
		 */
		public static function easeOut(t:Number, b:Number, c:Number, d:Number) :  
Number {
			return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
		};
		
		// -== Class Methods ==-
		
		/**
		 * An alternative default easing with no acceleration.
		 * (The two default easings in this class are included because there's
		 * currently no single easing classpath shared between Flash & Flex.)
		 */
		public static function easeNone(t:Number, b:Number, c:Number, d:Number) :  
Number {
			return c * t / d + b;
		};
		
		/**
		 * A quick one-time setup command that lets you turn on useFrames mode
		 * as a default for all new tweens and adjust some related settings.
		 * (Note that useFrames mode is normally only used for specialty  
situations.)
		 *
		 * @param defaultToFramesMode		Sets an internal default so all new  
LinearGo instances
		 * 									will be set to use framecounts for their delay and duration.
		 * 									Also sets GoItem.defaultPulseInterval to enterframe which is
		 * 									most normal for frame-based updates.
		 * @param useZeroBasedFrameIndex	Normally currentFrame reads 1 on first  
update, like the Flash
		 * 									timeline starts at Frame 1. Set this option to use a  
zero-based
		 * 									index on all tweens instead.
		 * @see #useFrames
		 * @see #currentFrame
		 */
		public static function setupUseFramesMode( defaultToFramesMode: Boolean =  
true,
								   				   useZeroBasedFrameIndex: Boolean=false):void {
			GoItem.defaultPulseInterval = GoEngine.ENTER_FRAME;
			_useFramesMode = defaultToFramesMode;
			if (useZeroBasedFrameIndex) { _framesBase = 0; }
		}

		// -== Pulic Properties ==-
		
		/**
		 * Number of seconds after start() call that the LinearGo begins  
processing.
		 * <p>If not set manually, the class default defaultDelay is adopted.</p>
		 * @see #defaultDelay
		 */
		public function get delay():Number {
			return _delay;
		}
		public function set delay(seconds:Number):void {
			if (_state==PlayStates.STOPPED && seconds >= 0) {
				_delay = seconds;
			}
		}
		
		/**
		 * Number of seconds the LinearGo takes to process.
		 * <p>If not set manually, the class default defaultDuration is  
adopted.</p>
		 * @see #defaultDuration
		 */
		public function get duration():Number {
			return _duration;
		}
		public function set duration(seconds:Number):void {
			if (_state==PlayStates.STOPPED && seconds >= 0) {
				_duration = seconds;
			}
		}
		
		/**
		 * Any standard easing-equation function such as the ones found in
		 * the Flash package fl.motion.easing or the flex package  
mx.effects.easing.
		 *
		 * <p>If not set manually, the class default defaultEasing is adopted. An  
error
		 * is thrown if the function does not follow the typical format. For  
easings
		 * that accept more than four parameters use  
<code>extraEasingParams</code>.
		 * </p>
		 *
		 * @see #defaultEasing
		 * @see #extraEasingParams
		 */
		public function get easing():Function {
			return _easing;
		}
		public function set easing(type:Function):void {
			if (_state==PlayStates.STOPPED) {
				try {
					if (type(1,1,1,1) is Number) {
						_easing = type;
						return;
					}
				} catch (e:Error) {}
				throw new EasingFormatError();
			}
		}
		
		/**
		 * Additional parameters to pass to easing functions that accept more  
than four.
		 * @see #easing
		 */
		public function get extraEasingParams() : Array {
			return _extraEaseParams;
		}
		public function set extraEasingParams(params:Array):void {
			if (_state==PlayStates.STOPPED && params is Array && params.length>0) {
				_extraEaseParams = params;
			}
		}
		
		/**
		 * A LinearGoRepeater instance that defines options for repeated
		 * or back-and-forth cycling animation.
		 *
		 * <p>You may pass a LinearGoRepeater instance to the constructor's
		 * repeater parameter to set all options at instantiation. The
		 * repeater's cycles property can be set to an integer, or
		 * to Repeater.INFINITE or 0 to repeat indefinitely, and checked using
		 * <code>linearGo.repeater.currentCycle</code>. LinearGoRepeater's
		 * <code>reverseOnCycle</code> flag is true by default, which
		 * causes animation to cycle back and forth. In that mode you can
		 * also specify a separate easing function (plus extraEasingParams)
		 * to use for the reverse animation cycle. For example, an easeOut
		 * easing with an easeIn easingOnCycle will produce a more
		 * natural-looking result. If <code>reverseOnCycle</code> is disabled,
		 * the animation will repeat its play forward each time.</p>
		 *
		 * <p>(The repeater property replaces the cycles, easeOnCycle and
		 * currentCycle parameters in earlier releases of LinearGo).</p>
		 *
		 * @see org.goasap.managers.LinearGoRepeater LinearGoRepeater
		 */
		public function get repeater(): LinearGoRepeater {
			return _repeater;
		}
		
		/**
		 * When useFrames mode is activated, duration and delay are treated
		 * as update-counts instead of time values.
		 *
		 * <p>(This mode is normally only used for specialty situations.)</p>
		 *
		 * <p>Using this feature with a pulseInterval of GoEngine.ENTER_FRAME
		 * will result in a frame-based update that mimics the behavior of the
		 * flash timeline. As with the timeline, frame-based tween durations can
		 * vary based on the host computer's processor load and other factors.</p>
		 *
		 * <p>The <code>setupUseFramesMode()</code> class method is a much easier
		 * way to use frames in your project, instead of setting this property
		 * on every tween individually.</p>
		 *
		 * @see #setupUseFramesMode()
		 */
		public function set useFrames(value:Boolean):void {
			if (_state==PlayStates.STOPPED)
				_useFrames = value;
		}
		public function get useFrames():Boolean {
			return _useFrames;
		}
		
		/**
		 * A number between 0 and 1 representing the current tween value.
		 *
		 * <p>Use this number as a multiplier to apply values to targets
		 * across time.<p>
		 *
		 * <p>Here's an example of what an overridden update method might  
contain:</p>
		 * <pre>
		 * super.update(currentTime);
		 * target[ propName ] = super.correctValue(startValue + change*_position);
		 * </pre>
		 * @see #timePosition
		 */
		public function get position():Number {
			return _position;
		}
		
		/**
		 * For time-based tweens, returns a time value which is negative during  
delay
		 * then spans the tween duration in positive values, ignoring repeat  
cycles.
		 *
		 * <p>In useFrames mode, this getter differs from  
<code>currentFrame</code>
		 * significantly. Instead of constantly increasing through all cycles as  
if
		 * tweens were back-to-back in a timeline layer, this method acts more  
like
		 * a single tween placed at frame 1, with a timeline playhead that scans  
back
		 * and forth or loops during cycles. So for a 10-frame tween with a  
5-frame
		 * delay and 2 repeater cycles with reverseOnCycle set to true, this  
method
		 * will return values starting at -5, start the animation at 1, play to 10
		 * then step backward to 1 again.</p>
		 *
		 * @see #position
		 * @see #currentFrame
		 * @see #duration
		 * @see #delay
		 * @see #setupUseFramesMode()
		 */
		public function get timePosition():Number {
			if (_state==PlayStates.STOPPED)
				return 0;
			var mult:Number = Math.max(0, timeMultiplier);
			if (_useFrames) {
				if (_currentFrame>_framesBase) {
					var cf:uint = _currentFrame-_framesBase;
					if (_repeater.direction==-1) {
						return ((_duration-1) - cf%_duration) + _framesBase;
					}
					return cf%_duration + _framesBase;
				}
				return _currentFrame;
			}
			return ((getTimer()-_startTime) / 1000 / mult);
		}

		/**
		 * Returns the number of updates that have occured since start.
		 *
		 * <p>This update-count property does not necessarily correspond
		 * to the actual player framerate, just the instance's pulseInterval.</p>
		 *
		 * <p>This property is set up to mirror the flash timeline. Imagine a  
timeline
		 * layer with a delay being a set of blank frames followed by the tween,
		 * followed by subsequent cycles as additional tweens: this is the way
		 * the <code>currentFrame</code> property works. Its first value is 1 by
		 * default, which can be changed to 0 in  
<code>setupUseFramesMode()</code>.
		 * This differs significantly from <code>timePosition</code>, which places
		 * the start of a single instance of the tween at frame 1 and steps its
		 * values from negative during delay then cycling through the single  
tween.</p>
		 *
		 *
		 * @see #useFrames
		 * @see #setupUseFramesMode()
		 * @see #timePosition
		 */
		public function get currentFrame():uint {
			return _currentFrame;
		}
		
		// -== Protected Properties ==-
		
		/** @private */
		protected static var _useFramesMode : Boolean = false;
		
		/** @private */
		protected static var _framesBase : Number = 1;
		
		/** @private */
		protected var _delay 			: Number;
		
		/** @private */
		protected var _duration 		: Number;
		
		/** @private */
		protected var _tweenDuration	: Number;
		
		/** @private */
		protected var _easing 			: Function;
		
		/** @private */
		protected var _easeParams		: Array;
		
		/** @private */
		protected var _extraEaseParams	: Array;
		
		/** @private */
		protected var _repeater			: LinearGoRepeater;
		
		/** @private */
		protected var _currentEasing	: Function;
		
		/** @private */
		protected var _useFrames		: Boolean;
		
		/** @private */
		protected var _started			: Boolean = false;
		
		/** @private */
		protected var _currentFrame		: int;
		
		/** @private */
		protected var _position			: Number;
		
		/** @private */
		protected var _change			: Number;
		
		/** @private */
		protected var _startTime		: Number;
		
		/** @private */
		protected var _endTime 			: Number;
		
		/** @private */
		protected var _pauseTime 		: Number;
		
		/** @private */
		protected var _callbacks		: Object = new Object(); // In tests, creating  
this object up front is more efficient.
		
		// -== Public Methods ==-
		
		/**
		 * The inputs here are not a convention, subclasses should design
		 * their own constructors appropriate to usage. They are provided
		 * here primarily as a convenience for subclasses. However, do not
		 * omit calling super() from subclass constructors: LinearGo's
		 * constructor sets and validates class defaults and sets up the
		 * repeater instance.
		 */
		public function LinearGo(	delay	 			: Number=NaN,
									duration 			: Number=NaN,
									easing 				: Function=null,
									extraEasingParams	: Array=null,
									repeater			: LinearGoRepeater=null,
									useRelative			: Boolean=false,
									useRounding			: Boolean=false,
									useFrames			: Boolean=false,
									pulseInterval		: Number=NaN ) {
			// validate & set class defaults first
			if (isNaN(defaultDelay))
				defaultDelay = 0;
			if (isNaN(defaultDuration))
				defaultDuration = 1;
			try { this.easing = defaultEasing; }
			catch (e1:EasingFormatError) { defaultEasing = easeOut; }
			// set params
			if (!isNaN(delay)) _delay = delay;
			else _delay = defaultDelay;
			if (!isNaN(duration)) _duration = duration;
			else _duration = defaultDuration;
			try { this.easing = easing; }
			catch (e2:EasingFormatError) {
				if (easing!=null) { throw e2; } // user passed invalid easing function
				this.easing = defaultEasing;
			}
			if (extraEasingParams) _extraEaseParams = extraEasingParams;
			if (useRelative) this.useRelative = true;
			if (useRounding) this.useRounding = true;
			_useFrames = (useFrames || _useFramesMode);
			if (!isNaN(pulseInterval)) _pulse = pulseInterval;
			if (repeater!=null) _repeater = repeater; // repeater setup makes  
super() call important for all subclasses.
			else _repeater = new LinearGoRepeater();
			_repeater.setParent(this);
		}
		
		/**
		 * Starts play for this LinearGo instance using GoEngine.
		 *
		 * <p>CONVENTION ALERT: If <code>useRelative</code> is true, calculate  
tween values
		 * relative to the target object's existing value as in the example  
below.</p>
		 *
		 * <p>Most typically you should also store the tween's start and change  
values
		 * for later use in <code>onUpdate</code>.</p>
		 *
		 * <pre>
		 * protected var _target : DisplayObject;
		 * protected var _width : Number;
		 * protected var _changeWidth : Number;
		 *
		 * public function start():Boolean
		 * {
		 *     if (!_target || !_width || isNaN(_width))
		 *         return false;
		 *
		 *     _startWidth = _target.width;
		 *
		 *     if (useRelative) {
		 *         _changeWidth = _width;
		 *     } else {
		 *         _changeWidth = (_width - _startWidth);
		 *     }
		 *
		 *     return (super.start());
		 * }
		 * </pre>
		 *
		 * @return Successful addition of the item to GoEngine
		 *
		 * @see GoItem#useRelative
		 * @see #onUpdate()
		 */
		public function start() : Boolean {
			stop(); // does nothing if already stopped.
			if (GoEngine.addItem(this)==false)
				return false;
			reset();
			_state = (_delay > 0 ? PlayStates.PLAYING_DELAY : PlayStates.PLAYING);  
// has to be set here since delay is not included in PlayableBase.
			// note: start event is dispatched on the first update cycle for tighter  
cross-item syncing.
			return true;
		}
		
		/**
		 * Ends play for this LinearGo instance and dispatches a GoEvent.STOP
		 * event if the tween is incomplete. This method does not typically
		 * require subclassing.
		 *
		 * @return Successful removal of the item from GoEngine
		 */
		public function stop() : Boolean {
			if (_state==PlayStates.STOPPED || GoEngine.removeItem(this)==false)
				return false;
			_state = PlayStates.STOPPED;
			var completed:Boolean = (_easeParams!=null &&  
_position==_easeParams[1]+_change);
			reset();
			if (!completed) // otherwise a COMPLETE event was dispatched.
				dispatch( GoEvent.STOP );
			return true;
		}

		/**
		 * Pauses play (including delay) for this LinearGo instance.
		 * This method does not typically require subclassing.
		 *
		 * @return Success
		 * @see #resume()
		 * @see org.goasap.GoEngine#setPaused GoEngine.setPaused()
		 */
		public function pause() : Boolean {
			if (_state==PlayStates.STOPPED || _state==PlayStates.PAUSED)
				return false;
			_state = PlayStates.PAUSED;
			_pauseTime = (_useFrames ? _currentFrame : getTimer()); // This causes  
update() to skip processing.
			dispatch(GoEvent.PAUSE);
			return true;
		}
		
		/**
		 * Resumes previously paused play, including delay.
		 * This method does not typically require subclassing.
		 *
		 * @return Success
		 * @see #pause()
		 * @see org.goasap.GoEngine#setPaused GoEngine.setPaused()
		 */
		public function resume() : Boolean {
			if (_state != PlayStates.PAUSED)
				return false;
			var currentTime:Number = (_useFrames ? _currentFrame : getTimer());
			setup(currentTime - (_pauseTime - _startTime));
			_pauseTime = NaN;
			_state = (_startTime > currentTime ? PlayStates.PLAYING_DELAY :  
PlayStates.PLAYING);
			dispatch(GoEvent.RESUME);
			return true;
		}
		
		/**
		 * Skips to a point in the tween's duration and plays, from any state.
		 * This method does not typically require subclassing.
		 *
		 * <p>If GoItem.timeMultiplier is set to a custom value, you should still  
pass a
		 * seconds value based on the tween's real duration setting.</p>
		 *
		 * @param time		Seconds or frames to jump to across all cycles, where 0  
(or 1 in useFramesMode)
		 * 					represents tween start, numbers greater than duration represent  
higher repeat cycles,
		 * 					and negative numbers represent a new delay to play before tween  
start.
		 * @return Success
		 * @see #timePosition
		 */
		public function skipTo(time : Number) : Boolean
		{
			if (_state==PlayStates.STOPPED) {
				if (start()==false)
					return false;
			}
			if (isNaN(time)) { time = 0; }
			var mult:Number = Math.max(0, timeMultiplier) * (_useFrames ? 1 : 1000);
			var startTime:Number;
			var currentTime:Number;
			if (time < _framesBase) { // Negative value: rewind and add a new delay.
				_repeater.reset();
				if (_position>0) { skipTo(_framesBase); } // skips to start so new  
pause occurs in starting position
			}
			else {
				time = _repeater.skipTo(_duration, time-_framesBase); // sets cycles  
and returns new position
			}
			if (_useFrames) {
				startTime = _framesBase;
				currentTime = _currentFrame = Math.round(time*mult);
			}
			else {
				currentTime = getTimer();
				startTime = (currentTime - (time * mult)); // skipTo operation is  
performed by altering the tween's start & end times.
			}
			setup(startTime);
			_state = (_startTime > currentTime ? PlayStates.PLAYING_DELAY :  
PlayStates.PLAYING);
			update(currentTime); // sets _position
			return true;
		}
		
		/**
		 * An alternative to subscribing to events is to store callbacks. You can
		 * associate any number of callbacks with the primary GoEvent types START,
		 * UPDATE, COMPLETE, and STOP (only fired if the tween is stopped before  
it
		 * completes).
		 *
		 * <p>
		 * Note that there is little difference between using callbacks and  
events.
		 * Both are common techniques used in many various modern tweening APIs.  
Callbacks
		 * are slightly faster, but this won't normally be noticeable unless  
thousands of
		 * tweens are being run at once.
		 * </p>
		 *
		 * @param closure	A reference to a callback function
		 * @param type		Any GoEvent type constant, the default is COMPLETE.
		 * @see #removeCallback
		 * @see org.goasap.events.GoEvent GoEvent
		 */
		public function addCallback(closure : Function, type :  
String=GoEvent.COMPLETE):void {
			if (!_callbacks[ type ])
				_callbacks[ type ] = new Array();
			var a:Array = (_callbacks[ type ] as Array);
			if (a.indexOf(closure)==-1)
				a.push(closure);
		}
		
		/**
		 * Removes a method closure previously stored using addCallback.
		 *
		 * @param closure	A reference to a function
		 * @param type		A GoEvent constant, default is COMPLETE.
		 * @see #addCallback
		 * @see org.goasap.events.GoEvent GoEvent
		 */
		public function removeCallback(closure : Function, type :  
String=GoEvent.COMPLETE):void {
			var a:Array = (_callbacks[ type ] as Array);
			if (a)
				while (a.indexOf(closure)>-1)
					a.splice(a.indexOf(closure), 1);
		}
		
		/**
		 * Performs tween calculations on GoEngine pulse.
		 *
		 * <p>Subclass <code>onUpdate</code> instead of this method.
		 *
		 * @param currentTime	Clock time for the current block of updates.
		 * @see #onUpdate()
		 */
		override public function update(currentTime:Number) : void
		{
			if (_state==PlayStates.PAUSED)
				return;
			
			_currentFrame ++;
			if (_useFrames)
				currentTime = _currentFrame;
			
			if (isNaN(_startTime))		// setup() must be called once prior to tween's  
1st update.
				setup(currentTime);		// This is done here, not in start, for tighter  
syncing of items.
			
			if (_startTime > currentTime)
				return; // still PlayStates.PLAYING_DELAY
			
			// (1.) Set _position and determine primary update type.
			var type:String = GoEvent.UPDATE;
			if (currentTime < _endTime) { // start, update...
				if (!_started)
					type = GoEvent.START;
				var time:Number = _easeParams[0] = (currentTime - _startTime);
				_position = _currentEasing.apply(null, _easeParams); // update position  
using easing function.
				if (_position==2.220446049250313e-16) { _position = 0; }// Corrects for  
a computer rounding error in Back.easeOut() at position 0.
			}
			else { // complete, cycle...
				_position = _easeParams[1] + _change; // set absolute 1 or 0 position  
at end of cycle
				type = (_repeater.hasNext() ? GoEvent.CYCLE : GoEvent.COMPLETE);
			}
			
			// (2.) Run onUpdate() passing the primary update type, then
			// (3.) dispatch up to three events in correct order.
			onUpdate(type);
			if (!_started) {
				_state = PlayStates.PLAYING;
				_started = true;
				dispatch(GoEvent.START);
			}
			dispatch(GoEvent.UPDATE);
			if (type==GoEvent.COMPLETE) {
				stop();
				dispatch(GoEvent.COMPLETE);
			}
			else if (type==GoEvent.CYCLE) {
				_repeater.next();
				dispatch(GoEvent.CYCLE);
				_startTime = NaN; // causes setup() to be called again on next update  
to prep next cycle.
			}
		}
		
		// -== Protected Methods ==-
		
		/**
		 * Subclass this method (instead of the update method) for simplicity.
		 *
		 * <p>Use this method to manipulate targets based on the current _position
		 * setting, which is a 0-1 multiplier precalculated to the tween's  
position
		 * based on its easing style and the current time in the tween.</p>
		 *
		 * <p>CONVENTION ALERT: To implement the Go convention  
<code>useRounding</code>,
		 * always call GoItem's <code>correctValue()</code> method on each  
calculated
		 * tween value before you apply it to a target. This corrects NaN to 0 and
		 * rounds the value if <code>useRounding</code> is true.</p>
		 *
		 * Example:
		 * <pre>
		 * override protected function onUpdate(type:String):void
		 * {
		 *     target[ propName ] = super.correctValue(startValue +  
change*_position);
		 * }
		 * </pre>
		 *
		 * @param type	A constant from the class GoEvent: START, UPDATE, CYCLE,  
or COMPLETE.
		 * @see GoItem#correctValue()
		 * @see GoItem#useRounding
		 * @see #update()
		 */
		protected function onUpdate(type : String) : void
		{
			// Subclass this method and start to implement your tween class.
		}
		
		/**
		 * @private
		 * Internal setup routine used by start() and other methods.
		 *
		 * @param time			Tween start time based on getTimer
		 */
		protected function setup(startTime : Number) : void
		{
			_startTime = startTime;
			var mult:Number = Math.max(0, timeMultiplier) * (_useFrames ? 1 : 1000);
			_tweenDuration = (_useFrames ? Math.round(_duration * mult)-1 :  
(_duration * mult));
			_endTime = _startTime + _tweenDuration;
			if (!_started) {
				var d:Number = (_useFrames ? Math.round(_delay * mult) : (_delay *  
mult));
				_startTime += d;
				_endTime += d;
			}
			// Set up a tween cycle: _currentEasing, _change, _position, and  
_easeParams.
			// Be sure _repeater is updated before this call so the next cycle gets  
set up.
			var useCycleEase:Boolean = _repeater.currentCycleHasEasing;
			_currentEasing = (useCycleEase ? _repeater.easingOnCycle : _easing);
			var extras:Array = (useCycleEase ? _repeater.extraEasingParams :  
_extraEaseParams);
			_change = _repeater.direction;
			_position = (_repeater.direction==-1 ? 1 : 0);
			_easeParams = new Array(0, _position, _change, _tweenDuration); //  
stored to reduce runtime object-creation
			if (extras) _easeParams = _easeParams.concat(extras);
		}
		
		/**
		 * @private
		 * Internal, dispatches events and executes callbacks of any pre-verified  
type.
		 *
		 * @param type	Verified in addCallback, not in this method.
		 * @see #org.goasap.events.GoEvent GoEvent
		 */
		protected function dispatch(type:String):void
		{
			var a:Array = (_callbacks[ type ] as Array);
			if (a)
				for each (var callback:Function in a)
					callback();
			if (hasEventListener(type))
				dispatchEvent(new GoEvent( type ));
		}
		
		/**
		 * @private
		 */
		protected function reset() : void {
			_position = 0;
			_change = 1;
			_repeater.reset();
			_currentFrame = _framesBase-1;
			_currentEasing = _easing;
			_easeParams = null;
			_started = false;
			_pauseTime = NaN;
			_startTime = NaN;
		}
	}
}
\ No newline at end of file
+/**
  * Copyright (c) 2007 Moses Gunesch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a  
copy
  * of this software and associated documentation files (the "Software"), to  
deal
  * in the Software without restriction, including without limitation the  
rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included  
in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  
OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  
THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  
FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
package org.goasap.items {
	import flash.utils.getTimer;
	
	import org.goasap.GoEngine;
	import org.goasap.PlayStates;
	import org.goasap.errors.EasingFormatError;
	import org.goasap.events.GoEvent;
	import org.goasap.interfaces.IPlayable;
	import org.goasap.managers.LinearGoRepeater;	

	/**
	 * Dispatched during an animation's first update after the delay
	 * has completed, if one was set. Any number of callbacks may also be
	 * associated with this event using <code>addCallback</code>.
	 * @eventType org.goasap.events.START
	 */
	[Event(name="START", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched on the animation's update pulse. Any number of callbacks
	 * may also be associated with this event using <code>addCallback</code>.
	 * @eventType org.goasap.events.UPDATE
	 */
	[Event(name="UPDATE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when pause() is called successfully.  Any number of callbacks
	 * may also be associated with this event using <code>addCallback</code>.
	 * @eventType org.goasap.events.PAUSE
	 */
	[Event(name="PAUSE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when resume() is called successfully. Any number of callbacks
	 * may also be associated with this event using <code>addCallback</code>.
	 * @eventType org.goasap.events.RESUME
	 */
	[Event(name="RESUME", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched at the end of each cycle if the tween has more than one.
	 * Any number of callbacks may also be associated with this event using
	 * <code>addCallback</code>.
	 * @eventType org.goasap.events.CYCLE
	 */
	[Event(name="CYCLE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched if an animation is manually stopped. Any number of callbacks
	 * may also be associated with this event using <code>addCallback</code>.
	 * @eventType org.goasap.events.STOP
	 */
	[Event(name="STOP", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched on an animation's final update, just after the last update  
event.
	 * Any number of callbacks may also be associated with this event using
	 * <code>addCallback</code>.
	 * @eventType org.goasap.events.COMPLETE
	 */
	[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]

	/**
	 * LinearGo extends the base class GoItem to define a playable A-to-B  
animation.
	 *
	 * <p><b>LinearGo: A very simple tween</b></p>
	 *
	 * <p>A LinearGo instance is a playable object that animates a single  
number. It dispatches events
	 * and callbacks associated with the animation's start, update and  
completion. Instances can be used
	 * directly, or easily subclassed to build custom tweening APIs. LinearGo  
extends GoItem, which
	 * provides basic settings shared by physics and tween items. These  
include a <code>state</code> property,
	 * <code>pulseInterval</code>, and the two common animation options  
<code>useRounding</code> and
	 * <code>useRelative</code>.</p>
	 *
	 * <p>The tween can be customized using the instance properties  
<code>duration</code>, <code>easing</code>
	 * and <code>delay</code>. The number crunched by a LinearGo is readable  
in its <code>position</code>
	 * property. This number always starts at 0 and completes at 1, regardless  
of the tween's duration
	 * or easing (those parameters are factored in to produce accurate  
fractional in-between values).
	 * As the tween runs, you can use <code>position</code> as a multiplier to  
animate virtually anything:
	 * motion, alpha, a sound level, the values in a ColorTransform,  
BitmapFilter, a 3D scene, and so on.
	 * Note that at times position may be less than 0 or greater than 1  
depending on the easing function.</p>
	 *
	 * <p>The START event occurs just before the first update (after the  
delay). UPDATE is fired once on
	 * <i>every</i> update pulse, and COMPLETE just after the final update.  
The STOP event is fired by LinearGo
	 * only if a tween is stopped before it completes. Additional events are  
fired on PAUSE, RESUME and at
	 * the end of each CYCLE if the tween plays more than one cycle. Besides  
standard events, you can store
	 * callback functions (method-closures) using <code>addCallback</code>.  
Any number of callbacks can be
	 * associated with each GoEvent type. This alternative to the standard  
event model was included in
	 * LinearGo since it's a common feature of many modern tweening APIs, and  
very slightly more efficient
	 * than standard events.</p>
	 *
	 * <p>LinearGo can play multiple back-and-forth tween cycles or repeat  
forward-play any number of times.
	 * This functionality is handled by the LinearGo's <code>repeater</code>  
instance, which has settings for
	 * alternate easing on reverse-cycles, infinite cycling, plus  
<code>currentCycle</code> and <code>done</code>
	 * state properties.</p>
	 *
	 * <p><b>Subclassing to create custom tweens</b></p>
	 *
	 * <p><i>Important: Store your custom tween classes in a package bearing  
your own classpath, not in the core
	 * package! This will help avoid confusion with other authors'  
work.</i></p>
	 *
	 * <p>It's possible to build virtually any tweening API over LinearGo  
because all of the specifics are left
	 * up to you: target objects, tweenable properties, tween values — and  
importantly, the datatypes of all of these.</p>
	 *
	 * <p>A basic subclass can be created in three steps: Gathering target &  
property information, subclassing the
	 * <code>start</code> method to set up the tween, and finally subclassing  
the <code>onUpdate</code> method
	 * to affect the tween. The first step, gathering tween target and  
property information, can be done by writing
	 * getter/setter properties, customizing the constructor, or both.  
Consider various options such as allowing for
	 * single vs. multiple target objects, open vs. specific tween properties,  
and so on. The next step, subclassing
	 * <code>start</code>, involves figuring the tween's amount of change and  
implementing a standard Go convention,
	 * <code>useRelative</code>. This option should enable the user to declare  
tween values as relative to existing
	 * values instead of as fixed absolutes. In the final step, you subclass  
<code>onUpdate</code> to apply the tween,
	 * using the <code>_position</code> calculated by this base class:</p>
	 *
	 * <pre>target[ propName ] = super.correctValue(start + change *  
_position);</pre>
	 *
	 * <p>The helper method <code>correctValue</code> is provided in the  
superclass GoItem, to clean up NaN values
	 * and apply rounding when <code>useRounding</code> is activated. That's  
it — events and callbacks are
	 * dispatched by LinearGo, so subclasses can remain simple.</p>
	 *
	 * <p>An optional fourth step will make your custom tween compatible with  
Go managers. To do this, implement
	 * the IManageable interface. (OverlapMonitor prevents different tween  
instances from handling the same
	 * property at once; you can build other managers as well.)</p>
	 *
	 * {In the game of Go a black or white stone is called a go-ishi.}
	 *
	 * @author Moses Gunesch
	 */
	public class LinearGo extends GoItem implements IPlayable
	{
		// -== Settable Class Defaults ==-
		
		/**
		 * Class default for the instance property delay.
		 * @default 0
		 * @see #delay
		 */
		public static var defaultDelay : Number = 0;
		
		/**
		 * Class default for the instance property duration.
		 * @default 1
		 * @see #duration
		 */
		public static var defaultDuration : Number = 1;
		
		/**
		 * Class default for the instance property easing.
		 * Note that this property is left null until the first LinearGo
		 * is instantiated, at which time it is set to Quintic.easeOut.
		 * @default fl.motion.easing.Quintic.easeOut
		 * @see #easing
		 */
		public static var defaultEasing:Function;
		
		/**
		 * Normal default easing, this is Quintic.easeOut.
		 * (The two default easings in this class are included because there's
		 * currently no single easing classpath shared between Flash & Flex.)
		 */
		public static function easeOut(t:Number, b:Number, c:Number, d:Number) :  
Number {
			return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
		};
		
		// -== Class Methods ==-
		
		/**
		 * An alternative default easing with no acceleration.
		 * (The two default easings in this class are included because there's
		 * currently no single easing classpath shared between Flash & Flex.)
		 */
		public static function easeNone(t:Number, b:Number, c:Number, d:Number) :  
Number {
			return c * t / d + b;
		};
		
		/**
		 * A quick one-time setup command that lets you turn on useFrames mode
		 * as a default for all new tweens and adjust some related settings.
		 * (Note that useFrames mode is normally only used for specialty  
situations.)
		 *
		 * @param defaultToFramesMode		Sets an internal default so all new  
LinearGo instances
		 * 									will be set to use framecounts for their delay and duration.
		 * 									Also sets GoItem.defaultPulseInterval to enterframe which is
		 * 									most normal for frame-based updates.
		 * @param useZeroBasedFrameIndex	Normally currentFrame reads 1 on first  
update, like the Flash
		 * 									timeline starts at Frame 1. Set this option to use a  
zero-based
		 * 									index on all tweens instead.
		 * @see #useFrames
		 * @see #currentFrame
		 */
		public static function setupUseFramesMode( defaultToFramesMode: Boolean =  
true,
								   				   useZeroBasedFrameIndex: Boolean=false):void {
			GoItem.defaultPulseInterval = GoEngine.ENTER_FRAME;
			_useFramesMode = defaultToFramesMode;
			if (useZeroBasedFrameIndex) { _framesBase = 0; }
		}

		// -== Pulic Properties ==-
		
		/**
		 * Number of seconds after start() call that the LinearGo begins  
processing.
		 * <p>If not set manually, the class default defaultDelay is adopted.</p>
		 * @see #defaultDelay
		 */
		public function get delay():Number {
			return _delay;
		}
		public function set delay(seconds:Number):void {
			if (_state==PlayStates.STOPPED && seconds >= 0) {
				_delay = seconds;
			}
		}
		
		/**
		 * Number of seconds the LinearGo takes to process.
		 * <p>If not set manually, the class default defaultDuration is  
adopted.</p>
		 * @see #defaultDuration
		 */
		public function get duration():Number {
			return _duration;
		}
		public function set duration(seconds:Number):void {
			if (_state==PlayStates.STOPPED && seconds >= 0) {
				_duration = seconds;
			}
		}
		
		/**
		 * Any standard easing-equation function such as the ones found in
		 * the Flash package fl.motion.easing or the flex package  
mx.effects.easing.
		 *
		 * <p>If not set manually, the class default defaultEasing is adopted. An  
error
		 * is thrown if the function does not follow the typical format. For  
easings
		 * that accept more than four parameters use  
<code>extraEasingParams</code>.
		 * </p>
		 *
		 * @see #defaultEasing
		 * @see #extraEasingParams
		 */
		public function get easing():Function {
			return _easing;
		}
		public function set easing(type:Function):void {
			if (_state==PlayStates.STOPPED) {
				try {
					if (type(1,1,1,1) is Number) {
						_easing = type;
						return;
					}
				} catch (e:Error) {}
				throw new EasingFormatError();
			}
		}
		
		/**
		 * Additional parameters to pass to easing functions that accept more  
than four.
		 * @see #easing
		 */
		public function get extraEasingParams() : Array {
			return _extraEaseParams;
		}
		public function set extraEasingParams(params:Array):void {
			if (_state==PlayStates.STOPPED && params is Array && params.length>0) {
				_extraEaseParams = params;
			}
		}
		
		/**
		 * A LinearGoRepeater instance that defines options for repeated
		 * or back-and-forth cycling animation.
		 *
		 * <p>You may pass a LinearGoRepeater instance to the constructor's
		 * repeater parameter to set all options at instantiation. The
		 * repeater's cycles property can be set to an integer, or
		 * to Repeater.INFINITE or 0 to repeat indefinitely, and checked using
		 * <code>linearGo.repeater.currentCycle</code>. LinearGoRepeater's
		 * <code>reverseOnCycle</code> flag is true by default, which
		 * causes animation to cycle back and forth. In that mode you can
		 * also specify a separate easing function (plus extraEasingParams)
		 * to use for the reverse animation cycle. For example, an easeOut
		 * easing with an easeIn easingOnCycle will produce a more
		 * natural-looking result. If <code>reverseOnCycle</code> is disabled,
		 * the animation will repeat its play forward each time.</p>
		 *
		 * <p>(The repeater property replaces the cycles, easeOnCycle and
		 * currentCycle parameters in earlier releases of LinearGo).</p>
		 *
		 * @see org.goasap.managers.LinearGoRepeater LinearGoRepeater
		 */
		public function get repeater(): LinearGoRepeater {
			return _repeater;
		}
		
		/**
		 * When useFrames mode is activated, duration and delay are treated
		 * as update-counts instead of time values.
		 *
		 * <p>(This mode is normally only used for specialty situations.)</p>
		 *
		 * <p>Using this feature with a pulseInterval of GoEngine.ENTER_FRAME
		 * will result in a frame-based update that mimics the behavior of the
		 * flash timeline. As with the timeline, frame-based tween durations can
		 * vary based on the host computer's processor load and other factors.</p>
		 *
		 * <p>The <code>setupUseFramesMode()</code> class method is a much easier
		 * way to use frames in your project, instead of setting this property
		 * on every tween individually.</p>
		 *
		 * @see #setupUseFramesMode()
		 */
		public function set useFrames(value:Boolean):void {
			if (_state==PlayStates.STOPPED)
				_useFrames = value;
		}
		public function get useFrames():Boolean {
			return _useFrames;
		}
		
		/**
		 * A number between 0 and 1 representing the current tween value.
		 *
		 * <p>Use this number as a multiplier to apply values to targets
		 * across time.<p>
		 *
		 * <p>Here's an example of what an overridden update method might  
contain:</p>
		 * <pre>
		 * super.update(currentTime);
		 * target[ propName ] = super.correctValue(startValue + change*_position);
		 * </pre>
		 * @see #timePosition
		 */
		public function get position():Number {
			return _position;
		}
		
		/**
		 * For time-based tweens, returns a time value which is negative during  
delay
		 * then spans the tween duration in positive values, ignoring repeat  
cycles.
		 *
		 * <p>In useFrames mode, this getter differs from  
<code>currentFrame</code>
		 * significantly. Instead of constantly increasing through all cycles as  
if
		 * tweens were back-to-back in a timeline layer, this method acts more  
like
		 * a single tween placed at frame 1, with a timeline playhead that scans  
back
		 * and forth or loops during cycles. So for a 10-frame tween with a  
5-frame
		 * delay and 2 repeater cycles with reverseOnCycle set to true, this  
method
		 * will return values starting at -5, start the animation at 1, play to 10
		 * then step backward to 1 again.</p>
		 *
		 * @see #position
		 * @see #currentFrame
		 * @see #duration
		 * @see #delay
		 * @see #setupUseFramesMode()
		 */
		public function get timePosition():Number {
			if (_state==PlayStates.STOPPED)
				return 0;
			var mult:Number = Math.max(0, timeMultiplier);
			if (_useFrames) {
				if (_currentFrame>_framesBase) {
					var cf:uint = _currentFrame-_framesBase;
					if (_repeater.direction==-1) {
						return ((_duration-1) - cf%_duration) + _framesBase;
					}
					return cf%_duration + _framesBase;
				}
				return _currentFrame;
			}
			return ((getTimer()-_startTime) / 1000 / mult);
		}

		/**
		 * Returns the number of updates that have occured since start.
		 *
		 * <p>This update-count property does not necessarily correspond
		 * to the actual player framerate, just the instance's pulseInterval.</p>
		 *
		 * <p>This property is set up to mirror the flash timeline. Imagine a  
timeline
		 * layer with a delay being a set of blank frames followed by the tween,
		 * followed by subsequent cycles as additional tweens: this is the way
		 * the <code>currentFrame</code> property works. Its first value is 1 by
		 * default, which can be changed to 0 in  
<code>setupUseFramesMode()</code>.
		 * This differs significantly from <code>timePosition</code>, which places
		 * the start of a single instance of the tween at frame 1 and steps its
		 * values from negative during delay then cycling through the single  
tween.</p>
		 *
		 *
		 * @see #useFrames
		 * @see #setupUseFramesMode()
		 * @see #timePosition
		 */
		public function get currentFrame():uint {
			return _currentFrame;
		}
		
		// -== Protected Properties ==-
		
		/** @private */
		protected static var _useFramesMode : Boolean = false;
		
		/** @private */
		protected static var _framesBase : Number = 1;
		
		/** @private */
		protected var _delay 			: Number;
		
		/** @private */
		protected var _duration 		: Number;
		
		/** @private */
		protected var _tweenDuration	: Number;
		
		/** @private */
		protected var _easing 			: Function;
		
		/** @private */
		protected var _easeParams		: Array;
		
		/** @private */
		protected var _extraEaseParams	: Array;
		
		/** @private */
		protected var _repeater			: LinearGoRepeater;
		
		/** @private */
		protected var _currentEasing	: Function;
		
		/** @private */
		protected var _useFrames		: Boolean;
		
		/** @private */
		protected var _started			: Boolean = false;
		
		/** @private */
		protected var _currentFrame		: int;
		
		/** @private */
		protected var _position			: Number;
		
		/** @private */
		protected var _change			: Number;
		
		/** @private */
		protected var _startTime		: Number;
		
		/** @private */
		protected var _endTime 			: Number;
		
		/** @private */
		protected var _pauseTime 		: Number;
		
		/** @private */
		protected var _callbacks		: Object = new Object(); // In tests, creating  
this object up front is more efficient.
		
		// -== Public Methods ==-
		
		/**
		 * The inputs here are not a convention, subclasses should design
		 * their own constructors appropriate to usage. They are provided
		 * here primarily as a convenience for subclasses. However, do not
		 * omit calling super() from subclass constructors: LinearGo's
		 * constructor sets and validates class defaults and sets up the
		 * repeater instance.
		 */
		public function LinearGo(	delay	 			: Number=NaN,
									duration 			: Number=NaN,
									easing 				: Function=null,
									extraEasingParams	: Array=null,
									repeater			: LinearGoRepeater=null,
									useRelative			: Boolean=false,
									useRounding			: Boolean=false,
									useFrames			: Boolean=false,
									pulseInterval		: Number=NaN ) {
			// validate & set class defaults first
			if (isNaN(defaultDelay))
				defaultDelay = 0;
			if (isNaN(defaultDuration))
				defaultDuration = 1;
			try { this.easing = defaultEasing; }
			catch (e1:EasingFormatError) { defaultEasing = easeOut; }
			// set params
			if (!isNaN(delay)) _delay = delay;
			else _delay = defaultDelay;
			if (!isNaN(duration)) _duration = duration;
			else _duration = defaultDuration;
			try { this.easing = easing; }
			catch (e2:EasingFormatError) {
				if (easing!=null) { throw e2; } // user passed invalid easing function
				this.easing = defaultEasing;
			}
			if (extraEasingParams) _extraEaseParams = extraEasingParams;
			if (useRelative) this.useRelative = true;
			if (useRounding) this.useRounding = true;
			_useFrames = (useFrames || _useFramesMode);
			if (!isNaN(pulseInterval)) _pulse = pulseInterval;
			if (repeater!=null) _repeater = repeater; // repeater setup makes  
super() call important for all subclasses.
			else _repeater = new LinearGoRepeater();
			_repeater.setParent(this);
		}
		
		/**
		 * Starts play for this LinearGo instance using GoEngine.
		 *
		 * <p>CONVENTION ALERT: If <code>useRelative</code> is true, calculate  
tween values
		 * relative to the target object's existing value as in the example  
below.</p>
		 *
		 * <p>Most typically you should also store the tween's start and change  
values
		 * for later use in <code>onUpdate</code>.</p>
		 *
		 * <pre>
		 * protected var _target : DisplayObject;
		 * protected var _width : Number;
		 * protected var _changeWidth : Number;
		 *
		 * public function start():Boolean
		 * {
		 *     if (!_target || !_width || isNaN(_width))
		 *         return false;
		 *
		 *     _startWidth = _target.width;
		 *
		 *     if (useRelative) {
		 *         _changeWidth = _width;
		 *     } else {
		 *         _changeWidth = (_width - _startWidth);
		 *     }
		 *
		 *     return (super.start());
		 * }
		 * </pre>
		 *
		 * @return Successful addition of the item to GoEngine
		 *
		 * @see GoItem#useRelative
		 * @see #onUpdate()
		 */
		public function start() : Boolean {
			stop(); // does nothing if already stopped.
			if (GoEngine.addItem(this)==false)
				return false;
			reset();
			_state = (_delay > 0 ? PlayStates.PLAYING_DELAY : PlayStates.PLAYING);  
// has to be set here since delay is not included in PlayableBase.
			// note: start event is dispatched on the first update cycle for tighter  
cross-item syncing.
			return true;
		}
		
		/**
		 * Ends play for this LinearGo instance and dispatches a GoEvent.STOP
		 * event if the tween is incomplete. This method does not typically
		 * require subclassing.
		 *
		 * @return Successful removal of the item from GoEngine
		 */
		public function stop() : Boolean {
			if (_state==PlayStates.STOPPED || GoEngine.removeItem(this)==false)
				return false;
			_state = PlayStates.STOPPED;
			var completed:Boolean = (_easeParams!=null &&  
_position==_easeParams[1]+_change);
			reset();
			if (!completed) // otherwise a COMPLETE event was dispatched.
				dispatch( GoEvent.STOP );
			return true;
		}

		/**
		 * Pauses play (including delay) for this LinearGo instance.
		 * This method does not typically require subclassing.
		 *
		 * @return Success
		 * @see #resume()
		 * @see org.goasap.GoEngine#setPaused GoEngine.setPaused()
		 */
		public function pause() : Boolean {
			if (_state==PlayStates.STOPPED || _state==PlayStates.PAUSED)
				return false;
			_state = PlayStates.PAUSED;
			_pauseTime = (_useFrames ? _currentFrame : getTimer()); // This causes  
update() to skip processing.
			dispatch(GoEvent.PAUSE);
			return true;
		}
		
		/**
		 * Resumes previously paused play, including delay.
		 * This method does not typically require subclassing.
		 *
		 * @return Success
		 * @see #pause()
		 * @see org.goasap.GoEngine#setPaused GoEngine.setPaused()
		 */
		public function resume() : Boolean {
			if (_state != PlayStates.PAUSED)
				return false;
			var currentTime:Number = (_useFrames ? _currentFrame : getTimer());
			setup(currentTime - (_pauseTime - _startTime));
			_pauseTime = NaN;
			_state = (_startTime > currentTime ? PlayStates.PLAYING_DELAY :  
PlayStates.PLAYING);
			dispatch(GoEvent.RESUME);
			return true;
		}
		
		/**
		 * Skips to a point in the tween's duration and plays, from any state.
		 * This method does not typically require subclassing.
		 *
		 * <p>If GoItem.timeMultiplier is set to a custom value, you should still  
pass a
		 * seconds value based on the tween's real duration setting.</p>
		 *
		 * @param time		Seconds or frames to jump to across all cycles, where 0  
(or 1 in useFramesMode)
		 * 					represents tween start, numbers greater than duration represent  
higher repeat cycles,
		 * 					and negative numbers represent a new delay to play before tween  
start.
		 * @return Success
		 * @see #timePosition
		 */
		public function skipTo(time : Number) : Boolean
		{
			if (_state==PlayStates.STOPPED) {
				if (start()==false)
					return false;
			}
			if (isNaN(time)) { time = 0; }
			var mult:Number = Math.max(0, timeMultiplier) * (_useFrames ? 1 : 1000);
			var startTime:Number;
			var currentTime:Number;
			if (time < _framesBase) { // Negative value: rewind and add a new delay.
				_repeater.reset();
				if (_position>0) { skipTo(_framesBase); } // skips to start so new  
pause occurs in starting position
			}
			else {
				time = _repeater.skipTo(_duration, time-_framesBase); // sets cycles  
and returns new position
			}
			if (_useFrames) {
				startTime = _framesBase;
				currentTime = _currentFrame = Math.round(time*mult);
			}
			else {
				currentTime = getTimer();
				startTime = (currentTime - (time * mult)); // skipTo operation is  
performed by altering the tween's start & end times.
			}
			setup(startTime);
			_state = (_startTime > currentTime ? PlayStates.PLAYING_DELAY :  
PlayStates.PLAYING);
			update(currentTime); // sets _position
			return true;
		}
		
		/**
		 * An alternative to subscribing to events is to store callbacks. You can
		 * associate any number of callbacks with the primary GoEvent types START,
		 * UPDATE, COMPLETE, and STOP (only fired if the tween is stopped before  
it
		 * completes).
		 *
		 * <p>
		 * Note that there is little difference between using callbacks and  
events.
		 * Both are common techniques used in many various modern tweening APIs.  
Callbacks
		 * are slightly faster, but this won't normally be noticeable unless  
thousands of
		 * tweens are being run at once.
		 * </p>
		 *
		 * @param closure	A reference to a callback function
		 * @param type		Any GoEvent type constant, the default is COMPLETE.
		 * @see #removeCallback
		 * @see org.goasap.events.GoEvent GoEvent
		 */
		public function addCallback(closure : Function, type :  
String=GoEvent.COMPLETE):void {
			if (!_callbacks[ type ])
				_callbacks[ type ] = new Array();
			var a:Array = (_callbacks[ type ] as Array);
			if (a.indexOf(closure)==-1)
				a.push(closure);
		}
		
		/**
		 * Removes a method closure previously stored using addCallback.
		 *
		 * @param closure	A reference to a function
		 * @param type		A GoEvent constant, default is COMPLETE.
		 * @see #addCallback
		 * @see org.goasap.events.GoEvent GoEvent
		 */
		public function removeCallback(closure : Function, type :  
String=GoEvent.COMPLETE):void {
			var a:Array = (_callbacks[ type ] as Array);
			if (a)
				while (a.indexOf(closure)>-1)
					a.splice(a.indexOf(closure), 1);
		}
		
		/**
		 * Performs tween calculations on GoEngine pulse.
		 *
		 * <p>Subclass <code>onUpdate</code> instead of this method.
		 *
		 * @param currentTime	Clock time for the current block of updates.
		 * @see #onUpdate()
		 */
		override public function update(currentTime:Number) : void
		{
			if (_state==PlayStates.PAUSED)
				return;
			
			_currentFrame ++;
			if (_useFrames)
				currentTime = _currentFrame;
			
			if (isNaN(_startTime))		// setup() must be called once prior to tween's  
1st update.
				setup(currentTime);		// This is done here, not in start, for tighter  
syncing of items.
			
			if (_startTime > currentTime)
				return; // still PlayStates.PLAYING_DELAY
			
			// (1.) Set _position and determine primary update type.
			var type:String = GoEvent.UPDATE;
			if (currentTime < _endTime) { // start, update...
				if (!_started)
					type = GoEvent.START;
				var time:Number = _easeParams[0] = (currentTime - _startTime);
				_position = (time==0 ? 0 : _currentEasing.apply(null, _easeParams)); //  
update position using easing function.
			}																		// [note: checking for zero may reduce efficiency a  
little. Back easing gives a faulty return value @ 0.]
			else { // complete, cycle...
				_position = _easeParams[1] + _change; // set absolute 1 or 0 position  
at end of cycle
				type = (_repeater.hasNext() ? GoEvent.CYCLE : GoEvent.COMPLETE);
			}
			
			// (2.) Run onUpdate() passing the primary update type, then
			// (3.) dispatch up to three events in correct order.
			onUpdate(type);
			if (!_started) {
				_state = PlayStates.PLAYING;
				_started = true;
				dispatch(GoEvent.START);
			}
			dispatch(GoEvent.UPDATE);
			if (type==GoEvent.COMPLETE) {
				stop();
				dispatch(GoEvent.COMPLETE);
			}
			else if (type==GoEvent.CYCLE) {
				_repeater.next();
				dispatch(GoEvent.CYCLE);
				_startTime = NaN; // causes setup() to be called again on next update  
to prep next cycle.
			}
		}
		
		// -== Protected Methods ==-
		
		/**
		 * Subclass this method (instead of the update method) for simplicity.
		 *
		 * <p>Use this method to manipulate targets based on the current _position
		 * setting, which is a 0-1 multiplier precalculated to the tween's  
position
		 * based on its easing style and the current time in the tween.</p>
		 *
		 * <p>CONVENTION ALERT: To implement the Go convention  
<code>useRounding</code>,
		 * always call GoItem's <code>correctValue()</code> method on each  
calculated
		 * tween value before you apply it to a target. This corrects NaN to 0 and
		 * rounds the value if <code>useRounding</code> is true.</p>
		 *
		 * Example:
		 * <pre>
		 * override protected function onUpdate(type:String):void
		 * {
		 *     target[ propName ] = super.correctValue(startValue +  
change*_position);
		 * }
		 * </pre>
		 *
		 * @param type	A constant from the class GoEvent: START, UPDATE, CYCLE,  
or COMPLETE.
		 * @see GoItem#correctValue()
		 * @see GoItem#useRounding
		 * @see #update()
		 */
		protected function onUpdate(type : String) : void
		{
			// Subclass this method and start to implement your tween class.
		}
		
		/**
		 * @private
		 * Internal setup routine used by start() and other methods.
		 *
		 * @param time			Tween start time based on getTimer
		 */
		protected function setup(startTime : Number) : void
		{
			_startTime = startTime;
			var mult:Number = Math.max(0, timeMultiplier) * (_useFrames ? 1 : 1000);
			_tweenDuration = (_useFrames ? Math.round(_duration * mult)-1 :  
(_duration * mult));
			_endTime = _startTime + _tweenDuration;
			if (!_started) {
				var d:Number = (_useFrames ? Math.round(_delay * mult) : (_delay *  
mult));
				_startTime += d;
				_endTime += d;
			}
			// Set up a tween cycle: _currentEasing, _change, _position, and  
_easeParams.
			// Be sure _repeater is updated before this call so the next cycle gets  
set up.
			var useCycleEase:Boolean = _repeater.currentCycleHasEasing;
			_currentEasing = (useCycleEase ? _repeater.easingOnCycle : _easing);
			var extras:Array = (useCycleEase ? _repeater.extraEasingParams :  
_extraEaseParams);
			_change = _repeater.direction;
			_position = (_repeater.direction==-1 ? 1 : 0);
			_easeParams = new Array(0, _position, _change, _tweenDuration); //  
stored to reduce runtime object-creation
			if (extras) _easeParams = _easeParams.concat(extras);
		}
		
		/**
		 * @private
		 * Internal, dispatches events and executes callbacks of any pre-verified  
type.
		 *
		 * @param type	Verified in addCallback, not in this method.
		 * @see #org.goasap.events.GoEvent GoEvent
		 */
		protected function dispatch(type:String):void
		{
			var a:Array = (_callbacks[ type ] as Array);
			if (a)
				for each (var callback:Function in a)
					callback();
			if (hasEventListener(type))
				dispatchEvent(new GoEvent( type ));
		}
		
		/**
		 * @private
		 */
		protected function reset() : void {
			_position = 0;
			_change = 1;
			_repeater.reset();
			_currentFrame = _framesBase-1;
			_currentEasing = _easing;
			_easeParams = null;
			_started = false;
			_pauseTime = NaN;
			_startTime = NaN;
		}
	}
}
\ No newline at end of file

Modified: branches/goasap0.5.2/src_go/org/goasap/utils/PlayableGroup.as
==============================================================================
--- branches/goasap0.5.2/src_go/org/goasap/utils/PlayableGroup.as	(original)
+++ branches/goasap0.5.2/src_go/org/goasap/utils/PlayableGroup.as	Sun Mar  
15 17:46:33 2009
@@ -1 +1 @@
-/**
  * Copyright (c) 2007 Moses Gunesch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a  
copy
  * of this software and associated documentation files (the "Software"), to  
deal
  * in the Software without restriction, including without limitation the  
rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included  
in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  
OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  
THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  
FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
package org.goasap.utils {
	import flash.utils.Dictionary;
	
	import org.goasap.PlayStates;
	import org.goasap.PlayableBase;
	import org.goasap.events.GoEvent;
	import org.goasap.interfaces.IPlayable;
	import org.goasap.managers.Repeater;	

	/**
	 * Dispatched when the group starts.
	 * @eventType org.goasap.events.START
	 */
	[Event(name="START", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when the group is paused successfully.
	 * @eventType org.goasap.events.PAUSE
	 */
	[Event(name="PAUSE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when the group is resumed successfully.
	 * @eventType org.goasap.events.RESUME
	 */
	[Event(name="RESUME", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched at the end the group if <code>repeater.cycles</code> is set  
to
	 * a value other than one, just before the group starts its next play  
cycle.
	 * @eventType org.goasap.events.CYCLE
	 */
	[Event(name="CYCLE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched if the group is manually stopped.
	 * @eventType org.goasap.events.STOP
	 */
	[Event(name="STOP", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched after all children have dispatched a STOP or COMPLETE event.
	 * @eventType org.goasap.events.COMPLETE
	 */
	[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]


	/**
	 * Batch-play a set of items and receive an event when all of them have  
finished.
	 *
	 * <p>PlayableGroup accepts any IPlayable for its children, which can  
include
	 * tweens, other groups, sequences and so forth. The group listens for both
	 * GoEvent.STOP and GoEvent.COMPLETE events from its children, either of  
which
	 * are counted toward group completion.</p>
	 *
	 * <p>The <code>repeater</code> property of PlayableGroup allows you to  
loop play
	 * any number of times, or indefinitely by setting its cycles to  
Repeater.INFINITE.
	 * GoEvent.CYCLE is dispatched on each loop and GoEvent.COMPLETE when  
finished.
	 * Other events dispatched include the GoEvent types START, STOP, PAUSE,  
and RESUME.</p>
	 *
	 * @author Moses Gunesch
	 */
	public class PlayableGroup extends PlayableBase implements IPlayable {
		
		// -== Public Properties ==-
		
		/**
		 * Get or set the children array. Only IPlayable items are stored. Note  
that
		 * unlike the methods <code>addChild</code> and <code>removeChild</code>,
		 * setting this property will stop any group play currently in progress.
		 */
		public function get children():Array {
			var a:Array = [];
			for (var item:Object in _children)
				a.push(item);
			return a;
		}
		public function set children(a:Array):void {
			if (_listeners > 0)
				stop();
			for each (var item:Object in a)
				if (item is IPlayable)
					addChild(item as IPlayable);
		}
		
		/**
		 * The groups's Repeater instance, which may be used to make
		 * it loop and play more than one time.
		 *
		 * <p>The Repeater's cycles property can be set to an integer, or
		 * to Repeater.INFINITE or 0 to repeat indefinitely.</p>
		 *
		 * <pre>
		 * var group:PlayableGroup = new PlayableGroup(tween1, tween2, tween3);
		 * group.repeater.cycles = 2;
		 * group.start();
		 * trace(group.repeater.currentCycle); // output: 0
		 * </pre>
		 */
		public function get repeater(): Repeater {
			return _repeater;
		}
		
		/**
		 * Determines the number of children currently being monitored
		 * for completion by the group.
		 */
		public function get listenerCount() : uint {
			return _listeners;
		}
		
		// I think I'm going to strip the adoptChildState option and add this  
simpler flag.
		//public var restartActiveChildren: Boolean = true;

		// -== Protected Properties ==-
		
		/** @private */
		protected var _children: Dictionary = new Dictionary();
		
		/** @private */
		protected var _listeners: uint = 0;
		
		/** @private */
		protected var _repeater: Repeater;
		
		// -== Public Methods ==-
		
		/**
		 * Constructor.
		 *
		 * @param items	Any number of IPlayable items as separate arguments,
		 * 					or a single array of them.
		 */
		public function PlayableGroup(...items) {
			super();
			if (items.length > 0)
				this.children = ((items[ 0 ] is Array) ? items[ 0 ] : items);
			_repeater = new Repeater();
			_repeater.setParent(this);
		}
		
		/**
		 * Searches for a child with the specified playableID.
		 *
		 * @param playableID	The item playableID to search for. (The item must  
have a
		 * 						property called <code>playableID</code> which is a general
		 * 						GoASAP convention established in PlayableBase.)
		 * @param deepSearch	If child is not found in the group, this option runs  
a
		 * 						recursive search on any children that are PlayableGroup.
		 * @return				The SequenceStep with the matching playableID.
		 */
		public function getChildByID(playableID:*,  
deepSearch:Boolean=true):IPlayable {
			for (var item:Object in _children)
				if (item.hasOwnProperty("playableID") &&  
item["playableID"]===playableID)
					return (item as IPlayable);
			if (deepSearch) {
				for (item in _children) {
					if (item is PlayableGroup) {
						var match:IPlayable = ((item as  
PlayableGroup).getChildByID(playableID, true));
						if (match) { return (match as IPlayable); }
					}
				}
			}
			return null;
		}
		
		/**
		 * Adds a single IPlayable to the children array (duplicates are  
rejected) and
		 * syncs up the group and child play-states based on various conditions.
		 *
		 * <p>If both the group and the item being added are STOPPED, the item is  
simply
		 * added to the children list.</p>
		 *
		 * <p>If both the group and the item being added are PAUSED or  
PLAYING/PLAYING_DELAY,
		 * the child is actively added to the group during play and will be  
monitored for
		 * completion along with others.</p>
		 *
		 * <p>In other cases where the child's state mismatches the group's  
state, there
		 * are several behaviors available. Normally if the second parameter  
<code>adoptChildState</code>
		 * is left false, the child's mismatched state will be updated to match  
the group's
		 * state. This can result in it being stopped, paused, or started/resumed  
and monitored
		 * for completion along with other children. Passing true for  
<code>adoptChildState</code>
		 * results in updating the group's state to match the child's. This  
option could be used, for
		 * example, if you wanted to build a group of already-playing items  
without disrupting their
		 * play cycle with a start() call to the group.</p>
		 *
		 * @param item				Any instance that implements IPlayable and uses  
PlayState constants.
		 *
		 * @param adoptChildState	Makes this group change its play-state to match  
the state of the new child.
		 *
		 * @return 					False if the child was already in the group, or if  
adoptChildState is false
		 * 							and a child with a mismatched state was not settable to the  
group's state.
		 */
		public function addChild(item:IPlayable, adoptChildState:Boolean=false):  
Boolean {
			if (_children[ item ]!=null) {
				return false; // child already added!
			}
			_children[ item ] = false;
			if (item.state==_state) {
				return true; // states match: no further action is needed. this is most  
common.
			}
			
			// special case for adoptChildState-true: resolve mismatch by affecting  
this group.
			if (adoptChildState) {
				return adoptState(item);
			}
			
			// normal case for adoptChildState=false: resolve mismatch by updating  
child state to match group. fail out if child state cannot be set.
			var success:Boolean = true;
			listenTo(item); // first in case of immediate STOP/COMPLETE.
			if (_state==PlayStates.STOPPED) {
				success = item.stop();
			}
			else if (_state==PlayStates.PAUSED) {
				if (item.state==PlayStates.STOPPED) {
					success = (item.start()==true && item.state!=PlayStates.STOPPED);
				}
				success = (success && item.pause());
			}
			else if (_state==PlayStates.PLAYING || _state==PlayStates.PLAYING_DELAY)  
{
				if (item.state==PlayStates.PAUSED)
					item.resume();
				else if (item.state==PlayStates.STOPPED) {
					success = (item.start()==true && item.state!=PlayStates.STOPPED);
				}
			}
			if (success==false) {
				unListenTo(item);
				delete _children[ item ];
			}
			return success;
		}
		
		/**
		 * Removes a single IPlayable from the children array.
		 *
		 * <p>Note that if play is in progress when a child is added it does not
		 * interrupt play and the child is monitored for completion along with
		 * others.</p>
		 *
		 * @param item		Any instance that implements IPlayable and uses PlayState  
constants.
		 * @return Success.
		 */
		public function removeChild(item:IPlayable): Boolean {
			var v:* = _children[ item ];
			if (v===null)
				return false;
			if (v===true)
				unListenTo( item );
			delete _children[ item ];
			return true;
		}
		
		/**
		 * Test whether any child has a particular PlayState.
		 *
		 * <pre>
		 * // Example: resume a paused group
		 * if ( myGroup.anyChildHasState(PlayStates.PlayStates.PAUSED) ) {
		 *     myGroup.resume();
		 * }
		 * </pre>
		 * @see org.goasap.PlayStates PlayStates
		 */
		public function anyChildHasState(state:String): Boolean {
			for (var item:Object in _children)
				if ((item as IPlayable).state==state)
					return true;
			return false;
		}
		
		// -== IPlayable implementation ==-
		
		/**
		 * Calls start on all children.
		 *
		 * <p>If the group is active when this method is called, a  
<code>stop</code> call
		 * is automated which will result in a GoEVent.STOP event being  
dispatched.</p>
		 *
		 * @return Returns true if any child in the group starts successfully.
		 */
		public function start() : Boolean {
			stop();
			var r:Boolean = false;
			var c:Array = this.children; // array is used because cycling Dictionary  
and setting entries can result in duplicates (Dictionary bug, apparently.)
			for each (var item:IPlayable in c) {
				listenTo(item); // first in case of immediate STOP/COMPLETE.
				var started:Boolean = item.start();
				if (!started || item.state==PlayStates.STOPPED) {
					unListenTo(item);
					started = false;
				}
				r = (started || r);
			}
			if (!r) return false; // all starts failed
			_state = PlayStates.PLAYING;
			dispatchEvent(new GoEvent( GoEvent.START));
			_playRetainer[ this ] = 1; // Developers - Important! Look up  
_playRetainer.
			return true;
		}

		/**
		 * If the group is active, this method stops all child items and
		 * dispatches a GoEvent.STOP event.
		 *
		 * @return Returns true only if all children in the group stop  
successfully.
		 */
		public function stop() : Boolean {
			if (_state == PlayStates.STOPPED)
				return false;
			_state = PlayStates.STOPPED;
			_repeater.reset();
			delete _playRetainer[ this ]; // Developers - Important! Look up  
_playRetainer.
			if (_listeners==0) {
				dispatchEvent(new GoEvent( GoEvent.COMPLETE ));
				return true;
			}
			var r:Boolean = true;
			var c:Array = this.children; // array is used because cycling Dictionary  
and setting entries can result in duplicates (Dictionary bug, apparently.)
			for each (var item:IPlayable in c) {
				unListenTo(item);
				r = (item.stop() && r);
			}
			dispatchEvent(new GoEvent( GoEvent.STOP ));
			return r;
		}
		
		/**
		 * Calls <code>pause</code> on all children.
		 *
		 * @return  Returns true only if all playing children in the group paused  
successfully
		 * 			and at least one child was paused.
		 */
		public function pause() : Boolean {
			if (_state!= PlayStates.PLAYING)
				return false;
			var r:Boolean = true;
			var n:uint = 0;
			var c:Array = this.children; // array is used because cycling Dictionary  
and setting entries can result in duplicates (Dictionary bug, apparently.)
			for each (var item:IPlayable in c) {
				var success:Boolean = item.pause();
				if (success) n++;
				r = (r && success);
			}
			if (n>0) {
				_state = PlayStates.PAUSED; // state should reflect that at least one  
item was paused,
								  // while return value may indicate that not all pause calls  
succeeded.
				dispatchEvent(new GoEvent( GoEvent.PAUSE ));
			}
			return (n>0 && r);
		}
		
		/**
		 * Calls <code>resume</code> on all children.
		 *
		 * @return	Returns true only if all paused children in the group resumed  
successfully
		 * 			and at least one child was resumed.
		 */
		public function resume() : Boolean {
			if (_state!= PlayStates.PAUSED)
				return false;
			var r:Boolean = true;
			var n:uint = 0;
			var c:Array = this.children; // array is used because cycling Dictionary  
and setting entries can result in duplicates (Dictionary bug, apparently.)
			for each (var item:IPlayable in c) {
				var success:Boolean = item.resume();
				if (success) n++;
				r = (r && success);
			}
			if (n>0) {
				_state = PlayStates.PLAYING; // state should reflect that at least one  
item was resumed,
								  // while return value may indicate that not all resume calls  
succeeded.
				dispatchEvent(new GoEvent( GoEvent.RESUME ));
			}
			return (n>0 && r);
		}
		
		/**
		 * Calls <code>skipTo</code> on all children.
		 *
		 * @return	Returns true only if all children in the group skipTo the  
position successfully
		 * 			and at least one child was affected.
		 */
		public function skipTo(position : Number) : Boolean {
			var r:Boolean = true;
			var n:uint = 0;
			position = _repeater.skipTo(_repeater.cycles, position); // TODO: TEST
			var c:Array = this.children; // array is used because cycling Dictionary  
and setting entries can result in duplicates (Dictionary bug, apparently.)
			for each (var item:IPlayable in c) {
				listenTo(item); // first in case of immediate STOP/COMPLETE.
				var started:Boolean = item.skipTo(position);
				if (!started || item.state==PlayStates.STOPPED) {
					unListenTo(item);
					started = false;
				}
				else { n++; }
				r = (started && r);
			}
			_state = (r ? PlayStates.PLAYING : PlayStates.STOPPED);
			return (n>0 && r);
		}
		
		// -== Protected Methods ==-
		
		/**
		 * @private
		 * Internal handler for item completion.
		 * @param event		GoEvent dispatched by child item.
		 */
		protected function onItemEnd(event:GoEvent) : void {
			unListenTo(event.target as IPlayable);
			if (_listeners==0) {
				complete();
			}
		}
		
		/**
		 * @private
		 * Internal handler for group completion.
		 */
		protected function complete() : void {
			if (_repeater.next()) {
				dispatchEvent(new GoEvent( GoEvent.CYCLE ));
				var c:Array = this.children; // array is used because cycling  
Dictionary and setting entries can result in duplicates (Dictionary bug,  
apparently.)
				for each (var item:IPlayable in c) {
					listenTo(item); // first in case of immediate STOP/COMPLETE.
					var started:Boolean = (item).start();
					if (!started || item.state==PlayStates.STOPPED)
						unListenTo(item);
				}
			}
			else {
				stop();
			}
		}

		/**
		 * @private
		 * Internal. Listen for item completion, keeping tight track of listeners.
		 * @param item	Any instance that extends IPlayable (IPlayable itself  
should not be used directly).
		 */
		protected function listenTo(item:IPlayable) : void {
			if (_children[ item ] === false) {
				item.addEventListener(GoEvent.STOP, onItemEnd, false, 0, true);
				item.addEventListener(GoEvent.COMPLETE, onItemEnd, false, 0, true);
				_children[ item ] = true;
				_listeners++;
			}
		}
		
		/**
		 * @private
		 * Internal. Stop listening for item completion.
		 * @param item	Any instance that extends IPlayable (IPlayable itself  
should not be used directly).
		 * @return Number of completion listeners remaining.
		 */
		protected function unListenTo(item:IPlayable) : void {
			if (_children[ item ] === true) {
				item.removeEventListener(GoEvent.STOP, onItemEnd);
				item.removeEventListener(GoEvent.COMPLETE, onItemEnd);
				_children[ item ] = false;
				_listeners--;
			}
		}
		
		/**
		 * @private
		 * @param item	Item whose state to adopt
		 * @see #addChild
		 */
		protected function adoptState(item:IPlayable): Boolean {
			if (item.state==PlayStates.STOPPED) {
				this.stop();
				return true;
			}
			listenTo(item);
			var startOthers:Boolean = false;
			if (item.state==PlayStates.PAUSED) { // pause() is called after the  
group is started
				if (_state==PlayStates.STOPPED)
					startOthers = true;
			}
			else if (item.state==PlayStates.PLAYING ||  
item.state==PlayStates.PLAYING_DELAY) {
				if (_state==PlayStates.PAUSED)
					this.resume();
				else
					startOthers = true;
			}
			if (startOthers) { // differs from start() in that only non-playing  
items are started.
				var c:Array = this.children; // array is used because cycling  
Dictionary and setting entries can result in duplicates (Dictionary bug,  
apparently.)
				for each (var child:IPlayable in c) {
					if (child!=item) {
						if (child.state==PlayStates.PAUSED) {
							child.resume();
						}
						else if (child.state==PlayStates.STOPPED) {
							listenTo(child); // first in case of immediate STOP/COMPLETE.
							if (child.start()==false || child.state==PlayStates.STOPPED)
								unListenTo(child);
						}
					}
				}
				_state = PlayStates.PLAYING; // Group adopts child playing state
				dispatchEvent(new GoEvent( GoEvent.START));
			}
			if (item.state==PlayStates.PAUSED) // see note above
				this.pause();
			return true;
		}
	}
}
\ No newline at end of file
+/**
  * Copyright (c) 2007 Moses Gunesch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a  
copy
  * of this software and associated documentation files (the "Software"), to  
deal
  * in the Software without restriction, including without limitation the  
rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included  
in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  
OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  
THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  
FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
package org.goasap.utils {
	import org.goasap.PlayStates;
	import org.goasap.PlayableBase;
	import org.goasap.events.GoEvent;
	import org.goasap.interfaces.IPlayable;
	import org.goasap.managers.Repeater;
	
	import flash.utils.Dictionary;	

	/**
	 * Dispatched when the group starts.
	 * @eventType org.goasap.events.START
	 */
	[Event(name="START", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when the group is paused successfully.
	 * @eventType org.goasap.events.PAUSE
	 */
	[Event(name="PAUSE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when the group is resumed successfully.
	 * @eventType org.goasap.events.RESUME
	 */
	[Event(name="RESUME", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched at the end the group if <code>repeater.cycles</code> is set  
to
	 * a value other than one, just before the group starts its next play  
cycle.
	 * @eventType org.goasap.events.CYCLE
	 */
	[Event(name="CYCLE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched if the group is manually stopped.
	 * @eventType org.goasap.events.STOP
	 */
	[Event(name="STOP", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched after all children have dispatched a STOP or COMPLETE event.
	 * @eventType org.goasap.events.COMPLETE
	 */
	[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]


	/**
	 * Batch-play a set of items and receive an event when all of them have  
finished.
	 *
	 * <p>PlayableGroup accepts any IPlayable for its children, which can  
include
	 * tweens, other groups, sequences and so forth. The group listens for both
	 * GoEvent.STOP and GoEvent.COMPLETE events from its children, either of  
which
	 * are counted toward group completion.</p>
	 *
	 * <p>The <code>repeater</code> property of PlayableGroup allows you to  
loop play
	 * any number of times, or indefinitely by setting its cycles to  
Repeater.INFINITE.
	 * GoEvent.CYCLE is dispatched on each loop and GoEvent.COMPLETE when  
finished.
	 * Other events dispatched include the GoEvent types START, STOP, PAUSE,  
and RESUME.</p>
	 *
	 * @author Moses Gunesch
	 */
	public class PlayableGroup extends PlayableBase implements IPlayable {
		
		// -== Public Properties ==-
		
		/**
		 * Get or set the children array. Only IPlayable items are stored. Note  
that
		 * unlike the methods <code>addChild</code> and <code>removeChild</code>,
		 * setting this property will stop any group play currently in progress.
		 */
		public function get children():Array {
			var a:Array = [];
			for (var item:Object in _children)
				a.push(item);
			return a;
		}
		public function set children(a:Array):void {
			if (_listeners > 0)
				stop();
			for each (var item:Object in a)
				if (item is IPlayable)
					addChild(item as IPlayable);
		}
		
		/**
		 * The groups's Repeater instance, which may be used to make
		 * it loop and play more than one time.
		 *
		 * <p>The Repeater's cycles property can be set to an integer, or
		 * to Repeater.INFINITE or 0 to repeat indefinitely.</p>
		 *
		 * <pre>
		 * var group:PlayableGroup = new PlayableGroup(tween1, tween2, tween3);
		 * group.repeater.cycles = 2;
		 * group.start();
		 * trace(group.repeater.currentCycle); // output: 0
		 * </pre>
		 */
		public function get repeater(): Repeater {
			return _repeater;
		}
		
		/**
		 * Determines the number of children currently being monitored
		 * for completion by the group.
		 */
		public function get listenerCount() : uint {
			return _listeners;
		}
		
		// -== Protected Properties ==-
		
		/** @private */
		protected var _children: Dictionary = new Dictionary();
		
		/** @private */
		protected var _listeners: uint = 0;
		
		/** @private */
		protected var _repeater: Repeater;
		
		// -== Public Methods ==-
		
		/**
		 * Constructor.
		 *
		 * @param items	Any number of IPlayable items as separate arguments,
		 * 					or a single array of them.
		 */
		public function PlayableGroup(...items) {
			super();
			if (items.length > 0)
				this.children = ((items[ 0 ] is Array) ? items[ 0 ] : items);
			_repeater = new Repeater();
			_repeater.setParent(this);
		}
		
		/**
		 * Searches for a child with the specified playableID.
		 *
		 * @param playableID	The item playableID to search for. (The item must  
have a
		 * 						property called <code>playableID</code> which is a general
		 * 						GoASAP convention established in PlayableBase.)
		 * @param deepSearch	If child is not found in the group, this option runs  
a
		 * 						recursive search on any children that are PlayableGroup.
		 * @return				The SequenceStep with the matching playableID.
		 */
		public function getChildByID(playableID:*,  
deepSearch:Boolean=true):IPlayable {
			for (var item:Object in _children)
				if (item.hasOwnProperty("playableID") &&  
item["playableID"]===playableID)
					return (item as IPlayable);
			if (deepSearch) {
				for (item in _children) {
					if (item is PlayableGroup) {
						var match:IPlayable = ((item as  
PlayableGroup).getChildByID(playableID, true));
						if (match) { return (match as IPlayable); }
					}
				}
			}
			return null;
		}
		
		/**
		 * Adds a single IPlayable to the children array (duplicates are  
rejected) and
		 * syncs up the group and child play-states based on various conditions.
		 *
		 * <p>Normally this method is called when both the group and the child  
item are
		 * not playing. However in some cases either the group or the child might  
be
		 * playing or paused, in which case calling this method will attempt to  
sync
		 * the child state with the group's state. If syncing fails the child is  
not added
		 * and false is returned.</p>
		 *
		 * @param item				Any instance that implements IPlayable and uses  
PlayState constants.
		 * @return 					The item added, or null if the item was already in the  
group or
		 * 							a mismatched child state was not syncable to the group's state.
		 */
		public function addChild(item:IPlayable): IPlayable {
			var itemState:String = item.state;
			if (_children[ item ]!=null) {
				return null; // child already added!
			}
			_children[ item ] = false;
			if (_state==PlayStates.STOPPED && itemState!=PlayStates.STOPPED) {
				item.stop();
			}
			if (itemState==_state) {
				return item; // states match: no further action is needed. this is most  
common.
			}
			
			// Group is active: attempt syncing of child state to group state.
			var success:Boolean = true;
			listenTo(item);
			if (_state==PlayStates.PLAYING || _state==PlayStates.PLAYING_DELAY) {
				if (itemState==PlayStates.PAUSED) {
					success = item.resume();
				}
				else if (itemState==PlayStates.STOPPED) {
					success = item.start();
				}
				else {
					return item; // Item is already playing and only delay states  
mismatch: Success.
				}
			}
			else if (_state==PlayStates.PAUSED) {
				if (itemState==PlayStates.STOPPED) {
					item.start();
				}
				success = item.pause();
			}
			if (!success) {
				unListenTo(item);
				delete _children[ item ];
				return null;
			}
			return item;
		}
		
		/**
		 * Removes a single IPlayable from the children array.
		 *
		 * <p>Note that if play is in progress when a child is added it does not
		 * interrupt play and the child is monitored for completion along with
		 * others.</p>
		 *
		 * @param item		Any instance that implements IPlayable and uses PlayState  
constants.
		 * @return 			The item removed, or null if the call failed.
		 */
		public function removeChild(item:IPlayable): IPlayable {
			var v:* = _children[ item ];
			if (v===null)
				return null;
			if (v===true)
				unListenTo( item );
			delete _children[ item ];
			return item;
		}
		
		/**
		 * Enables already-playing items to be added to a group seamlessly by  
presetting
		 * the group's state to PLAYING instead of interrupting the child item.
		 *
		 * @param item		Any instance that implements IPlayable and uses PlayState  
constants.
		 * @return 			The item added, or null if the call failed.
		 */
		public function addPlayingChildAndStart(item:IPlayable): IPlayable {
			_state = PlayStates.PLAYING;
			item = addChild(item); // Item will start if stopped and resume if  
paused.
			if (_listeners > 0) {
				dispatchEvent(new GoEvent( GoEvent.START));
				_playRetainer[ this ] = 1; // Developers - Important! Look up  
_playRetainer.
			}
			else {
				_state = PlayStates.STOPPED;
			}
			return item;
		}
		
		/**
		 * Test whether any child has a particular PlayState.
		 *
		 * <pre>
		 * // Example: resume a paused group
		 * if ( myGroup.anyChildHasState(PlayStates.PlayStates.PAUSED) ) {
		 *     myGroup.resume();
		 * }
		 * </pre>
		 * @see org.goasap.PlayStates PlayStates
		 */
		public function anyChildHasState(state:String): Boolean {
			for (var item:Object in _children)
				if ((item as IPlayable).state==state)
					return true;
			return false;
		}
		
		// -== IPlayable implementation ==-
		
		/**
		 * Calls start on all children.
		 *
		 * <p>If the group is active when this method is called, a  
<code>stop</code> call
		 * is automated which will result in a GoEVent.STOP event being  
dispatched.</p>
		 *
		 * @return Returns true if any child in the group starts successfully.
		 */
		public function start() : Boolean {
			stop();
			var r:Boolean = false;
			var c:Array = this.children; // array is used because cycling Dictionary  
and setting entries can result in duplicates (Dictionary bug, apparently.)
			for each (var item:IPlayable in c) {
				listenTo(item); // first in case of immediate STOP/COMPLETE.
				var started:Boolean = item.start();
				if (!started) {
					unListenTo(item);
				}
				r = (started || r);
			}
			if (!r) return false; // all starts failed
			_state = PlayStates.PLAYING;
			dispatchEvent(new GoEvent( GoEvent.START));
			_playRetainer[ this ] = 1; // Developers - Important! Look up  
_playRetainer.
			return true;
		}

		/**
		 * If the group is active, this method stops all child items and
		 * dispatches a GoEvent.STOP event.
		 *
		 * @return Returns true only if all children in the group stop  
successfully.
		 */
		public function stop() : Boolean {
			if (_state == PlayStates.STOPPED)
				return false;
			_state = PlayStates.STOPPED;
			_repeater.reset();
			delete _playRetainer[ this ]; // Developers - Important! Look up  
_playRetainer.
			if (_listeners==0) {
				dispatchEvent(new GoEvent( GoEvent.COMPLETE ));
				return true;
			}
			var r:Boolean = true;
			var c:Array = this.children; // array is used because cycling Dictionary  
and setting entries can result in duplicates (Dictionary bug, apparently.)
			for each (var item:IPlayable in c) {
				unListenTo(item);
				r = (item.stop() && r);
			}
			dispatchEvent(new GoEvent( GoEvent.STOP ));
			return r;
		}
		
		/**
		 * Calls <code>pause</code> on all children.
		 *
		 * @return  Returns true only if all playing children in the group paused  
successfully
		 * 			and at least one child was paused.
		 */
		public function pause() : Boolean {
			if (_state!= PlayStates.PLAYING)
				return false;
			var r:Boolean = true;
			var n:uint = 0;
			var c:Array = this.children; // array is used because cycling Dictionary  
and setting entries can result in duplicates (Dictionary bug, apparently.)
			for each (var item:IPlayable in c) {
				var success:Boolean = item.pause();
				if (success) n++;
				r = (r && success);
			}
			if (n>0) {
				_state = PlayStates.PAUSED; // state should reflect that at least one  
item was paused,
								  // while return value may indicate that not all pause calls  
succeeded.
				dispatchEvent(new GoEvent( GoEvent.PAUSE ));
			}
			return (n>0 && r);
		}
		
		/**
		 * Calls <code>resume</code> on all children.
		 *
		 * @return	Returns true only if all paused children in the group resumed  
successfully
		 * 			and at least one child was resumed.
		 */
		public function resume() : Boolean {
			if (_state!= PlayStates.PAUSED)
				return false;
			var r:Boolean = true;
			var n:uint = 0;
			var c:Array = this.children; // array is used because cycling Dictionary  
and setting entries can result in duplicates (Dictionary bug, apparently.)
			for each (var item:IPlayable in c) {
				var success:Boolean = item.resume();
				if (success) n++;
				r = (r && success);
			}
			if (n>0) {
				_state = PlayStates.PLAYING; // state should reflect that at least one  
item was resumed,
								  // while return value may indicate that not all resume calls  
succeeded.
				dispatchEvent(new GoEvent( GoEvent.RESUME ));
			}
			return (n>0 && r);
		}
		
		/**
		 * Calls <code>skipTo</code> on all children.
		 *
		 * @return	Returns true only if all children in the group skipTo the  
position successfully
		 * 			and at least one child was affected.
		 */
		public function skipTo(position : Number) : Boolean {
			var r:Boolean = true;
			var n:uint = 0;
			position = _repeater.skipTo(_repeater.cycles, position); // TODO: TEST
			var c:Array = this.children; // array is used because cycling Dictionary  
and setting entries can result in duplicates (Dictionary bug, apparently.)
			for each (var item:IPlayable in c) {
				listenTo(item); // first in case of immediate STOP/COMPLETE.
				var started:Boolean = item.skipTo(position);
				if (!started || item.state==PlayStates.STOPPED) {
					unListenTo(item);
					started = false;
				}
				else { n++; }
				r = (started && r);
			}
			_state = (r ? PlayStates.PLAYING : PlayStates.STOPPED);
			return (n>0 && r);
		}
		
		// -== Protected Methods ==-
		
		/**
		 * @private
		 * Internal handler for item completion.
		 * @param event		GoEvent dispatched by child item.
		 */
		protected function onItemEnd(event:GoEvent) : void {
			unListenTo(event.target as IPlayable);
			if (_listeners==0) {
				complete();
			}
		}
		
		/**
		 * @private
		 * Internal handler for group completion.
		 */
		protected function complete() : void {
			if (_repeater.next()) {
				dispatchEvent(new GoEvent( GoEvent.CYCLE ));
				var c:Array = this.children; // array is used because cycling  
Dictionary and setting entries can result in duplicates (Dictionary bug,  
apparently.)
				for each (var item:IPlayable in c) {
					listenTo(item); // first in case of immediate STOP/COMPLETE.
					var started:Boolean = (item).start();
					if (!started || item.state==PlayStates.STOPPED)
						unListenTo(item);
				}
			}
			else {
				stop();
			}
		}

		/**
		 * @private
		 * Internal. Listen for item completion, keeping tight track of listeners.
		 * @param item	Any instance that extends IPlayable (IPlayable itself  
should not be used directly).
		 */
		protected function listenTo(item:IPlayable) : void {
			if (_children[ item ] === false) {
				item.addEventListener(GoEvent.STOP, onItemEnd, false, 0, true);
				item.addEventListener(GoEvent.COMPLETE, onItemEnd, false, 0, true);
				_children[ item ] = true;
				_listeners++;
			}
		}
		
		/**
		 * @private
		 * Internal. Stop listening for item completion.
		 * @param item	Any instance that extends IPlayable (IPlayable itself  
should not be used directly).
		 * @return Number of completion listeners remaining.
		 */
		protected function unListenTo(item:IPlayable) : void {
			if (_children[ item ] === true) {
				item.removeEventListener(GoEvent.STOP, onItemEnd);
				item.removeEventListener(GoEvent.COMPLETE, onItemEnd);
				_children[ item ] = false;
				_listeners--;
			}
		}
	}
}
\ No newline at end of file

Modified: branches/goasap0.5.2/src_go/org/goasap/utils/Sequence.as
==============================================================================
--- branches/goasap0.5.2/src_go/org/goasap/utils/Sequence.as	(original)
+++ branches/goasap0.5.2/src_go/org/goasap/utils/Sequence.as	Sun Mar 15  
17:46:33 2009
@@ -1 +1 @@
-/**
  * Copyright (c) 2007 Moses Gunesch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a  
copy
  * of this software and associated documentation files (the "Software"), to  
deal
  * in the Software without restriction, including without limitation the  
rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included  
in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  
OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  
THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  
FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
package org.goasap.utils {
	import org.goasap.interfaces.IPlayable;	

	/**
	 * Simple playable sequence, composed of groups of playable items.
	 *
	 * <p>A sequence can be built by passing any item that implements IPlayable
	 * and uses the standard set of PlayState constants. Sequences are composed
	 * of SequenceStep instances, which can contain any number of child items
	 * such as LinearGo or PlayableGroup instances. Sequences dispatch  
SequenceEvent.ADVANCE
	 * each time a step completes and the play index advances to the next one,
	 * then GoEvent.COMPLETE when done.</p>
	 *
	 * <p>Other events dispatched include the GoEvent types START, STOP,  
PAUSE, RESUME,
	 * and CYCLE if the repeater.cycles property is set to a value other than  
one.</p>
	 *
	 * <p>All items in each step must dispatch COMPLETE or STOP before a  
Sequence
	 * will advance. This simple behavior can be limiting, especially with  
steps that
	 * are composed of groups of items. The Go utility package includes another
	 * sequencer called SequenceCA, which allows you to define different ways  
a sequence
	 * can advance: after a particular item in a step, a particular duration,  
after
	 * an event fires, etc.</p>
	 *
	 * @see SequenceBase
	 * @see SequenceCA
	 * @author Moses Gunesch
	 */
	public class Sequence extends SequenceBase {

		// -== Public Properties ==-
		
		// Also in super:
		// length : uint   [Read-only.]
		// playIndex : int [Read-only.]
		// steps : Array
		// start() : Boolean
		// stop() : Boolean
		// pause() : Boolean
		// resume() : Boolean
		// skipTo(index:Number) : Boolean
		
		/**
		 * Returns the currently-playing SequenceStep.
		 * @return The currently-playing SequenceStep.
		 * @see #getStepAt()
		 * @see #getStepByID()
		 * @see #steps
		 * @see #lastStep
		 */
		public function get currentStep() : SequenceStep {
			return (super._getCurrentStep());
		}
		
		/**
		 * Returns the final SequenceStep in the current sequence.
		 * @return The final SequenceStep in the current sequence.
		 * @see #getStepAt()
		 * @see #getStepByID()
		 * @see #steps
		 * @see #currentStep
		 */
		public function get lastStep() : SequenceStep {
			return (super._getLastStep());
		}
		
		// -== Public Methods ==-
		
		/**
		 * Constructor.
		 *
		 * @param items		Any number of IPlayable instances (e.g. LinearGo,  
PlayableGroup,
		 * 					SequenceStep) as separate arguments, or a single array of them.
		 */
		public function Sequence(...items) {
			super((items[ 0 ] is Array) ? items[ 0 ] : items);
		}
		
		/**
		 * Retrieves any SequenceStep from the steps array.
		 * @param index		An array index starting at 0.
		 * @return			The SequenceStep instance at this index.
		 * @see #getStepByID()
		 * @see #currentStep
		 * @see #lastStep
		 */
		public function getStepAt(index:int) : SequenceStep {
			return (super._getStepAt(index) as SequenceStep);
		}
		
		/**
		 * Locates a step with the specified playableID. To search within a step  
for a
		 * child by playableID, use the step instance's <code>getChildByID</code>  
method.
		 *
		 * @param playableID	The step instance's playableID to search for.
		 * @return				The SequenceStep with the matching playableID.
		 * @see #getStepAt()
		 */
		public function getStepByID(playableID:*) : SequenceStep {
			return (super._getStepByID(playableID) as SequenceStep);
		}
		
		/**
		 * Adds a single IPlayable instance (e.g. LinearGo, PlayableGroup,  
SequenceStep)
		 * to the end of the steps array, or optionally adds the instance into  
the last
		 * SequenceStep instead of adding it as a new step.
		 *
		 * <p>To remove a step use the <code>removeStepAt</code> method.</p>
		 *
		 * @param item			The playable item to add to the sequence. Note
		 * 						that when new steps are added, any IPlayable
		 * 						instance of a type other than SequenceStep is
		 * 						automatically wrapped in a new SequenceStep.
		 * 						
		 * @param addToLastStep	If true is passed the item is added to the last
		 * 						existing SequenceStep in the steps array. This
		 * 						option should be used with individual items that
		 * 						you want added as children to the SequenceStep.
		 * 						If there are no steps yet this option ignored and
		 * 						a new step is created.
		 * 						
		 * @return New length of the steps array.
		 */
		public function addStep(item:IPlayable, addToLastStep:Boolean=false): int  
{
			return (super._addStep(item, addToLastStep, SequenceStep));
		}
		
		/**
		 * Adds a single IPlayable instance (e.g. LinearGo, PlayableGroup,
		 * SequenceStep) at a specific index in the steps array. Calling this  
method
		 * stops any sequence play currently in progress.
		 *
		 * @param item		The playable item to splice into the sequence.
		 *
		 * @param index		Position in the array starting at 0, or a negative
		 * 					index like Array.splice.
		 * 					
		 * @return 			New length of the steps array.
		 */
		public function addStepAt(item:IPlayable, index:int): int {
			return (super._addStepAt(index, item, SequenceStep));
		}

		/**
		 * Removes and returns the SequenceStep at a specific index from the steps
		 * array. Calling this method stops any sequence play currently in  
progress.
		 *
		 * @param index		Position in the array starting at 0, or a negative
		 * 					index like Array.splice.
		 * 					
		 * @return 			The SequenceStep instance removed from the steps array.
		 */
		public function removeStepAt(index:int): SequenceStep {
			return (super._removeStepAt(index) as SequenceStep);
		}
		
		// Also in super:
		// length : uint   [Read-only.]
		// playIndex : int [Read-only.]
		// steps : Array
		// start() : Boolean
		// stop() : Boolean
		// pause() : Boolean
		// resume() : Boolean
		// skipTo(index:Number) : Boolean
	}
}
\ No newline at end of file
+/**
  * Copyright (c) 2007 Moses Gunesch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a  
copy
  * of this software and associated documentation files (the "Software"), to  
deal
  * in the Software without restriction, including without limitation the  
rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included  
in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  
OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  
THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  
FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
package org.goasap.utils {
	import org.goasap.interfaces.IPlayable;	

	/**
	 * Simple playable sequence, composed of groups of playable items.
	 *
	 * <p>A sequence can be built by passing any item that implements IPlayable
	 * and uses the standard set of PlayState constants. Sequences are composed
	 * of SequenceStep instances, which can contain any number of child items
	 * such as LinearGo or PlayableGroup instances. Sequences dispatch  
SequenceEvent.ADVANCE
	 * each time a step completes and the play index advances to the next one,
	 * then GoEvent.COMPLETE when done.</p>
	 *
	 * <p>Other events dispatched include the GoEvent types START, STOP,  
PAUSE, RESUME,
	 * and CYCLE if the repeater.cycles property is set to a value other than  
one.</p>
	 *
	 * <p>All items in each step must dispatch COMPLETE or STOP before a  
Sequence
	 * will advance. This simple behavior can be limiting, especially with  
steps that
	 * are composed of groups of items. The Go utility package includes another
	 * sequencer called SequenceCA, which allows you to define different ways  
a sequence
	 * can advance: after a particular item in a step, a particular duration,  
after
	 * an event fires, etc.</p>
	 *
	 * @see SequenceBase
	 * @see SequenceCA
	 * @author Moses Gunesch
	 */
	public class Sequence extends SequenceBase {

		// -== Public Properties ==-
		
		// Also in super:
		// length : uint   [Read-only.]
		// playIndex : int [Read-only.]
		// steps : Array
		// start() : Boolean
		// stop() : Boolean
		// pause() : Boolean
		// resume() : Boolean
		// skipTo(index:Number) : Boolean
		
		/**
		 * Returns the currently-playing SequenceStep.
		 * @return The currently-playing SequenceStep.
		 * @see #getStepAt()
		 * @see #getStepByID()
		 * @see #steps
		 * @see #lastStep
		 */
		public function get currentStep() : SequenceStep {
			return (super._getCurrentStep());
		}
		
		/**
		 * Returns the final SequenceStep in the current sequence.
		 * @return The final SequenceStep in the current sequence.
		 * @see #getStepAt()
		 * @see #getStepByID()
		 * @see #steps
		 * @see #currentStep
		 */
		public function get lastStep() : SequenceStep {
			return (super._getLastStep());
		}
		
		// -== Public Methods ==-
		
		/**
		 * Constructor.
		 *
		 * @param items		Any number of IPlayable instances (e.g. LinearGo,  
PlayableGroup,
		 * 					SequenceStep) as separate arguments, or a single array of them.
		 */
		public function Sequence(...items) {
			super((items[ 0 ] is Array) ? items[ 0 ] : items);
		}
		
		/**
		 * Retrieves any SequenceStep from the steps array.
		 * @param index		An array index starting at 0.
		 * @return			The SequenceStep instance at this index.
		 * @see #getStepByID()
		 * @see #currentStep
		 * @see #lastStep
		 */
		public function getStepAt(index:int) : SequenceStep {
			return (super._getStepAt(index) as SequenceStep);
		}
		
		/**
		 * Locates a step with the specified playableID. To search within a step  
for a
		 * child by playableID, use the step instance's <code>getChildByID</code>  
method.
		 *
		 * @param playableID	The step instance's playableID to search for.
		 * @return				The SequenceStep with the matching playableID.
		 * @see #getStepAt()
		 */
		public function getStepByID(playableID:*) : SequenceStep {
			return (super._getStepByID(playableID) as SequenceStep);
		}
		
		/**
		 * Adds a single SequenceStep or IPlayable instance (LinearGo,  
PlayableGroup,
		 * etc.) to the end of the steps array, or optionally adds the instance  
into
		 * the last SequenceStep instead of adding it as a new step. Calling this
		 * method stops any sequence play currently in progress.
		 *
		 * <p>To remove a step use the <code>removeStepAt</code> method.</p>
		 *
		 * @param item			The playable item to add to the sequence. Note
		 * 						that when new steps are added, any IPlayable
		 * 						instance of a type other than SequenceStep is
		 * 						automatically wrapped in a new SequenceStep.
		 * 						
		 * @param addToLastStep	If true is passed the item is added to the last
		 * 						existing SequenceStep in the steps array. This
		 * 						option should be used with individual items that
		 * 						you want added as children to the SequenceStep.
		 * 						If there are no steps yet this option ignored and
		 * 						a new step is created.
		 * 						
		 * @return Item added.
		 */
		public function addStep(item:IPlayable, addToLastStep:Boolean=false):  
IPlayable {
			return (super._addStep(item, addToLastStep, SequenceStep));
		}
		
		/**
		 * Adds a single SequenceStep or IPlayable instance (LinearGo,  
PlayableGroup,
		 * etc.) at a specific index in the steps array. Calling this method
		 * stops any sequence play currently in progress.
		 *
		 * @param item		The playable item to splice into the sequence.
		 *
		 * @param index		Position in the array starting at 0, or a negative
		 * 					index like Array.splice.
		 * 					
		 * @return 			Item added.
		 */
		public function addStepAt(item:IPlayable, index:int): IPlayable {
			return (super._addStepAt(index, item, SequenceStep));
		}

		/**
		 * Removes and returns the SequenceStep at a specific index from the steps
		 * array. Calling this method stops any sequence play currently in  
progress.
		 *
		 * @param index		Position in the array starting at 0, or a negative
		 * 					index like Array.splice.
		 * 					
		 * @return 			The SequenceStep instance removed from the steps array.
		 */
		public function removeStepAt(index:int): SequenceStep {
			return (super._removeStepAt(index) as SequenceStep);
		}
		
		// Also in super:
		// length : uint   [Read-only.]
		// playIndex : int [Read-only.]
		// steps : Array
		// start() : Boolean
		// stop() : Boolean
		// pause() : Boolean
		// resume() : Boolean
		// skipTo(index:Number) : Boolean
	}
}
\ No newline at end of file

Modified: branches/goasap0.5.2/src_go/org/goasap/utils/SequenceBase.as
==============================================================================
--- branches/goasap0.5.2/src_go/org/goasap/utils/SequenceBase.as	(original)
+++ branches/goasap0.5.2/src_go/org/goasap/utils/SequenceBase.as	Sun Mar 15  
17:46:33 2009
@@ -1 +1 @@
-/**
  * Copyright (c) 2007 Moses Gunesch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a  
copy
  * of this software and associated documentation files (the "Software"), to  
deal
  * in the Software without restriction, including without limitation the  
rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included  
in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  
OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  
THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  
FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
package org.goasap.utils {
	import flash.events.Event;
	import flash.utils.getQualifiedClassName;
	
	import org.goasap.PlayStates;
	import org.goasap.PlayableBase;
	import org.goasap.errors.InstanceNotAllowedError;
	import org.goasap.events.GoEvent;
	import org.goasap.events.SequenceEvent;
	import org.goasap.interfaces.IPlayable;
	import org.goasap.managers.Repeater;	

	/**
	 * Dispatched when the sequence starts.
	 * @eventType org.goasap.events.START
	 */
	[Event(name="START", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when the sequence advances to its next step.
	 * @eventType org.goasap.events.SequenceEvent.ADVANCE
	 */
	[Event(name="ADVANCE", type="org.goasap.events.SequenceEvent")]

	/**
	 * Dispatched when the sequence is paused successfully.
	 * @eventType org.goasap.events.PAUSE
	 */
	[Event(name="PAUSE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when the sequence is resumed successfully.
	 * @eventType org.goasap.events.RESUME
	 */
	[Event(name="RESUME", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched at the end the group if <code>repeater.cycles</code> is set  
to
	 * a value other than one, just before the sequence starts its next play  
cycle.
	 * @eventType org.goasap.events.CYCLE
	 */
	[Event(name="CYCLE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when the sequence is manually stopped, which may also occur
	 * if one of its step instances is manually stopped outside the sequence.
	 * @eventType org.goasap.events.STOP
	 */
	[Event(name="STOP", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when the sequence successfully finishes. (In SequenceCA this  
event
	 * is not fired until all custom-advanced steps have dispatched STOP or  
COMPLETE.)
	 * @eventType org.goasap.events.COMPLETE
	 */
	[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]

	/**
	 * This base class should not be used directly, use it to build sequencing  
classes.
	 *
	 * <p>When subclassing, follow the instructions in the comments of the  
protected
	 * methods to add a standard set of public getters and methods that work  
with the
	 * specific datatype of your SequenceStep subclass, if you create one.  
(This system
	 * is designed to work around the restrictiveness of overrides in AS3  
which don't
	 * allow you to redefine datatypes.) See Sequence and SequenceCA for  
examples.</p>
	 *
	 * @see Sequence
	 * @see SequenceCA
	 *
	 * @author Moses Gunesch
	 */
	public class SequenceBase extends PlayableBase implements IPlayable {
		
		// -== Public Properties ==-
		
		/**
		 * The number of steps in the sequence.
		 */
		public function get length(): int {
			return (_steps ? _steps.length : 0);
		}
		
		/**
		 * The current play index of the sequence, starting a 0.
		 */
		public function get playIndex(): int {
			return _index;
		}

		/**
		 * Get or set the list of SequenceStep instances that defines the  
sequence.
		 *
		 * <p>
		 * When setting this property, each item must implement IPlayable that  
uses
		 * PlayState constants and dispatches STOP or COMPLETE when finished.
		 * Each item is automatically wrapped in a SequenceStep if it is of any  
other IPlayable
		 * type, such as a GoItem or PlayableGroup. Setting this property stops  
any sequence
		 * play currently in progress.
		 * </p>
		 * @see #_getStepAt()
		 * @see #_getStepByID()
		 * @see #_getCurrentStep()
		 * @see #_getLastStep()
		 */
		public function get steps():Array {
			return _steps;
		}
		public function set steps(a:Array):void {
			if (_state!=PlayStates.STOPPED)
				stop();
			
			while (_steps.length > 0)
				_removeStepAt(_steps.length-1);
			
			for each (var item:Object in a)
				if (item is IPlayable)
					_addStep(item as IPlayable);
		}
		
		/**
		 * The sequence's Repeater instance, which may be used to make
		 * the sequence loop and play more than one time.
		 *
		 * <p>The Repeater's cycles property can be set to an integer, or
		 * to Repeater.INFINITE or 0 to repeat indefinitely.</p>
		 *
		 * <pre>var seq:Sequence = new Sequence(tween1, tween2, tween3);
		 * seq.repeater.cycles = 2;
		 * seq.start();
		 * trace(seq.repeater.currentCycle); // output: 0
		 *
		 * seq.skipTo(4); // moves to 2nd action in 2nd cycle
		 * trace(seq.repeater.currentCycle); // output: 1</pre>
		 *
		 * <p>(The repeater property replaces the repeatCount and currentCount
		 * parameters in earlier releases of SequenceBase).</p>
		 */
		public function get repeater(): Repeater {
			return _repeater;
		}
		
		
		// -== Protected Properties ==-
		
		/** @private */
		protected var _index: int = 0;
		
		/** @private */
		protected var _steps: Array;
		
		/** @private */
		protected var _repeater: Repeater;
		
		
		// -== Public Methods ==-
		
		/**
		 * Constructor.
		 *
		 * @param items		Any number of IPlayable instances (e.g. LinearGo,  
PlayableGroup,
		 * 					SequenceStep) as separate arguments, or a single array of them.
		 */
		public function SequenceBase(...items) {
			super();
			var className:String = getQualifiedClassName(this);
			if (className.slice(className.lastIndexOf("::")+2) == "SequenceBase") {
				throw new InstanceNotAllowedError("SequenceBase");
			}
			_steps = new Array();
			if (items.length > 0) {
				steps = ((items[ 0 ] is Array) ? items[ 0 ] : items);
			}
			_repeater = new Repeater();
			_repeater.setParent(this);
		}
		
		// -== IPlayable implementation ==-
		
		/**
		 * Begins a sequence.
		 *
		 * <p>If the group is active when this method is called, a  
<code>stop</code> call
		 * is automated which will result in a GoEvent.STOP event being  
dispatched.</p>
		 *
		 * @return Returns true unless there are no steps in the sequence.
		 */
		public function start() : Boolean {
			if (_steps.length==0)
				return false;
			stop();
			_state = PlayStates.PLAYING;
			_getCurrentStep().start();
			dispatchEvent(new GoEvent( GoEvent.START ));
			_playRetainer[ this ] = 1; // Developers - Important! Look up  
_playRetainer.
			return true;
		}

		/**
		 * Stops all activity and dispatches a GoEvent.STOP event.
		 *
		 * @return Returns true unless sequence was already stopped.
		 */
		public function stop() : Boolean {
			if (_state==PlayStates.STOPPED || _steps.length==0)
				return false;
			_state = PlayStates.STOPPED;
			var stepState:String = _getCurrentStep().state; // TODO: this won't see  
the _trailingSteps state in SequenceCA
			_getCurrentStep().stop();
			if (_steps.length-_index > 1 || stepState!=PlayStates.STOPPED)
				dispatchEvent(new GoEvent( GoEvent.STOP ));
			else
				dispatchEvent(new GoEvent( GoEvent.COMPLETE ));
			_index = 0;
			_repeater.reset();
			delete _playRetainer[ this ]; // Developers - Important! Look up  
_playRetainer.
			return true;
		}
		
		/**
		 * Pauses sequence play.
		 *
		 * @return  Returns true unless sequence was unable to pause any children.
		 */
		public function pause() : Boolean {
			var prevState:String = _state;
			if (_state==PlayStates.STOPPED || _state==PlayStates.PAUSED)
				return false;
			_state = PlayStates.PAUSED;
			if (_getCurrentStep().pause()==false) {
				_state = prevState;
				return false;
			}
			dispatchEvent(new GoEvent( GoEvent.PAUSE ));
			return true;
		}
		
		/**
		 * Resumes previously-paused sequence play.
		 *
		 * @return  Returns true unless sequence was unable to resume any  
children.
		 */
		public function resume() : Boolean {
			if (_state != PlayStates.PAUSED || _getCurrentStep().resume()==false) {
				return false;
			}
			_state = PlayStates.PLAYING;
			dispatchEvent(new GoEvent( GoEvent.RESUME));
			return true;
		}
		
		/**
		 * Stops the current step and skips to another step by sequence index.
		 *
		 * @return Always returns true since the index is normalized to the  
sequence.
		 */
		public function skipTo(index : Number) : Boolean {
			if (_steps.length==0)
				return false;
			_state = PlayStates.PLAYING;
			var prevIndex:int = _index;
			_index = _repeater.skipTo(_steps.length-1, index);
			if (_index==prevIndex) {
				(_getCurrentStep() as IPlayable).skipTo(0);
			}
			else {
				_steps[prevIndex].stop();  // _index is updated before this call so  
that onStepEvent ignores the item's STOP event.
				_getCurrentStep().start();
			}
			return true;
		}
		
		// -== Add hooks for these methods to your subclass like Sequence &  
SequenceCA ==-
		// These methods are broken out to allow subclasses to use exact typing  
for their SequenceStep class.
		
		/**
		 * Developers: Add a getter called <code>currentStep</code> to your  
subclass as in Sequence.
		 *
		 * @return Developers: return the correct SequenceStep type for your  
subclass in your corresponding public method.
		 */
		protected function _getCurrentStep() : * {
			return (_steps.length==0 ? null : _steps[_index]);
		}
		
		/**
		 * Developers: Add a getter called <code>lastStep</code> to your subclass  
as in Sequence.
		 *
		 * @return Developers: return the correct SequenceStep type for your  
subclass in your corresponding public method.
		 */
		protected function _getLastStep() : * {
			return (_steps.length==0 ? null : _steps[ _steps.length-1 ]);
		}
		/**
		 * Developers: Add a method called <code>getStepAt</code> to your  
subclass as in Sequence.
		 *
		 * @param index	An array index starting at 0.
		 * @return		Developers: return the correct SequenceStep type for your  
subclass in your corresponding public method.
		 */
		protected function _getStepAt(index:int) : * {
			if (index >= _steps.length)
				return null;
			return (_steps[index] as SequenceStep);
		}
		
		/**
		 * Developers: Add a method called <code>getStepByID</code> to your  
subclass as in Sequence.
		 *
		 * @param playableID	The step instance's playableID to search for.
		 * @return				 Developers: return the correct SequenceStep type for your  
subclass in your corresponding public method.
		 */
		protected function _getStepByID(playableID:*) : * {
			for each (var step:SequenceStep in _steps)
				if (step.playableID===playableID)
					return step;
			return null;
		}
		
		/**
		 * Developers: Add a method called <code>addStep</code> to your subclass  
as in Sequence.
		 *
		 * <p>Drop the third parameter in your subclass' addStep method. Use it  
to be sure
		 * the correct type of wrapper is created, as in SequenceCA.</p>
		 *
		 * @param item			The playable item to add to the sequence.
		 * 						
		 * @param addToLastStep	If true is passed the item is added to the last
		 * 						existing SequenceStep in the steps array. This
		 * 						option should be used with individual items that
		 * 						you want added as children to the SequenceStep.
		 * 						If there are no steps yet this option ignored and
		 * 						a new step is created.
		 * 						
		 * @param stepTypeAsClass	Type for SequenceSteps. (Do not include this  
parameter in subclass addStep method.)
		 * 			
		 * @return New length of the steps array.
		 */
		protected function _addStep(item:IPlayable, addToLastStep:Boolean=false,  
stepTypeAsClass:*=null): int {
			if (item is SequenceStep && !addToLastStep) {
				return _addStepAt(_steps.length, item);
			}
			if (!stepTypeAsClass)
				stepTypeAsClass = SequenceStep;
			var step:SequenceStep = (addToLastStep && _steps.length > 0
									 ? (_steps.pop() as SequenceStep)
									 : new stepTypeAsClass() as SequenceStep);
			step.addChild(item);
			return _addStepAt(_steps.length, step, stepTypeAsClass); // adds  
listeners
		}
		
		/**
		 * Developers: Add a method called <code>addStep</code> to your subclass  
as in Sequence.
		 *
		 * <p>Drop the third parameter in your subclass' addStep method. Use it  
to be sure
		 * the correct type of wrapper is created, as in SequenceCA.</p>
		 *
		
		 * @param index			Position in the array starting at 0, or a negative
		 * 						index like Array.splice.
		 * 					
		 * @param item			The playable item to splice into the sequence.
		 * 						
		 * @param stepTypeAsClass	Type for SequenceSteps. (Do not include this  
parameter in subclass addStep method.)
		 *
		 * @return 				New length of the steps array.
		 */
		protected function _addStepAt(index:int, item:IPlayable,  
stepTypeAsClass:*=null): int {
			if (_state!=PlayStates.STOPPED)
				stop();
			if (!stepTypeAsClass)
				stepTypeAsClass = SequenceStep;
			var step:SequenceStep = (item is SequenceStep
									 ? item as SequenceStep
									 : new stepTypeAsClass(item) as SequenceStep);
			step.addEventListener(SequenceEvent.ADVANCE, onStepEvent, false, 0,  
true);
			step.addEventListener(GoEvent.STOP, onStepEvent, false, 0, true);
			_steps.splice(index, 0, step);
			return _steps.length;
		}

		/**
		 * Developers: Add a method called <code>addStep</code> to your subclass  
as in Sequence.
		 *
		 * @param index	Position in the array starting at 0, or a negative
		 * 				index like Array.splice.
		 * 					
		 * @return 		Developers: return the correct SequenceStep type for your  
subclass in your corresponding public method.
		 */
		protected function _removeStepAt(index:int) : * {
			if (_state!=PlayStates.STOPPED)
				stop();
			var step:SequenceStep = _steps.splice(index, 1) as SequenceStep;
			step.removeEventListener(SequenceEvent.ADVANCE, onStepEvent);
			step.removeEventListener(GoEvent.STOP, onStepEvent);
			return step;
		}
		
		// -== Protected Methods ==-
		
		/**
		 * @private
		 * Internal handler for step advance.
		 *
		 * @param event		SequenceEvent dispatched by child item.
		 */
		protected function onStepEvent(event : Event) : void {
			// A stop() call to the sequence results in step dispatching STOP, which  
would recurse here.
			if (_state==PlayStates.STOPPED || event.target!=_steps[_index])
				return;
			
			// Only occurs if the SequenceItem is manually stopped outside of this  
manager.
			if (event.type==GoEvent.STOP) {
				stop();
				return;
			}
			
			// Normal step advance
			if (event.type==SequenceEvent.ADVANCE) {
				if (_steps.length-_index == 1) {
					complete();
				}
				else {
					advance();
				}
			}
		}
		
		/**
		 * @private
		 * Internal handler for group completion.
		 */
		protected function advance() : void {
			if (_steps.length-_index > 1) {
				_index ++; // this changes currentStep value in following code
				_getCurrentStep().start();
			}
			dispatchEvent(new SequenceEvent( SequenceEvent.ADVANCE ));
		}
		
		/**
		 * @private
		 * Internal handler for group completion.
		 */
		protected function complete() : void {
			// order-sensitive
			if (_repeater.next()) {
				dispatchEvent(new GoEvent( GoEvent.CYCLE ));
				_index = 0;
				_getCurrentStep().start();
			}
			else {
				_index = _steps.length - 1;
				stop();
			}
		}
	}
}
\ No newline at end of file
+/**
  * Copyright (c) 2007 Moses Gunesch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a  
copy
  * of this software and associated documentation files (the "Software"), to  
deal
  * in the Software without restriction, including without limitation the  
rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included  
in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  
OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  
THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  
FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
package org.goasap.utils {
	import flash.events.Event;
	import flash.utils.getQualifiedClassName;
	
	import org.goasap.PlayStates;
	import org.goasap.PlayableBase;
	import org.goasap.errors.InstanceNotAllowedError;
	import org.goasap.events.GoEvent;
	import org.goasap.events.SequenceEvent;
	import org.goasap.interfaces.IPlayable;
	import org.goasap.managers.Repeater;	

	/**
	 * Dispatched when the sequence starts.
	 * @eventType org.goasap.events.START
	 */
	[Event(name="START", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when the sequence advances to its next step.
	 * @eventType org.goasap.events.SequenceEvent.ADVANCE
	 */
	[Event(name="ADVANCE", type="org.goasap.events.SequenceEvent")]

	/**
	 * Dispatched when the sequence is paused successfully.
	 * @eventType org.goasap.events.PAUSE
	 */
	[Event(name="PAUSE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when the sequence is resumed successfully.
	 * @eventType org.goasap.events.RESUME
	 */
	[Event(name="RESUME", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched at the end the group if <code>repeater.cycles</code> is set  
to
	 * a value other than one, just before the sequence starts its next play  
cycle.
	 * @eventType org.goasap.events.CYCLE
	 */
	[Event(name="CYCLE", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when the sequence is manually stopped, which may also occur
	 * if one of its step instances is manually stopped outside the sequence.
	 * @eventType org.goasap.events.STOP
	 */
	[Event(name="STOP", type="org.goasap.events.GoEvent")]

	/**
	 * Dispatched when the sequence successfully finishes. (In SequenceCA this  
event
	 * is not fired until all custom-advanced steps have dispatched STOP or  
COMPLETE.)
	 * @eventType org.goasap.events.COMPLETE
	 */
	[Event(name="COMPLETE", type="org.goasap.events.GoEvent")]

	/**
	 * This base class should not be used directly, use it to build sequencing  
classes.
	 *
	 * <p>When subclassing, follow the instructions in the comments of the  
protected
	 * methods to add a standard set of public getters and methods that work  
with the
	 * specific datatype of your SequenceStep subclass, if you create one.  
(This system
	 * is designed to work around the restrictiveness of overrides in AS3  
which don't
	 * allow you to redefine datatypes.) See Sequence and SequenceCA for  
examples.</p>
	 *
	 * @see Sequence
	 * @see SequenceCA
	 *
	 * @author Moses Gunesch
	 */
	public class SequenceBase extends PlayableBase implements IPlayable {
		
		// -== Public Properties ==-
		
		/**
		 * The number of steps in the sequence.
		 */
		public function get length(): int {
			return (_steps ? _steps.length : 0);
		}
		
		/**
		 * The current play index of the sequence, starting a 0.
		 */
		public function get playIndex(): int {
			return _index;
		}

		/**
		 * Get or set the list of SequenceStep instances that defines the  
sequence.
		 *
		 * <p>
		 * When setting this property, each item must implement IPlayable that  
uses
		 * PlayState constants and dispatches STOP or COMPLETE when finished.
		 * Each item is automatically wrapped in a SequenceStep if it is of any  
other IPlayable
		 * type, such as a GoItem or PlayableGroup. Setting this property stops  
any sequence
		 * play currently in progress.
		 * </p>
		 * @see #_getStepAt()
		 * @see #_getStepByID()
		 * @see #_getCurrentStep()
		 * @see #_getLastStep()
		 */
		public function get steps():Array {
			return _steps;
		}
		public function set steps(a:Array):void {
			if (_state!=PlayStates.STOPPED)
				stop();
			
			while (_steps.length > 0)
				_removeStepAt(_steps.length-1);
			
			for each (var item:Object in a)
				if (item is IPlayable)
					_addStep(item as IPlayable);
		}
		
		/**
		 * The sequence's Repeater instance, which may be used to make
		 * the sequence loop and play more than one time.
		 *
		 * <p>The Repeater's cycles property can be set to an integer, or
		 * to Repeater.INFINITE or 0 to repeat indefinitely.</p>
		 *
		 * <pre>var seq:Sequence = new Sequence(tween1, tween2, tween3);
		 * seq.repeater.cycles = 2;
		 * seq.start();
		 * trace(seq.repeater.currentCycle); // output: 0
		 *
		 * seq.skipTo(4); // moves to 2nd action in 2nd cycle
		 * trace(seq.repeater.currentCycle); // output: 1</pre>
		 *
		 * <p>(The repeater property replaces the repeatCount and currentCount
		 * parameters in earlier releases of SequenceBase).</p>
		 */
		public function get repeater(): Repeater {
			return _repeater;
		}
		
		
		// -== Protected Properties ==-
		
		/** @private */
		protected var _index: int = 0;
		
		/** @private */
		protected var _steps: Array;
		
		/** @private */
		protected var _repeater: Repeater;
		
		
		// -== Public Methods ==-
		
		/**
		 * Constructor.
		 *
		 * @param items		Any number of IPlayable instances (e.g. LinearGo,  
PlayableGroup,
		 * 					SequenceStep) as separate arguments, or a single array of them.
		 */
		public function SequenceBase(...items) {
			super();
			var className:String = getQualifiedClassName(this);
			if (className.slice(className.lastIndexOf("::")+2) == "SequenceBase") {
				throw new InstanceNotAllowedError("SequenceBase");
			}
			_steps = new Array();
			if (items.length > 0) {
				steps = ((items[ 0 ] is Array) ? items[ 0 ] : items);
			}
			_repeater = new Repeater();
			_repeater.setParent(this);
		}
		
		// -== IPlayable implementation ==-
		
		/**
		 * Begins a sequence.
		 *
		 * <p>If the group is active when this method is called, a  
<code>stop</code> call
		 * is automated which will result in a GoEvent.STOP event being  
dispatched.</p>
		 *
		 * @return Returns true unless there are no steps in the sequence.
		 */
		public function start() : Boolean {
			if (_steps.length==0)
				return false;
			stop();
			_state = PlayStates.PLAYING;
			_getCurrentStep().start();
			dispatchEvent(new GoEvent( GoEvent.START ));
			_playRetainer[ this ] = 1; // Developers - Important! Look up  
_playRetainer.
			return true;
		}

		/**
		 * Stops all activity and dispatches a GoEvent.STOP event.
		 *
		 * @return Returns true unless sequence was already stopped.
		 */
		public function stop() : Boolean {
			if (_state==PlayStates.STOPPED || _steps.length==0)
				return false;
			_state = PlayStates.STOPPED;
			var stepState:String = _getCurrentStep().state; // TODO: this won't see  
the _trailingSteps state in SequenceCA
			_getCurrentStep().stop();
			if (_steps.length-_index > 1 || stepState!=PlayStates.STOPPED)
				dispatchEvent(new GoEvent( GoEvent.STOP ));
			else
				dispatchEvent(new GoEvent( GoEvent.COMPLETE ));
			_index = 0;
			_repeater.reset();
			delete _playRetainer[ this ]; // Developers - Important! Look up  
_playRetainer.
			return true;
		}
		
		/**
		 * Pauses sequence play.
		 *
		 * @return  Returns true unless sequence was unable to pause any children.
		 */
		public function pause() : Boolean {
			var prevState:String = _state;
			if (_state==PlayStates.STOPPED || _state==PlayStates.PAUSED)
				return false;
			_state = PlayStates.PAUSED;
			if (_getCurrentStep().pause()==false) {
				_state = prevState;
				return false;
			}
			dispatchEvent(new GoEvent( GoEvent.PAUSE ));
			return true;
		}
		
		/**
		 * Resumes previously-paused sequence play.
		 *
		 * @return  Returns true unless sequence was unable to resume any  
children.
		 */
		public function resume() : Boolean {
			if (_state != PlayStates.PAUSED || _getCurrentStep().resume()==false) {
				return false;
			}
			_state = PlayStates.PLAYING;
			dispatchEvent(new GoEvent( GoEvent.RESUME));
			return true;
		}
		
		/**
		 * Stops the current step and skips to another step by sequence index.
		 *
		 * @return Always returns true since the index is normalized to the  
sequence.
		 */
		public function skipTo(index : Number) : Boolean {
			if (_steps.length==0)
				return false;
			_state = PlayStates.PLAYING;
			var prevIndex:int = _index;
			_index = _repeater.skipTo(_steps.length-1, index);
			if (_index==prevIndex) {
				(_getCurrentStep() as IPlayable).skipTo(0);
			}
			else {
				_steps[prevIndex].stop();  // _index is updated before this call so  
that onStepEvent ignores the item's STOP event.
				_getCurrentStep().start();
			}
			return true;
		}
		
		// -== Add hooks for these methods to your subclass like Sequence &  
SequenceCA ==-
		// These methods are broken out to allow subclasses to use exact typing  
for their SequenceStep class.
		
		/**
		 * Developers: Add a getter called <code>currentStep</code> to your  
subclass as in Sequence.
		 *
		 * @return Developers: return the correct SequenceStep type for your  
subclass in your corresponding public method.
		 */
		protected function _getCurrentStep() : SequenceStep {
			return (_steps.length==0 ? null : _steps[_index]);
		}
		
		/**
		 * Developers: Add a getter called <code>lastStep</code> to your subclass  
as in Sequence.
		 *
		 * @return Developers: return the correct SequenceStep type for your  
subclass in your corresponding public method.
		 */
		protected function _getLastStep() : SequenceStep {
			return (_steps.length==0 ? null : _steps[ _steps.length-1 ]);
		}
		/**
		 * Developers: Add a method called <code>getStepAt</code> to your  
subclass as in Sequence.
		 *
		 * @param index	An array index starting at 0.
		 * @return		Developers: return the correct SequenceStep type for your  
subclass in your corresponding public method.
		 */
		protected function _getStepAt(index:int) : SequenceStep {
			if (index >= _steps.length)
				return null;
			return (_steps[index] as SequenceStep);
		}
		
		/**
		 * Developers: Add a method called <code>getStepByID</code> to your  
subclass as in Sequence.
		 *
		 * @param playableID	The step instance's playableID to search for.
		 * @return				 Developers: return the correct SequenceStep type for your  
subclass in your corresponding public method.
		 */
		protected function _getStepByID(playableID:*) : SequenceStep {
			for each (var step:SequenceStep in _steps)
				if (step.playableID===playableID)
					return step;
			return null;
		}
		
		/**
		 * Developers: Add a method called <code>addStep</code> to your subclass  
as in Sequence.
		 *
		 * <p>Drop the third parameter in your subclass' addStep method. Use it  
to be sure
		 * the correct type of wrapper is created, as in SequenceCA.</p>
		 *
		 * @param item			The playable item to add to the sequence.
		 * 						
		 * @param addToLastStep	If true is passed the item is added to the last
		 * 						existing SequenceStep in the steps array. This
		 * 						option should be used with individual items that
		 * 						you want added as children to the SequenceStep.
		 * 						If there are no steps yet this option ignored and
		 * 						a new step is created.
		 * 						
		 * @param stepTypeAsClass	Type for SequenceSteps. (Do not include this  
parameter in subclass addStep method.)
		 * 			
		 * @return Item added.
		 */
		protected function _addStep(item:IPlayable, addToLastStep:Boolean=false,  
stepTypeAsClass:*=null): IPlayable {
			if (item is SequenceStep && !addToLastStep) {
				_addStepAt(_steps.length, item);
				return item;
			}
			if (!stepTypeAsClass)
				stepTypeAsClass = SequenceStep;
			var step:SequenceStep = (addToLastStep && _steps.length > 0
									 ? (_steps.pop() as SequenceStep)
									 : new stepTypeAsClass() as SequenceStep);
			step.addChild(item);
			_addStepAt(_steps.length, step, stepTypeAsClass); // adds listeners
			return item;
		}
		
		/**
		 * Developers: Add a method called <code>addStep</code> to your subclass  
as in Sequence.
		 *
		 * <p>Drop the third parameter in your subclass' addStep method. Use it  
to be sure
		 * the correct type of wrapper is created, as in SequenceCA.</p>
		 *
		
		 * @param index			Position in the array starting at 0, or a negative
		 * 						index like Array.splice.
		 * 					
		 * @param item			The playable item to splice into the sequence.
		 * 						
		 * @param stepTypeAsClass	Type for SequenceSteps. (Do not include this  
parameter in subclass addStep method.)
		 *
		 * @return 				Item added.
		 */
		protected function _addStepAt(index:int, item:IPlayable,  
stepTypeAsClass:*=null): IPlayable {
			if (_state!=PlayStates.STOPPED)
				stop();
			if (!stepTypeAsClass)
				stepTypeAsClass = SequenceStep;
			var step:SequenceStep = (item is SequenceStep
									 ? item as SequenceStep
									 : new stepTypeAsClass(item) as SequenceStep);
			if (step==null)
				return null;
			step.addEventListener(SequenceEvent.ADVANCE, onStepEvent, false, 0,  
true);
			step.addEventListener(GoEvent.STOP, onStepEvent, false, 0, true);
			_steps.splice(Math.min(index, _steps.length), 0, step);
			return item;
		}

		/**
		 * Developers: Add a method called <code>addStep</code> to your subclass  
as in Sequence.
		 *
		 * @param index	Position in the array starting at 0, or a negative
		 * 				index like Array.splice.
		 * 					
		 * @return 		Developers: return the correct SequenceStep type for your  
subclass in your corresponding public method.
		 */
		protected function _removeStepAt(index:int) : SequenceStep {
			if (_state!=PlayStates.STOPPED)
				stop();
			var step:SequenceStep = _steps.splice(index, 1) as SequenceStep;
			step.removeEventListener(SequenceEvent.ADVANCE, onStepEvent);
			step.removeEventListener(GoEvent.STOP, onStepEvent);
			return step;
		}
		
		// -== Protected Methods ==-
		
		/**
		 * @private
		 * Internal handler for step advance.
		 *
		 * @param event		SequenceEvent dispatched by child item.
		 */
		protected function onStepEvent(event : Event) : void {
			// A stop() call to the sequence results in step dispatching STOP, which  
would recurse here.
			if (_state==PlayStates.STOPPED || event.target!=_steps[_index])
				return;
			
			// Only occurs if the SequenceItem is manually stopped outside of this  
manager.
			if (event.type==GoEvent.STOP) {
				stop();
				return;
			}
			
			// Normal step advance
			if (event.type==SequenceEvent.ADVANCE) {
				if (_steps.length-_index == 1) {
					complete();
				}
				else {
					advance();
				}
			}
		}
		
		/**
		 * @private
		 * Internal handler for group completion.
		 */
		protected function advance() : void {
			if (_steps.length-_index > 1) {
				_index ++; // this changes currentStep value in following code
				_getCurrentStep().start();
			}
			dispatchEvent(new SequenceEvent( SequenceEvent.ADVANCE ));
		}
		
		/**
		 * @private
		 * Internal handler for group completion.
		 */
		protected function complete() : void {
			// order-sensitive
			if (_repeater.next()) {
				dispatchEvent(new GoEvent( GoEvent.CYCLE ));
				_index = 0;
				_getCurrentStep().start();
			}
			else {
				_index = _steps.length - 1;
				stop();
			}
		}
	}
}
\ No newline at end of file

Modified: branches/goasap0.5.2/src_go/org/goasap/utils/SequenceCA.as
==============================================================================
--- branches/goasap0.5.2/src_go/org/goasap/utils/SequenceCA.as	(original)
+++ branches/goasap0.5.2/src_go/org/goasap/utils/SequenceCA.as	Sun Mar 15  
17:46:33 2009
@@ -1 +1 @@
-/**
  * Copyright (c) 2007 Moses Gunesch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a  
copy
  * of this software and associated documentation files (the "Software"), to  
deal
  * in the Software without restriction, including without limitation the  
rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included  
in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  
OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  
THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  
FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
package org.goasap.utils {
	import flash.events.Event;
	
	import org.goasap.PlayStates;
	import org.goasap.events.GoEvent;
	import org.goasap.events.SequenceEvent;
	import org.goasap.interfaces.IPlayable;	

	/**
	 * Sequence with "Custom Advance" options, in which steps can specify when  
they should advance.
	 *
	 * <p>This class works like Sequence but uses the special class  
SequenceStepCA for its steps.
	 * SequenceStepCA has a property called <code>advance</code>. When steps  
advance before animation
	 * finishes, the trailing steps are tracked so that the SequenceCA doesn't  
dispatch its COMPLETE
	 * event until all activity has completed.</p>
	 *
	 * <p>Any step's advance property can be set to an instance of  
OnDurationComplete, OnPlayableComplete,
	 * OnEventComplete or OnConditionTrue. Each of those classes defines its  
own parameters and rules for
	 * when the advance occurs. For example, using OnPlayableComplete a  
sequence can advance after one
	 * particular item in the step finishes, without needing to wait for all  
the other ones in that group
	 * to complete.</p>
	 *
	 * <p>Additionally, you can create your own custom advance types by  
subclassing the SequenceAdvance
	 * base class.</p>
	 *
	 * @see SequenceStepCA
	 * @see org.goasap.utils.customadvance.OnConditionTrue OnConditionTrue
	 * @see org.goasap.utils.customadvance.OnDurationComplete  
OnDurationComplete
	 * @see org.goasap.utils.customadvance.OnEventComplete OnEventComplete
	 * @see org.goasap.utils.customadvance.OnPlayableComplete  
OnPlayableComplete
	 * @see org.goasap.utils.customadvance.SequenceAdvance SequenceAdvance
	 * @see Sequence
	 * @see SequenceBase
	 *
	 * @author Moses Gunesch
	 */
	public class SequenceCA extends SequenceBase {
		
		// -== Public Properties ==-
		
		// Also in super:
		// length : uint   [Read-only.]
		// playIndex : int [Read-only.]
		// steps : Array
		// start() : Boolean
		// stop() : Boolean
		// pause() : Boolean
		// resume() : Boolean
		// skipTo(index:Number) : Boolean
		
		/**
		 * Returns the currently-playing SequenceStepCA.
		 * @return The currently-playing SequenceStepCA.
		 * @see #getStepAt()
		 * @see #getStepByID()
		 * @see #steps
		 * @see #lastStep
		 */
		public function get currentStep() : SequenceStepCA {
			return (super._getCurrentStep());
		}
		
		/**
		 * Returns the final SequenceStepCA in the current sequence.
		 * @return The final SequenceStepCA in the current sequence.
		 * @see #getStepAt()
		 * @see #getStepByID()
		 * @see #steps
		 * @see #currentStep
		 */
		public function get lastStep() : SequenceStepCA {
			return (super._getLastStep());
		}
		
		// -== Protected Properties ==-
		
		/**
		 * @private
		 */
		protected var _trailingSteps : SequenceStep;
		
		// -== Public Methods ==-
		
		/**
		 * Constructor.
		 *
		 * @param items		Any number of IPlayable instances (e.g. LinearGo,  
PlayableGroup,
		 * 					SequenceStepCA) as separate arguments, or a single array of them.
		 */
		public function SequenceCA(...items) {
			super((items[ 0 ] is Array) ? items[ 0 ] : items);
		}
		
		/**
		 * Retrieves any SequenceStepCA from the steps array.
		 * @param index		An array index starting at 0.
		 * @return			The SequenceStepCA instance at this index.
		 * @see #getStepByID()
		 */
		public function getStepAt(index:int) : SequenceStepCA {
			return (super._getStepAt(index) as SequenceStepCA);
		}
		
		/**
		 * Locates a step with the specified playableID. To search within a step  
for a
		 * child by playableID, use the step instance's <code>getChildByID</code>  
method.
		 *
		 * @param playableID	The step instance's playableID to search for.
		 * @return				The SequenceStepCA with the matching playableID.
		 */
		public function getStepByID(playableID:*) : SequenceStepCA {
			return (super._getStepByID(playableID) as SequenceStepCA);
		}
		
		/**
		 * Adds a single IPlayable instance (e.g. LinearGo, PlayableGroup,  
SequenceStepCA)
		 * to the end of the steps array, or optionally adds the instance into  
the last
		 * SequenceStepCA instead of adding it as a new step.
		 *
		 * <p>To remove a step use the <code>removeStepAt</code> method.</p>
		 *
		 * @param item			The playable item to add to the sequence. Note
		 * 						that when new steps are added, any IPlayable
		 * 						instance of a type other than SequenceStepCA is
		 * 						automatically wrapped in a new SequenceStepCA.
		 * 						
		 * @param addToLastStep	If true is passed the item is added to the last
		 * 						existing SequenceStepCA in the steps array. This
		 * 						option should be used with individual items that
		 * 						you want added as children to the SequenceStepCA.
		 * 						If there are no steps yet this option ignored and
		 * 						a new step is created.
		 * 						
		 * @return New length of the steps array.
		 */
		public function addStep(item:IPlayable, addToLastStep:Boolean=false): int  
{
			return (super._addStep(item, addToLastStep, SequenceStepCA));
		}
		
		/**
		 * Adds a single IPlayable instance (e.g. LinearGo, PlayableGroup,  
SequenceStepCA)
		 * at a specific index in the steps array. Calling this method stops any  
sequence
		 * play currently in progress.
		 *
		 * @param item		The playable item to splice into the sequence.
		 *
		 * @param index		Position in the array starting at 0, or a negative
		 * 					index like Array.splice.
		 * 					
		 * @return 			New length of the steps array.
		 */
		public function addStepAt(item:IPlayable, index:int): int {
			return (super._addStepAt(index, item, SequenceStepCA));
		}

		/**
		 * Removes and returns the SequenceStepCA at a specific index from the  
steps
		 * array. Calling this method stops any sequence play currently in  
progress.
		 *
		 * @param index		Position in the array starting at 0, or a negative
		 * 					index like Array.splice.
		 * 					
		 * @return 			The SequenceStepCA instance removed from the steps array.
		 */
		public function removeStepAt(index:int): SequenceStepCA {
			return (super._removeStepAt(index) as SequenceStepCA);
		}
				
		// -== IPlayable implementation ==-

		/**
		 * Begins a sequence.
		 *
		 * <p>If the group is active when this method is called, a  
<code>stop</code> call
		 * is automated which will result in a GoEvent.STOP event being  
dispatched.</p>
		 *
		 * @return Returns true unless there are no steps in the sequence.
		 */
		override public function start() : Boolean {
			return super.start();
		}
		
		/**
		 * Stops all activity and dispatches a GoEvent.STOP event.
		 *
		 * @return Returns true unless sequence was already stopped.
		 */
		override public function stop() : Boolean {
			if (super.stop()==false)
				return false;
			initTrailingSteps(false);
			return true;
		}
		
		/**
		 * Pauses sequence play.
		 *
		 * @return  Returns true unless sequence was unable to pause any children.
		 */
		override public function pause() : Boolean {
			var success:Boolean = super.pause();
			if (_trailingSteps!=null) {
				_trailingSteps.pause();
				if (_trailingSteps.state==PlayStates.PAUSED) {
					_state = PlayStates.PAUSED;
					success = true;
				}
			}
			return success;
		}
		
		/**
		 * Resumes previously-paused sequence play.
		 *
		 * @return  Returns true unless sequence was unable to resume any  
children.
		 */
		override public function resume() : Boolean {
			var success:Boolean = super.resume();
			if (_trailingSteps!=null) {
				_trailingSteps.resume();
				if (_trailingSteps.state==PlayStates.PLAYING) {
					_state = PlayStates.PLAYING;
					success = true;
				}
			}
			return success;
		}
		
		/**
		 * Stops current activity and skips to another step by sequence index.
		 *
		 * @return Always returns true since the index is normalized to the  
sequence.
		 */
		override public function skipTo(index : Number) : Boolean {
			if (_steps.length==0)
				return false;
			initTrailingSteps(false);
			return super.skipTo(index);
		}
		
		// -== Protected Methods ==-
		
		/**
		 * @private
		 * Internal handler for item completion.
		 * @param event		SequenceEvent dispatched by child item.
		 */
		override protected function onStepEvent(event : Event) : void {
			// A stop() call to the sequence results in step dispatching STOP, which  
would recurse here.
			if (_state==PlayStates.STOPPED)
				return;
			// trailing item
			if (_trailingSteps!=null && event.target==_trailingSteps &&  
event.type==SequenceEvent.ADVANCE) {
				initTrailingSteps(false);
				if (_steps.length-_index==1) {
					if (lastStep.state==PlayStates.STOPPED) {
						// A completed sequence was waiting for trailing steps to finish.
						// Otherwise, trailing items have finished before sequence ended so  
no action should be taken.
						complete();
					}
					else {
						// Special case where advance already fired but trailing steps have  
all completed: use COMPLETE
						lastStep.addEventListener(GoEvent.COMPLETE, onStepEvent);
					}
				}
				return;
			}
			
			// Finishes special case in trailing item block. Also, returns out if  
we're waiting
			if (lastStep.hasEventListener(GoEvent.COMPLETE)) {
				if (event.type==GoEvent.COMPLETE) {
					initTrailingSteps(false); // _trailingSteps is null, this is to remove  
the COMPLETE listener.
					complete();
				}
				return;
			}
			
			super.onStepEvent(event);
		}
						
		/**
		 * @private
		 * Internal handler for step advance.
		 */
		override protected function advance() : void {
			if (currentStep.listenerCount > 0) {
				initTrailingSteps(true);
				var isFirstItem:Boolean = (_trailingSteps.children.length==0);
				_trailingSteps.addChild(currentStep, isFirstItem); // 2nd param is  
adoptChildState flag: avoids a start call on the group.
			}
			super.advance();
		}
		
		/**
		 * @private
		 * Internal handler for group completion.
		 */
		override protected function complete() : void {
			if (_trailingSteps==null) {
				super.complete();
			}
		}
		
		/**
		 * @private
		 * Internal setup for tracking items that are continuing to run after a  
custom advance.
		 * @param active	Whether to create or destroy the trailing-steps group.
		 */
		protected function initTrailingSteps(active:Boolean):void {
			if (_trailingSteps==null && active) {
				_trailingSteps = new SequenceStep();
				_trailingSteps.playableID += "(_trailingSteps for  
sequence:"+playableID+")";
				_trailingSteps.addEventListener(SequenceEvent.ADVANCE, onStepEvent);
			}
			else if (!active) {
				lastStep.removeEventListener(GoEvent.COMPLETE, onStepEvent); // Remove  
special case set in onStepEvent.
				if (_trailingSteps!=null) {
					_trailingSteps.removeEventListener(SequenceEvent.ADVANCE, onStepEvent);
					_trailingSteps.stop();
					_trailingSteps = null;
				}
			}
		}
	}
}
\ No newline at end of file
+/**
  * Copyright (c) 2007 Moses Gunesch
  *
  * Permission is hereby granted, free of charge, to any person obtaining a  
copy
  * of this software and associated documentation files (the "Software"), to  
deal
  * in the Software without restriction, including without limitation the  
rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included  
in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  
OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  
THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  
FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
package org.goasap.utils {
	import org.goasap.PlayStates;
	import org.goasap.events.GoEvent;
	import org.goasap.events.SequenceEvent;
	import org.goasap.interfaces.IPlayable;
	
	import flash.events.Event;	

	/**
	 * Sequence with "Custom Advance" options, in which steps can specify when  
they should advance.
	 *
	 * <p>This class works like Sequence but uses the special class  
SequenceStepCA for its steps.
	 * SequenceStepCA has a property called <code>advance</code>. When steps  
advance before animation
	 * finishes, the trailing steps are tracked so that the SequenceCA doesn't  
dispatch its COMPLETE
	 * event until all activity has completed.</p>
	 *
	 * <p>Any step's advance property can be set to an instance of  
OnDurationComplete, OnPlayableComplete,
	 * OnEventComplete or OnConditionTrue. Each of those classes defines its  
own parameters and rules for
	 * when the advance occurs. For example, using OnPlayableComplete a  
sequence can advance after one
	 * particular item in the step finishes, without needing to wait for all  
the other ones in that group
	 * to complete.</p>
	 *
	 * <p>Additionally, you can create your own custom advance types by  
subclassing the SequenceAdvance
	 * base class.</p>
	 *
	 * @see SequenceStepCA
	 * @see org.goasap.utils.customadvance.OnConditionTrue OnConditionTrue
	 * @see org.goasap.utils.customadvance.OnDurationComplete  
OnDurationComplete
	 * @see org.goasap.utils.customadvance.OnEventComplete OnEventComplete
	 * @see org.goasap.utils.customadvance.OnPlayableComplete  
OnPlayableComplete
	 * @see org.goasap.utils.customadvance.SequenceAdvance SequenceAdvance
	 * @see Sequence
	 * @see SequenceBase
	 *
	 * @author Moses Gunesch
	 */
	public class SequenceCA extends SequenceBase {
		
		// -== Public Properties ==-
		
		// Also in super:
		// length : uint   [Read-only.]
		// playIndex : int [Read-only.]
		// steps : Array
		// start() : Boolean
		// stop() : Boolean
		// pause() : Boolean
		// resume() : Boolean
		// skipTo(index:Number) : Boolean
		
		/**
		 * Returns the currently-playing SequenceStepCA.
		 * @return The currently-playing SequenceStepCA.
		 * @see #getStepAt()
		 * @see #getStepByID()
		 * @see #steps
		 * @see #lastStep
		 */
		public function get currentStep() : SequenceStepCA {
			return (super._getCurrentStep() as SequenceStepCA);
		}
		
		/**
		 * Returns the final SequenceStepCA in the current sequence.
		 * @return The final SequenceStepCA in the current sequence.
		 * @see #getStepAt()
		 * @see #getStepByID()
		 * @see #steps
		 * @see #currentStep
		 */
		public function get lastStep() : SequenceStepCA {
			return (super._getLastStep() as SequenceStepCA);
		}
		
		// -== Protected Properties ==-
		
		/**
		 * @private
		 */
		protected var _trailingSteps : SequenceStep;
		
		// -== Public Methods ==-
		
		/**
		 * Constructor.
		 *
		 * @param items		Any number of IPlayable instances (e.g. LinearGo,  
PlayableGroup,
		 * 					SequenceStepCA) as separate arguments, or a single array of them.
		 */
		public function SequenceCA(...items) {
		

==============================================================================
Diff truncated at 200k characters


More information about the GoList mailing list