[Golist] [goasap commit] r41 - in trunk/goasap/src_go/org/goasap: . interfaces items managers
codesite-noreply at google.com
codesite-noreply at google.com
Mon Jun 2 18:18:31 PDT 2008
Author: mosesoak
Date: Mon Jun 2 18:17:54 2008
New Revision: 41
Added:
trunk/goasap/src_go/org/goasap/interfaces/ILiveManager.as
Modified:
trunk/goasap/src_go/org/goasap/GoEngine.as
trunk/goasap/src_go/org/goasap/interfaces/IManager.as
trunk/goasap/src_go/org/goasap/items/GoItem.as
trunk/goasap/src_go/org/goasap/managers/OverlapMonitor.as
Log:
Version 0.4.9 Improves GoASAP's management layer.
1. All managers are now accessed by GoEngine in the order
they are added, so various duties can be carefully ordered.
2. NEW INTERFACE: ILiveManager. GoEngine has been altered
so that any managers that implement this interface receive
a special onUpdate() callback after each update cycle.
3. OverlapMonitor has been rewritten to use Dictionaries
instead of Arrays to store handlers internally, and to
correct an efficiency flaw in release().
GoItem.defaultPulseInterval has been set back to ENTER_FRAME.
While 33ms does perform faster on benchmarks with thousands of
animations, ENTER_FRAME runs much smoother in practical contexts.
Documentation has been improved in IManager describing the manager
layer in a more friendly, tutorial style.
Modified: trunk/goasap/src_go/org/goasap/GoEngine.as
==============================================================================
--- trunk/goasap/src_go/org/goasap/GoEngine.as (original)
+++ trunk/goasap/src_go/org/goasap/GoEngine.as Mon Jun 2 18:17:54 2008
@@ -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;
/**
* Provides <code>update</code> calls to <code>IUpdatable</code> instances on their specified <code>pulseInterval</code>.
*
* <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></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><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>You may modify any class in the goasap package to suit your project's needs.</p>
*
* <p>Go is a community initiative 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.</p>
*
* <p><b>GoEngine</b></p>
*
* <p>GoEngine sits at the center of the Go system, and along with the IUpdatable
* interface is the only required element for using Go. GoEngine references two other
* interfaces for adding system-wide managers, IManager and IManageable.
* All other classes in the go package are merely one suggestion of how a
* system could be structured within Go, and may be considered optional
* elements. To create an API using the provided classes, you simply need
* to extend the item classes LinearGo and PhysicsGo to create animation items.</p>
*
* <p>GoEngine serves two purposes: first, it keeps a large system efficient
* by stacking and running updates on blocks of items. Note that any IUpdatable
* instance may specify its own pulseInterval; items with matching pulses
* are grouped into queues for efficiency. Its second purpose is centralization.
* By using a single hub for pulse-driven items of all types, management classes
* can be attached to GoEngine to run processes across items. This is done voluntarily
* by the end-user with <code>addManager</code>, which keeps management entirely
* compile-optional and extensible.</p>
*
* <p>You normally don't need to modify this class to use Go. While Go items typically
* only use <code>addItem</code> and <code>removeItem</code>, your project's code might
* use GoEngine to register managers, or to pause, resume or stop all Go animation in
* a SWF at once.</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>
*
* @author Moses Gunesch
*/
public class GoEngine
{
// -== Constants ==-
public static const INFO:String = "GoASAP 0.4.8jg1 (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.
private static var managers : Object = new Object(); // registration list of IManager instances
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.
// Without doing this, item groups like sequences often go out of sync because part of the next group gets updated
// before the rest. There may be a better way to do this, so please feel free to experiment. (Test changes w/ TweenBencher!)
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 managers[ 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 (managers[ className ]) {
throw new DuplicateManagerError( className );
return;
}
managers[ className ] = instance;
}
/**
* 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
{
delete managers[ className ];
}
/**
* 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 ];
// 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;
for (var item:* in items) {
if (items[ item ]==pulse && !addQueue[ item ]) {
(item as IUpdatable).update(currentTime);
}
}
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();
}
/**
* 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>.
*
* <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></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><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>You may modify any class in the goasap package to suit your project's needs.</p>
*
* <p>Go is a community initiative 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.</p>
*
* <p><b>GoEngine</b></p>
*
* <p>GoEngine sits at the center of the Go system, and along with the IUpdatable
* interface is the only required element for using Go. GoEngine references two other
* interfaces for adding system-wide managers, IManager and IManageable.
* All other classes in the go package are merely one suggestion of how a
* system could be structured within Go, and may be considered optional
* elements. To create an API using the provided classes, you simply need
* to extend the item classes LinearGo and PhysicsGo to create animation items.</p>
*
* <p>GoEngine serves two purposes: first, it keeps a large system efficient
* by stacking and running updates on blocks of items. Note that any IUpdatable
* instance may specify its own pulseInterval; items with matching pulses
* are grouped into queues for efficiency. Its second purpose is centralization.
* By using a single hub for pulse-driven items of all types, management classes
* can be attached to GoEngine to run processes across items. This is done voluntarily
* by the end-user with <code>addManager()</code>, which keeps management entirely
* compile-optional and extensible. See the documentation for <code>IManager</code>
* to learn more about Go's management architeture.</p>
*
* <p>You normally don't need to modify this class to use Go. While Go items typically
* only use <code>addItem</code> and <code>removeItem</code>, your project's code might
* use GoEngine to register managers, or to pause, resume or stop all Go animation in
* a SWF at once.</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.4.9 (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
Added: trunk/goasap/src_go/org/goasap/interfaces/ILiveManager.as
==============================================================================
--- (empty file)
+++ trunk/goasap/src_go/org/goasap/interfaces/ILiveManager.as Mon Jun 2 18:17:54 2008
@@ -0,0 +1,4 @@
+/**
* Copyright (c) 2008 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.interfaces {
+ import org.goasap.interfaces.IManager;
+
/**
* Instances receive a callback from GoEngine after each update cycle,
* allowing managers to more easily perform ongoing processes during animation.
*
* <p><font color="#CC0000">[This is a more advanced manager interface, so if
* you are just getting started with Go's management system it is suggested that
* you focus on <code>IManager</code> & <code>IManageable</code>, and save this
* section for when you need it.]</font> </p>
*
* <p>Hypothetical examples:</p>
* <ul>
* <li>An updater class that refreshes (rerenders) a 3D scene after all
* animations have processed each pulse.</li>
* <li>A hitTest manager that allows all items to update their positions
* first, then tests for hits between them.</li>
* </ul>
* <p>Each <code>ILiveManager</code> receives a special onUpdate() callback
* after GoEngine completes each pulse cycle for any particular pulseInterval.
* This callback receives three things: the pulseInterval associated with the
* cycle, an array containing the items updated, and the synced current-time value
* that was sent to all the items as update() was called. (Background: GoEngine
* stores different lists for every different pulseInterval specified by animation
* items. Usually users will stick to a single pulseInterval but at times it can
* be beneficial to run some animations slower than others – such as the readouts
* in a spaceship game's cockpit which don't need to refresh as often and can free
* up processing power for the game if they don't.)</p>
*
* <p>The list of updated items only includes items actually updated, which at
* times can differ slightly from the items that have been added to GoEngine and
* sent to the manager's reserve() method. (Background: when items are added to
* GoEngine during its update cycle, it defers updating them until the next pulse
* so as not to disrupt the cycle in progress.) Therefore, even though <code>ILiveManager</code>
* extends <code>IManager</code> and contains reserve() and release() methods,
* those methods are often not needed here, since you can filter and make use of
* the incoming array of updated items on each update. This can also relieve such
* managers from needing to store and manage complex handler lists (as
* <code>OverlapMonitor</code> does).</p>
*
* <p><code>ILiveManager</code> instances registered using <code>GoEngine.addManager()</code>
* are stored in an ordered list. You can control the priority of updates in a
* program by adding certain managers before others.</p>
*
* @see IManager
* @see IManageable
* @see org.goasap.GoEngine#addManager GoEngine.addManager()
*
* @author Moses Gunesch
*/
+ public interface ILiveManager extends IManager
{
/**
* GoEngine pings this function after each update() cycle for each pulse.
*
* @param pulseInterval The pulse interval for this update cycle (-1 is ENTER_FRAME)
* @param handlers The list of handlers actually updated during this cycle
* @param currentTime The clock time that was passed to items during update
*/
function onUpdate(pulseInterval:int, handlers:Array, currentTime : Number):void;
}
}
\ No newline at end of file
Modified: trunk/goasap/src_go/org/goasap/interfaces/IManager.as
==============================================================================
--- trunk/goasap/src_go/org/goasap/interfaces/IManager.as (original)
+++ trunk/goasap/src_go/org/goasap/interfaces/IManager.as Mon Jun 2 18:17:54 2008
@@ -1,3 +1,3 @@
/**
* 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.interfaces {
/**
- * Makes an object compatible with <code>GoEngine.addManager()</code>.
*
* <p>Go items normally have no knowledge of each other. The Go system's
* management layer provides a way to monitor and interact with some or
* all active items at once. It is designed so that users can choose to
* exclude all management from their projects to lighten filesize and
* processor load, or can create and add new managers as needed for any
* project. A simple manager/item contract is defined between this interface
* and the IManageable interface.</p>
*
* <p>The tie-in is simple: GoEngine reports the adding and removal of all
* items that implement the IManageable interface to all registered managers.
* The end-user gets to choose which managers to instantiate and register
* using <code>GoEngine.addManager()</code>. As the engine passes references
* to each item as it is added, managers can store and manipulate these items
* however they like. OverlapMonitor, for example, releases items with
* conflicting target/property combinations as new items are added.</p>
*
* <p>To make sure that the filesize associated with your managers remains
* compile-optional, only interfaces should be used as datatypes. It's especially
* important that item classes don't import or make direct references to specific
* manager classes, or those classes will always be compiled.</p>
*
* <p><b>Conventions</b></p>
*
* <p>There is a distinct difference in the Go system between <i>managers</i> and
* <i>utilities</i>, although both typically work with batches of Go items.
* Utilities are tools designed to be directly used by the developer at a project
* level, such as a Sequence class for animation timelining. In contrast, managers
* are self-sufficient entities that, once registered to GoEngine, operate in the
* background without requiring any direct interaction at runtime.</p>
*
* <p>Managers can do whatever they want with items, as long as they operate strictly
* via interfaces. When creating your own managers, be sure to document these things
* so users understand all caveats. Utilities and active program code will be handling
* the same items your manager does, so interactions need to be thought through and tested.
* The general assumption is that managers can freely call <code>releaseHandling()</code>
* on any item; the item should then dispatch a STOP event that can be listened for on
* the utility side.</p>
*
* <p>Finally, it's important that managers operate as efficiently as possible. You can use
* the open-source TweenBencher utility to test & optimize.</p>
*
* <p><b>Extending Management Capabilities</b></p>
*
* <p>The Go system only interacts with managers by telling them which items are being
* added and removed (played or stopped, normally). For more complex managers,
* like a hitTest routine, you may need to actively monitor items as they update.
* LinearGo and other items can provide udpate callbacks to facilitate this. You can
* also formally extend the manager/item contract by extending the IManager and
* IManageable interfaces to open up new interactions between managers and items.</p>
*
* <p>Interface extensions can also be used as marker datatypes that don't contain any
* custom methods. This enables your custom managers to sniff for a particular interface
* type in order to determine which items to store, monitor, or alter. If it's possible
* to avoid extending IManageable with new methods, you'll save the filesize of those
* method implementations across sets of items — As a general rule be as conservative
* as possible in what you add to the item side beyond IManageable's methods.</p>
*
* <p>(The aforementioned issue is a potential weakness with this version of Go. However,
* an alternative system of decorating or composititing this functionality in is hard to
* conceive of, since items need to be able to report private, instance-level information.)</p>
*
* @see IManageable
* @see org.goasap.managers.OverlapMonitor OverlapMonitor
* @see org.goasap.GoEngine#addManager GoEngine.addManager
*
* @author Moses Gunesch
*/
+ * Makes an object compatible with <code>GoEngine.addManager()</code>
* <font color="#CC0000">[This section updated recently!]</font></p>
* <p><b>What are managers?</b></p>
*
* <p>Tweens and other animation items are not aware of other items while they
* run; by contrast, manager classes can monitor and interact with many active
* items at once. <code>OverlapMonitor</code>, a manager shipped with GoASAP,
* prevents situations like two different tween instances trying to animate the
* x property of a single sprite at the same time. This type of conflict needs
* a system-level manager that can look at multiple items as they operate. Managers
* can be used to automate any general process within an animation system.
* This sounds dry, but it can be a creative opportunity as well: imagine a manager
* that automatically motion-blurs targets based on their velocity, for example.
* Working at the system level gives you power that you don't have at the GoItem level,
* and opens up a new range of possibilites. For example, a custom game engine would
* be built primarily at the management level.</p>
*
* <p>There is a distinct difference in the Go system between <i>managers</i> and
* <i>utilities</i>, although both typically work with batches of Go items. Utilities
* are tools designed to be directly used at a project level, such as a sequence or
* animation syntax parser (even <code>GoItems</code> like tween classes are essentially
* utilities). In contrast, managers are self-sufficient entities that, once registered
* to <code>GoEngine</code>, operate in the background without requiring any direct
* interaction at runtime.</p>
*
* <p><b>About Go's Decoupled Management system</b></p>
*
* <p>The downside of managers in general is that they can add overhead as they perform
* their additional processes, slowing your system down. Prefab tween engines usually "bake"
* management features into their core code, locking you into any processing cost incurred as
* well as whichever set of features the author decided were important. GoASAP's management
* layer is designed specifically to solve these problems, and is GoASAP's most unique
* architectural feature. It leverages the centralized pulse engine as a registration hub
* for any number of managers, then leaves it up to the end user which managers to register
* per project.</p>
*
* <p>This layer stays <i>optional</i> at all levels: it is optional to make tweens or other
* animation items manageable in the first place (by implementing <code>IManageable</code>),
* but it is very easy to write your own custom managers (that implement <code>IManager</code>).
* Then even after implementation, it still remains optional for the end-user whether to add
* any particular manager to GoEngine at runtime. By choosing not to add any managers if they
* aren't needed in a project, Go can stay ultimately streamlined and limit its footprint to
* just code that is used. It's also very easy to create custom managers to meet the needs
* of a challenging project. You can activate these custom tools at runtime this time, then
* ignore them until needed again. This allows you to tie your custom program code very tightly
* into your animation engine, but keeps those customizations neatly 'decoupled.'</p>
*
* <p><b>Go Manager types</b></p>
*
* <p>Go currently provides two manager interfaces to choose from, <code>IManager</code> and
* <code>ILiveManager</code>. An <code>IManager</code> is notified every time any <code>IManageable</code>
* item is added or removed from GoEngine. This is the interface used by <code>OverlapMonitor</code>
* for example, which only needs to detect conflicts as new items are added. The second interface,
* <code>ILiveManager</code>, is for situations where you want a manager to actively handle items
* as they update.</p>
*
* <p><b>Implementing <code>IManager</code></b></p>
*
* <p>This interface has two methods that are called by <code>GoEngine</code>, <code>reserve()</code>
* and <code>release()</code>. The first method is called when any item that implements <code>IManageable</code>
* is added to the engine, and the second is called when such an item is removed. This means that
* instances of a tween class that implements <code>IManageable</code>, for example, can be
* trapped by the manager while their play cycle is active. Managers can do whatever they want
* with the items, but the <code>IManageable</code> interface ensures that they can always get
* the <i>active animation targets and properties</i> from the item, determine <i>property overlap</i>
* between items, and ask items to <i>stop playing</i> when necessary. There are no rules for what you
* write in the <code>reserve()</code> or <code>release()</code> methods, except that you should not
* call <code>release()</code> directly from <code>reserve()</code>, but instead ask an item to stop via
* a <code>IManageable.releaseHandling()</code> call. <code>GoEngine</code> will call <code>release()</code>
* on the manager once the item has truly been stopped.</p>
*
* <p>You can also extend <code>IManageable</code> to add special functionality that a manager might use
* on an item, or even just to create a new marker datatype without adding any custom methods. This enables
* your custom managers to sniff for a particular interface type in order to determine which items to store,
* monitor, or alter. The general rule is that items like tweens are considered working code, so you might
* end up changing the management implementations on different sets of tweens based on your project needs.
* Regardless of implementation on the manageable side, managers will remain decoupled in that they need
* to be registered into <code>GoEngine</code> to be compiled and used in a project. As a general rule
* you should try to have managers and managed items only reference each other via interfaces so that no
* classes are forced to be compiled until they are used directly in a project.</p>
*
* @see IManageable
* @see org.goasap.managers.OverlapMonitor OverlapMonitor
* @see org.goasap.GoEngine#addManager GoEngine.addManager
*
* @author Moses Gunesch
*/
public interface IManager
{
/**
* GoEngine reporting that an IManageable is being added to its pulse list.
*
* @param handler IManageable to query
*/
function reserve(handler:IManageable):void;
/**
* GoEngine reporting that an IManageable is being removed from its pulse list.
*
* <p>This method should NOT directly stop the item, stopping an item results in
* a release() call from GoEngine. This method should simply remove the item from
* any internal lists and unsubscribe all listeners on the item.</p>
*/
function release(handler:IManageable):void;
}
}
Modified: trunk/goasap/src_go/org/goasap/items/GoItem.as
==============================================================================
--- trunk/goasap/src_go/org/goasap/items/GoItem.as (original)
+++ trunk/goasap/src_go/org/goasap/items/GoItem.as Mon Jun 2 18:17:54 2008
@@ -1,3 +1,3 @@
/**
* 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 org.goasap.GoEngine;
import org.goasap.PlayableBase;
import org.goasap.interfaces.IUpdatable;
/**
* Abstract base animation class for other base classes like LinearGo and PhysicsGo.
*
* <p>This class extends PlayableBase to add features common to any animation item,
* either linear or physics (LinearGo and PhysicsGo both extend this class).
* Animation items add themselves to GoEngine to run on a pulse, so the IUpdatable
* interface is implemented here, although update() needs to be subclassed.</p>
*
* <p>Animation items should individually implement the standard <code>useRounding</code>
* and <code>useRelative</code> options. Three user-accessible class default settings
* are provided for those and <code>pulseInterval</code>, while play-state constants
* live in the superclass PlayableBase.</p>
*
* @author Moses Gunesch
*/
- public class GoItem extends PlayableBase implements IUpdatable
{
// -== Settable Class Defaults ==-
/**
* Class default for the instance property <code>pulseInterval</code>.
*
* <p>The default value of 33 has proven to be more efficient than using GoEngine.ENTER_FRAME
* although this may vary per computer. Use the open-source TweenBencher utility to run your own
* tests. If your results differ significantly please post a comment to the Go mailing list.</p>
*
* @default 33
* @see #pulseInterval
*/
public static var defaultPulseInterval : Number = 33; // GoEngine.ENTER_FRAME;
/**
* Class default for the instance property <code>useRounding</code>.
* @default false
* @see #useRounding
*/
public static var defaultUseRounding : Boolean = false;
/**
* Class default for the instance property <code>useRelative</code>.
* @default false
* @see #useRelative
*/
public static var defaultUseRelative : Boolean = false;
/**
* Alters the play speed for instances of any subclass that factors
* this value into its calculations, such as LinearGo.
*
* <p>A setting of 2 should result in half-speed animations, while a setting
* of .5 should double animation speed. Note that changing this property at
* runtime does not usually affect already-playing items.</p>
*
* <p>This property is a Go convention, and all subclasses of GoItem (on the
* LinearGo base class level, but not on the item level extending LinearGo)
* need to implement it individually.</p>
* @default 1
*/
public static var timeMultiplier : Number = 1;
// -== Public Properties ==-
/**
* Required by IUpdatable: Defines the pulse on which <code>update</code> is called.
*
* <p>
* Can be a number of milliseconds for Timer-based updates or
* <code>GoEngine.ENTER_FRAME</code> (-1) for updates synced to the
* Flash Player's framerate. If not set manually, the class
* default <code>defaultPulseInterval</code> is adopted.
* </p>
*
* @see #defaultPulseInterval
* @see org.goasap.GoEngine#ENTER_FRAME GoEngine.ENTER_FRAME
*/
public function get pulseInterval() : int {
return _pulse;
}
public function set pulseInterval(interval:int) : void {
if (_state==STOPPED && (interval >= 0 || interval==GoEngine.ENTER_FRAME)) {
_pulse = interval;
}
}
/**
* CONVENTION ALERT: <i>This property is considered a Go convention, and subclasses must
* implement it individually by calling the correctValue() method on all calculated values
* before applying them to targets.</i>
*
* <p>The correctValue method fixes NaN's as 0 and applies Math.round if useRounding is active.</p>
*
* @see correctValue()
* @see LinearGo#onUpdate()
*/
public var useRounding : Boolean = defaultUseRounding;
/**
* CONVENTION ALERT: <i>This property is considered a Go convention, and subclasses must implement
* it individually.</i> Indicates that values should be treated as relative instead of absolute.
*
* <p>When true, user-set values should be calculated as
* relative to their existing value ("from" vs. "to"), when possible.
* See an example in the documentation for <code>LinearGo.start</code>.
* </p>
* <p>
* Items that handle more than one property at once, such as a bezier
* curve, might want to implement a useRelative option for each property
* instead of using this overall item property, which is included here
* to define a convention for standard single-property items.
* </p>
*
* @see #defaultUseRelative
*/
public var useRelative : Boolean = defaultUseRelative;
// -== Protected Properties ==-
/**
* @private
*/
protected var _pulse : int = defaultPulseInterval;
// -== Public Methods ==-
/**
* Constructor.
*/
public function GoItem() {
super();
}
/**
* IMPORTANT: <i>Subclasses need to implement this functionality
* individually</i>. When updating animation targets, always call
* <code>correctValue</code> on results first. This corrects any
* NaN's to 0 and applies Math.round if <code>useRounding</code>
* is active.
*
* <p>For example, a LinearGo <code>onUpdate</code> method might contain:</p>
* <pre>
* target[ prop ] = correctValue(start + (change * _position));
* </pre>
*
* @see #useRounding
* @see #defaultUseRounding
*/
public function correctValue(value:Number):Number
{
if (isNaN(value))
return 0;
if (useRounding) // thanks John Grden
return value = ((value%1==0)
? value
: ((value%1>=.5)
? int(value)+1
: int(value)));
return value;
}
/**
* Required by IUpdatable: Perform updates on a pulse.
*
* <p>The <i>currentTime</i> parameter enables tight visual syncing of groups of items.
* To ensure the tightest possible synchronization, do not set any internal start-time
* vars in the item until the first update() call is received, then set to the currentTime
* provided by GoEngine. This ensures that groups of items added in a for-loop all have the
* exact same start times, which may otherwise differ by a few milliseconds.</p>
*
* @param currentTime A clock time that should be used instead of getTimer
* to store any start-time vars on the first update call
* and for performing update calculations. The value is usually
* no more than a few milliseconds different than getTimer,
* but using it tightly syncs item groups visually.
*/
public function update(currentTime : Number) : void {
// override this method.
}
}
}
\ No newline at end of file
+ public class GoItem extends PlayableBase implements IUpdatable
{
// -== Settable Class Defaults ==-
/**
* Class default for the instance property <code>pulseInterval</code>.
*
* <p>GoEngine.ENTER_FRAME seems to run the smoothest in real-world contexts.
* The open-source TweenBencher utility shows that timer-based framerates like
* 33 milliseconds can perform best for thousands of simultaneous animations,
* but in practical contexts timer-based animations tend to stutter.</p>
*
* @default GoEngine.ENTER_FRAME
* @see #pulseInterval
*/
public static var defaultPulseInterval : Number = GoEngine.ENTER_FRAME;
/**
* Class default for the instance property <code>useRounding</code>.
* @default false
* @see #useRounding
*/
public static var defaultUseRounding : Boolean = false;
/**
* Class default for the instance property <code>useRelative</code>.
* @default false
* @see #useRelative
*/
public static var defaultUseRelative : Boolean = false;
/**
* Alters the play speed for instances of any subclass that factors
* this value into its calculations, such as LinearGo.
*
* <p>A setting of 2 should result in half-speed animations, while a setting
* of .5 should double animation speed. Note that changing this property at
* runtime does not usually affect already-playing items.</p>
*
* <p>This property is a Go convention, and all subclasses of GoItem (on the
* LinearGo base class level, but not on the item level extending LinearGo)
* need to implement it individually.</p>
* @default 1
*/
public static var timeMultiplier : Number = 1;
// -== Public Properties ==-
/**
* Required by IUpdatable: Defines the pulse on which <code>update</code> is called.
*
* <p>
* Can be a number of milliseconds for Timer-based updates or
* <code>GoEngine.ENTER_FRAME</code> (-1) for updates synced to the
* Flash Player's framerate. If not set manually, the class
* default <code>defaultPulseInterval</code> is adopted.
* </p>
*
* @see #defaultPulseInterval
* @see org.goasap.GoEngine#ENTER_FRAME GoEngine.ENTER_FRAME
*/
public function get pulseInterval() : int {
return _pulse;
}
public function set pulseInterval(interval:int) : void {
if (_state==STOPPED && (interval >= 0 || interval==GoEngine.ENTER_FRAME)) {
_pulse = interval;
}
}
/**
* CONVENTION ALERT: <i>This property is considered a Go convention, and subclasses must
* implement it individually by calling the correctValue() method on all calculated values
* before applying them to targets.</i>
*
* <p>The correctValue method fixes NaN's as 0 and applies Math.round if useRounding is active.</p>
*
* @see correctValue()
* @see LinearGo#onUpdate()
*/
public var useRounding : Boolean = defaultUseRounding;
/**
* CONVENTION ALERT: <i>This property is considered a Go convention, and subclasses must implement
* it individually.</i> Indicates that values should be treated as relative instead of absolute.
*
* <p>When true, user-set values should be calculated as
* relative to their existing value ("from" vs. "to"), when possible.
* See an example in the documentation for <code>LinearGo.start</code>.
* </p>
* <p>
* Items that handle more than one property at once, such as a bezier
* curve, might want to implement a useRelative option for each property
* instead of using this overall item property, which is included here
* to define a convention for standard single-property items.
* </p>
*
* @see #defaultUseRelative
*/
public var useRelative : Boolean = defaultUseRelative;
// -== Protected Properties ==-
/**
* @private
*/
protected var _pulse : int = defaultPulseInterval;
// -== Public Methods ==-
/**
* Constructor.
*/
public function GoItem() {
super();
}
/**
* IMPORTANT: <i>Subclasses need to implement this functionality
* individually</i>. When updating animation targets, always call
* <code>correctValue</code> on results first. This corrects any
* NaN's to 0 and applies Math.round if <code>useRounding</code>
* is active.
*
* <p>For example, a LinearGo <code>onUpdate</code> method might contain:</p>
* <pre>
* target[ prop ] = correctValue(start + (change * _position));
* </pre>
*
* @see #useRounding
* @see #defaultUseRounding
*/
public function correctValue(value:Number):Number
{
if (isNaN(value))
return 0;
if (useRounding) // thanks John Grden
return value = ((value%1==0)
? value
: ((value%1>=.5)
? int(value)+1
: int(value)));
return value;
}
/**
* Required by IUpdatable: Perform updates on a pulse.
*
* <p>The <i>currentTime</i> parameter enables tight visual syncing of groups of items.
* To ensure the tightest possible synchronization, do not set any internal start-time
* vars in the item until the first update() call is received, then set to the currentTime
* provided by GoEngine. This ensures that groups of items added in a for-loop all have the
* exact same start times, which may otherwise differ by a few milliseconds.</p>
*
* @param currentTime A clock time that should be used instead of getTimer
* to store any start-time vars on the first update call
* and for performing update calculations. The value is usually
* no more than a few milliseconds different than getTimer,
* but using it tightly syncs item groups visually.
*/
public function update(currentTime : Number) : void {
// override this method.
}
}
}
\ No newline at end of file
Modified: trunk/goasap/src_go/org/goasap/managers/OverlapMonitor.as
==============================================================================
--- trunk/goasap/src_go/org/goasap/managers/OverlapMonitor.as (original)
+++ trunk/goasap/src_go/org/goasap/managers/OverlapMonitor.as Mon Jun 2 18:17:54 2008
@@ -1,4 +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.managers {
import flash.utils.Dictionary;
import org.goasap.interfaces.IManageable;
import org.goasap.interfaces.IManager;
/**
- * Calls <code>releaseHandling()</code> on currently-active items when
* property-handling overlap is detected (like two tweens trying to set
* the same sprite's x property), as new items are added to GoEngine.
*
* <p>To activate this manager call the following line one time:</p>
* <pre>GoEngine.addManager( new OverlapMonitor() );</pre>
*
* {In the game of Go, a superko is a rule that prevents a potentially
* infinite competition - ko - over the same space.}
*
* @see org.goasap.interfaces.IManager IManager
* @see org.goasap.interfaces.IManageable IManageable
* @see org.goasap.GoEngine GoEngine
*
* @author Moses Gunesch
- */
- public class OverlapMonitor implements IManager
{
/**
* This manager uses a Dictionary with target objects as the index
* values and an Array of handlers currently handling the target as
* the stored values. Targets are indexed because they are the primary
* point of overlap to check first.
*/
protected var handlers : Dictionary = new Dictionary(true);
/**
* Sets an IManageable as reserving its target/property combinations.
*
* @param handler IManageable to reserve
*/
public function reserve(handler:IManageable):void
{
var targs:Array = handler.getActiveTargets();
var props:Array = handler.getActiveProperties();
if (!targs || !props)
return;
if (targs.length==0 || props.length==0)
return;
// The dictionary is keyed by animation target objects, the most primary point of potential overlap.
// Cycle through the targets handled by the incoming handler (often there's only one),
// then inspect the handlers currently assigned to determine overlap.
for each (var target:Object in targs)
{
// Case: incoming handler is the only one working with this target at this time.
if (!handlers[ target ]) {
handlers[ target ] = new Array(handler);
continue;
}
// There is a matching target in the Dictionary. Each entry in the dictionary is an Array of
// IManageable instances currently handling that target.
var handlersForTarget:Array = (handlers[ target ] as Array);
// Duplicate entries are not allowed in the handler lists. (Really we should return out here
// since targets are not supposed to change during item play, but continue just to be sure.)
if (handlersForTarget.indexOf(handler) > -1)
continue;
// Leave first so resulting release() doesn't destroy the array before we write to it.
handlersForTarget.push( handler );
// Test all properties being handled on this target against existing handlers for this target.
// If an overlap is reported, release the existing handler which is then responsible for stopping itself.
for each (var handlerToTest:IManageable in handlersForTarget) {
if (handlerToTest!=handler)
if (handlerToTest.isHandling(props) == true)
handlerToTest.releaseHandling();
}
}
}
/**
* Releases an IManageable from being monitored. Does not call releaseHandling() on instances,
* since this method is called after an instance has already removed itself from the engine.
*
* @param handler The IManageable to remove from internal lists.
*/
public function release(handler:IManageable):void
{
for (var target:Object in handlers) {
var handlerList:Array = (handlers[target] as Array);
// Array entries are unique so we only have to remove one.
var i:int =(handlerList.indexOf(handler));
if (i>-1) {
handlerList.splice(i, 1);
if (handlerList.length==0)
delete handlers[ target ];
}
}
}
}
}
\ 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.managers {
import flash.utils.Dictionary;
import org.goasap.interfaces.IManageable;
import org.goasap.interfaces.IManager;
/**
* Calls <code>releaseHandling()</code> on currently-active items when
* property-handling overlap is detected (like two tweens trying to set
* the same sprite's x property), as new items are added to GoEngine.
*
* <p>To activate this manager call the following line one time:</p>
* <pre>GoEngine.addManager( new OverlapMonitor() );</pre>
*
* {In the game of Go, a superko is a rule that prevents a potentially
* infinite competition - ko - over the same space.}
*
* @see org.goasap.interfaces.IManager IManager
* @see org.goasap.interfaces.IManageable IManageable
* @see org.goasap.GoEngine GoEngine
*
* @author Moses Gunesch
*/
public class OverlapMonitor implements IManager
{
/**
* A set of Dictionaries by target object. Targets are indexed
* because they are the primary point of overlap to check first.
*/
protected var handlers : Dictionary = new Dictionary(false);
/**
* Tracks subdictionary lengths.
*/
protected var counts : Dictionary = new Dictionary(false);
/**
* Sets an IManageable as reserving its target/property combinations.
*
* @param handler IManageable to reserve
*/
public function reserve(handler:IManageable):void
{
// =======================================================================================
// Step-by-step: Items are 'reserved' or stored in a Dictionary.
// When a new item says it's handling the same target as a stored item, the stored item
// is asked whether the new item's properties conflict. If so, the old item is 'released'
// from its duties. (Tip: 'handlers' here are GoItems like tweens, not functions.)
// =======================================================================================
var targs:Array = handler.getActiveTargets();
var props:Array = handler.getActiveProperties();
if (!targs || !props || targs.length==0 || props.length==0)
return;
for each (var targ:Object in targs)
{
if (handlers[ targ ]==null) {
// (I switched to using sub-dictionaries w/ counters, since it may be a hair faster than Array.
// Strong keys are fine here since GoEngine stores and will release() all active items.)
handlers[ targ ] = new Dictionary(false);
handlers[ targ ][ handler ] = true;
counts[ targ ] = 1;
continue;
}
var targ_handlers: Dictionary = (handlers[ targ ] as Dictionary); // as in, 'active tweens handling a same Sprite'
if (targ_handlers[ handler ]) continue; // safety (handler already reserved)
// keep before isHandling() tests
targ_handlers[ handler ] = true;
counts[ targ ] ++;
for (var other:Object in targ_handlers) {
if (other!=handler)
if ((other as IManageable).isHandling(props)) { // Ask each existing handler to report overlap.
(other as IManageable).releaseHandling(); // Items should stop themselves on this call.
// GoEngine will then call release() back on this class which will clear the item out.
}
}
}
}
/**
* Releases an IManageable from being monitored. Does not call releaseHandling() on instances,
* since this method is called after an instance has already removed itself from the engine.
*
* @param handler The IManageable to remove from internal lists.
*/
public function release(handler:IManageable):void
{
var targs:Array = handler.getActiveTargets();
for each (var targ:Object in targs) {
if (handlers[ targ ] && handlers[ targ ][ handler ]!=null) {
delete handlers[ targ ][ handler ];
counts[ targ ] --; // don't alter this syntax. (Flex doesn't like --counts[targ])
if ( counts[ targ ] == 0 ) {
delete handlers[ targ ];
delete counts[ targ ];
}
}
}
}
}
}
\ No newline at end of file
More information about the GoList
mailing list