Made ObserverList work with a passed-in function, actionscript style.
I might reconsider some earlier decisions to go away from ObserverList.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3952 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Ray Greenwell
2006-03-16 02:11:27 +00:00
parent 3325b1d7a3
commit 03d94291db
11 changed files with 1141 additions and 0 deletions
@@ -24,6 +24,7 @@ package com.threerings.crowd.chat.client {
import mx.collections.ArrayCollection;
import com.threerings.util.ArrayUtil;
import com.threerings.util.ResultListener;
import com.threerings.util.SimpleMap;
import com.threerings.presents.client.BasicDirector;
@@ -0,0 +1,50 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.data {
import com.threerings.util.Byte;
import com.threerings.crowd.chat.client.SpeakService;
import com.threerings.presents.client.Client;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.InvocationResponseEvent;
/**
* Provides the implementation of the {@link SpeakService} interface
* that marshalls the arguments and delivers the request to the provider
* on the server. Also provides an implementation of the response listener
* interfaces that marshall the response arguments and deliver them back
* to the requesting client.
*/
public class SpeakMarshaller extends InvocationMarshaller
implements SpeakService
{
/** The method id used to dispatch {@link #speak} requests. */
public static const SPEAK :int = 1;
// documentation inherited from interface
public function speak (arg1 :Client, arg2 :String, arg3 :int) :void
{
sendRequest(arg1, SPEAK, [ arg2, new Byte(arg3) ]);
}
}
}
@@ -0,0 +1,642 @@
//
// $Id: LocationDirector.java 3638 2005-06-30 00:02:13Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client {
// TODO: class is in progress of conversion
import com.threerings.util.ObserverList;
import com.threerings.util.ResultListener;
import com.threerings.presents.client.BasicDirector;
import com.threerings.presents.client.Client;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.ObjectAccessError;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.crowd.Log;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.LocationCodes;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.util.CrowdContext;
/**
* The location director provides a means by which entities on the client
* can request to move from place to place and can be notified if other
* entities have caused the client to move to a new place. It also
* provides a mechanism for ratifying a request to move to a new place
* before actually issuing the request.
*/
public class LocationDirector extends BasicDirector
implements LocationCodes, Subscriber, LocationReceiver,
LocationService.MoveListener
{
/**
* Used to recover from a moveTo request that was accepted but
* resulted in a failed attempt to fetch the place object to which we
* were moving.
*/
// public static interface FailureHandler
// {
// /**
// * Should instruct the client to move to the last known working
// * location (as well as clean up after the failed moveTo request).
// */
// public void recoverFailedMove (int placeId);
// }
/**
* Constructs a location director which will configure itself for
* operation using the supplied context.
*/
public function LocationDirector (ctx :CrowdContext)
{
super(ctx);
// keep this around for later
_cctx = ctx;
// register for location notifications
_cctx.getClient().getInvocationDirector().registerReceiver(
new LocationDecoder(this));
}
/**
* Adds a location observer to the list. This observer will
* subsequently be notified of potential, effected and failed location
* changes.
*/
public function addLocationObserver (observer :LocationObserver) :void
{
_observers.add(observer);
}
/**
* Removes a location observer from the list.
*/
public function removeLocationObserver (observer :LocationObserver) :void
{
_observers.remove(observer);
}
/**
* Returns the place object for the location we currently occupy or
* null if we're not currently occupying any location.
*/
public function getPlaceObject () :PlaceObject
{
return _plobj;
}
/**
* Returns true if there is a pending move request.
*/
public function movePending () :Boolean
{
return (_pendingPlaceId > 0);
}
/**
* Requests that this client be moved to the specified place. A
* request will be made and when the response is received, the
* location observers will be notified of success or failure.
*
* @return true if the move to request was issued, false if it was
* rejected by a location observer or because we have another request
* outstanding.
*/
public function moveTo (placeId :int) :Boolean
{
// make sure the placeId is valid
if (placeId < 0) {
Log.warning("Refusing moveTo(): invalid placeId " + placeId + ".");
return false;
}
// first check to see if our observers are happy with this move
// request
if (!mayMoveTo(placeId, null)) {
return false;
}
// we need to call this both to mark that we're issuing a move
// request and to check to see if the last issued request should
// be considered stale
var refuse :Boolean = checkRepeatMove();
// complain if we're over-writing a pending request
if (_pendingPlaceId != -1) {
// if the pending request has been outstanding more than a
// minute, go ahead and let this new one through in an attempt
// to recover from dropped moveTo requests
if (refuse) {
Log.warning("Refusing moveTo; We have a request outstanding " +
"[ppid=" + _pendingPlaceId +
", npid=" + placeId + "].");
return false;
} else {
Log.warning("Overriding stale moveTo request " +
"[ppid=" + _pendingPlaceId +
", npid=" + placeId + "].");
}
}
// make a note of our pending place id
_pendingPlaceId = placeId;
// issue a moveTo request
Log.info("Issuing moveTo(" + placeId + ").");
_lservice.moveTo(_cctx.getClient(), placeId, this);
return true;
}
/**
* Requests to move to the room that we last occupied, if such a room
* exists.
*
* @return true if we had a previous room and we requested to move to
* it, false if we had no previous room.
*/
public function moveBack () :Boolean
{
if (_previousPlaceId == -1) {
return false;
} else {
moveTo(_previousPlaceId);
return true;
}
}
/**
* Issues a request to leave our current location.
*
* @return true if we were able to leave, false if we are in the
* middle of moving somewhere and can't yet leave.
*/
public function leavePlace () :Boolean
{
if (_pendingPlaceId != -1) {
return false;
}
_lservice.leavePlace(_cctx.getClient());
didLeavePlace();
return true;
}
/**
* This can be called by cooperating directors that need to coopt the
* moving process to extend it in some way or other. In such
* situations, they should call this method before moving to a new
* location to check to be sure that all of the registered location
* observers are amenable to a location change.
*
* @param placeId the place oid of our tentative new location.
*
* @return true if everyone is happy with the move, false if it was
* vetoed by one of the location observers.
*/
public function mayMoveTo (placeId :int, rl :ResultListener) :Boolean
{
var vetoed :Boolean = false;
_observers.apply(function (obs :Object) :Boolean {
var lobs :LocationObserver = (obs as LocationObserver);
vetoed = vetoed || !lobs.locationMayChange(placeId);
return true;
});
// if we're actually going somewhere, let the controller know that
// we might be leaving
mayLeavePlace();
// if we have a result listener, let it know if we failed
// or keep it for later if we're still going
if (rl != null) {
if (vetoed) {
rl.requestFailed(new MoveVetoedException());
} else {
_moveListener = rl;
}
}
// and return the result
return !vetoed;
}
/**
* Called to inform our controller that we may be leaving the current
* place.
*/
protected void mayLeavePlace ()
{
if (_controller != null) {
try {
_controller.mayLeavePlace(_plobj);
} catch (Exception e) {
Log.warning("Place controller choked in " +
"mayLeavePlace [plobj=" + _plobj + "].");
Log.logStackTrace(e);
}
}
}
/**
* This can be called by cooperating directors that need to coopt the
* moving process to extend it in some way or other. In such
* situations, they will be responsible for receiving the successful
* move response and they should let the location director know that
* the move has been effected.
*
* @param placeId the place oid of our new location.
* @param config the configuration information for the new place.
*/
public void didMoveTo (int placeId, PlaceConfig config)
{
if (_moveListener != null) {
_moveListener.requestCompleted(config);
_moveListener = null;
}
// keep track of our previous place id
_previousPlaceId = _placeId;
// clear out our last request time
_lastRequestTime = 0;
// do some cleaning up in case we were previously in a place
didLeavePlace();
// make a note that we're now mostly in the new location
_placeId = placeId;
// check whether we should use a custom class loader
ClassLoader loader = getClassLoader(config);
if (loader != null) {
config.setClassLoader(loader);
}
Class cclass = config.getControllerClass();
try {
// start up a new place controller to manage the new place
_controller = (PlaceController)cclass.newInstance();
_controller.init(_cctx, config);
} catch (Exception e) {
Log.warning("Error creating or initializing place controller " +
"[cclass=" + cclass.getName() +
", config=" + config + "].");
Log.logStackTrace(e);
}
// subscribe to our new place object to complete the move
_cctx.getDObjectManager().subscribeToObject(_placeId, this);
}
/**
* Called when we're leaving our current location. Informs the
* location's controller that we're departing, unsubscribes from the
* location's place object, and clears out our internal place
* information.
*/
public void didLeavePlace ()
{
if (_plobj != null) {
// let the old controller know that things are going away
if (_controller != null) {
try {
_controller.didLeavePlace(_plobj);
} catch (Exception e) {
Log.warning("Place controller choked in " +
"didLeavePlace [plobj=" + _plobj + "].");
Log.logStackTrace(e);
}
_controller = null;
}
// unsubscribe from our old place object
_cctx.getDObjectManager().unsubscribeFromObject(
_plobj.getOid(), this);
_plobj = null;
// and clear out the associated place id
_placeId = -1;
}
}
/**
* This can be called by cooperating directors that need to coopt the
* moving process to extend it in some way or other. If the coopted
* move request fails, this failure can be propagated to the location
* observers if appropriate.
*
* @param placeId the place oid to which we failed to move.
* @param reason the reason code given for failure.
*/
public void failedToMoveTo (int placeId, String reason)
{
if (_moveListener != null) {
_moveListener.requestFailed(new MoveFailedException(reason));
_moveListener = null;
}
// clear out our last request time
_lastRequestTime = 0;
// let our observers know what's up
notifyFailure(placeId, reason);
}
/**
* Called to test and set a time stamp that we use to determine if a
* pending moveTo request is stale.
*/
public boolean checkRepeatMove ()
{
long now = System.currentTimeMillis();
if (now - _lastRequestTime < STALE_REQUEST_DURATION) {
return true;
} else {
_lastRequestTime = now;
return false;
}
}
// documentation inherited from interface
public void clientDidLogon (Client client)
{
super.clientDidLogon(client);
// subscribe to our body object
Subscriber sub = new Subscriber() {
public void objectAvailable (DObject object)
{
gotBodyObject((BodyObject)object);
}
public void requestFailed (int oid, ObjectAccessException cause)
{
Log.warning("Location director unable to fetch body " +
"object; all has gone horribly wrong" +
"[cause=" + cause + "].");
}
};
int cloid = client.getClientOid();
client.getDObjectManager().subscribeToObject(cloid, sub);
}
// documentation inherited from interface
public void clientDidLogoff (Client client)
{
super.clientDidLogoff(client);
// clear ourselves out and inform observers of our departure
mayLeavePlace();
didLeavePlace();
// let our observers know that we're no longer in a location
_observers.apply(_didChangeOp);
// clear out everything else (it's possible that we were logged
// off in the middle of a change location request)
_pendingPlaceId = -1;
_previousPlaceId = -1;
_lastRequestTime = 0L;
_lservice = null;
}
// documentation inherited
protected void fetchServices (Client client)
{
// obtain our service handle
_lservice = (LocationService)
client.requireService(LocationService.class);
}
protected void gotBodyObject (BodyObject clobj)
{
// check to see if we are already in a location, in which case
// we'll want to be going there straight away
}
// documentation inherited from interface
public void moveSucceeded (PlaceConfig config)
{
// handle the successful move
didMoveTo(_pendingPlaceId, config);
// and clear out the tracked pending oid
_pendingPlaceId = -1;
}
// documentation inherited from interface
public void requestFailed (String reason)
{
// clear out our pending request oid
int placeId = _pendingPlaceId;
_pendingPlaceId = -1;
Log.info("moveTo failed [pid=" + placeId +
", reason=" + reason + "].");
// let our observers know that something has gone horribly awry
notifyFailure(placeId, reason);
}
// documentation inherited from interface
public void forcedMove (final int placeId)
{
Log.info("Moving at request of server [placeId=" + placeId + "].");
if (movePending()) {
// clear out our old place information
mayLeavePlace();
didLeavePlace();
// move to the new place
moveTo(placeId);
}
}
/**
* Called when we receive the place object to which we subscribed
* after a successful moveTo request.
*/
public void objectAvailable (DObject object)
{
// yay, we have our new place object
_plobj = (PlaceObject)object;
// let the place controller know that we're ready to roll
if (_controller != null) {
try {
_controller.willEnterPlace(_plobj);
} catch (Exception e) {
Log.warning("Controller choked in willEnterPlace " +
"[place=" + _plobj + "].");
Log.logStackTrace(e);
}
}
// let our observers know that all is well on the western front
_observers.apply(_didChangeOp);
}
/**
* Called if we are unable to subscribe to the place object that was
* provided to us with our successful moveTo request. This is
* generally a bad scene and we do our best to recover by going back
* to the previously known location.
*/
public void requestFailed (int oid, ObjectAccessException cause)
{
// aiya! we were unable to fetch our new place object; something
// is badly wrong
Log.warning("Aiya! Unable to fetch place object for new location " +
"[plid=" + oid + ", reason=" + cause + "].");
// clear out our half initialized place info
int placeId = _placeId;
_placeId = -1;
// let the kids know shit be fucked
notifyFailure(placeId, "m.unable_to_fetch_place_object");
// we need to sort out what to do about the half-initialized place
// controller. presently we punt and hope that calling
// didLeavePlace() without ever having called willEnterPlace()
// does whatever's necessary
// try to return to our previous location
if (_failureHandler != null) {
_failureHandler.recoverFailedMove(placeId);
} else {
// if we were previously somewhere (and that somewhere isn't
// where we just tried to go), try going back to that happy
// place
if (_previousPlaceId != -1 && _previousPlaceId != placeId) {
moveTo(_previousPlaceId);
}
}
}
/**
* Sets the failure handler which will recover from place object
* fetching failures. In the event that we are unable to fetch our
* place object after making a successful moveTo request, we attempt
* to rectify the failure by moving back to the last known working
* location. Because entites that cooperate with the location director
* may need to become involved in this failure recovery, we provide
* this interface whereby they can interject themseves into the
* failure recovery process and do their own failure recovery.
*/
// public void setFailureHandler (FailureHandler handler)
// {
// if (_failureHandler != null) {
// Log.warning("Requested to set failure handler, but we've " +
// "already got one. The conflicting entities will " +
// "likely need to perform more sophisticated " +
// "coordination to deal with failures. " +
// "[old=" + _failureHandler + ", new=" + handler + "].");
//
// } else {
// _failureHandler = handler;
// }
// }
protected void notifyFailure (final int placeId, final String reason)
{
_observers.apply(new ObserverOp() {
public boolean apply (Object obs) {
((LocationObserver)obs).locationChangeFailed(placeId, reason);
return true;
}
});
}
/**
* By overriding this method, it is possible to customize the location
* director to cause it to load the classes associated with a
* particular place via a custom class loader. That loader may enforce
* restricted privileges or obtain the classes from some special
* source.
*/
protected ClassLoader getClassLoader (PlaceConfig config)
{
return null;
}
/** The context through which we access needed services. */
protected CrowdContext _cctx;
/** Provides access to location services. */
protected LocationService _lservice;
/** Our location observer list. */
protected ObserverList _observers =
new ObserverList(ObserverList.SAFE_IN_ORDER_NOTIFY);
/** The oid of the place we currently occupy. */
protected int _placeId = -1;
/** The place object that we currently occupy. */
protected PlaceObject _plobj;
/** The place controller in effect for our current place. */
protected PlaceController _controller;
/**
* The oid of the place for which we have an outstanding moveTo
* request, or -1 if we have no outstanding request.
*/
protected int _pendingPlaceId = -1;
/** The oid of the place we previously occupied. */
protected int _previousPlaceId = -1;
/** The last time we requested a move to. */
protected long _lastRequestTime;
/** The entity that deals when we fail to subscribe to a place
* object. */
// protected FailureHandler _failureHandler;
/** A listener that wants to know if we succeeded or
* how we failed to move. */
protected ResultListener _moveListener;
/** The operation used to inform observers that the location changed. */
protected ObserverOp _didChangeOp = new ObserverOp() {
public boolean apply (Object obs) {
((LocationObserver)obs).locationDidChange(_plobj);
return true;
}
};
/** We require that a moveTo request be outstanding for one minute
* before it is declared to be stale. */
protected static final long STALE_REQUEST_DURATION = 60L * 1000L;
}
}
@@ -0,0 +1,209 @@
//
// $Id: OccupantDirector.java 3406 2005-03-15 02:12:03Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client {
import com.threerings.util.Iterator;
import com.threerings.util.ObserverList;
import com.threerings.util.Name;
import com.threerings.presents.client.BasicDirector;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.ClientEvent;
import com.threerings.presents.dobj.EntryAddedEvent;
import com.threerings.presents.dobj.EntryRemovedEvent;
import com.threerings.presents.dobj.EntryUpdatedEvent;
import com.threerings.presents.dobj.SetListener;
import com.threerings.crowd.Log;
import com.threerings.crowd.data.OccupantInfo;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.util.CrowdContext;
/**
* The occupant director listens for occupants of places to enter and
* exit, and dispatches notices to interested parties about these events.
*
* <p> It will eventually provide a framework for keeping track of
* occupant information in a network efficient manner. The idea being that
* we want to store as little information about occupants as possible in
* the place object (probably just body oid and username), but upon
* entering a place, this will be all we know about the occupants. We then
* dispatch a request to get information about all of the occupants in the
* room (things like avatar information for a graphical display or perhaps
* their ratings in the game that is associated with a place for a gaming
* site) which we then pass on to the occupant observers when it becomes
* available.
*
* <p> This information would be cached and we could return cached
* information for occupants for which we have cached info. We will
* probably want to still make a request for the occupant info so that we
* can update non-static occupant data rather than permanently using
* what's in the cache.
*/
public class OccupantDirector extends BasicDirector
implements LocationObserver, SetListener
{
/**
* Constructs a new occupant director with the supplied context.
*/
public function OccupantDirector (ctx :CrowdContext)
{
super(ctx);
// register ourselves as a location observer
ctx.getLocationDirector().addLocationObserver(this);
}
/**
* Adds the specified occupant observer to the list.
*/
public function addOccupantObserver (obs :OccupantObserver) :void
{
_observers.add(obs);
}
/**
* Removes the specified occupant observer from the list.
*/
public function removeOccupantObserver (obs :OccupantObserver) :void
{
_observers.remove(obs);
}
/**
* Returns the occupant info for the user in question if it exists in
* the currently occupied place. Returns null if no occupant info
* exists for the specified body.
*/
public function getOccupantInfo (bodyOid :int) :OccupantInfo
{
// make sure we're somewhere
return (_place == null) ? null :
(_place.occupantInfo.get(bodyOid) as OccupantInfo);
}
/**
* Returns the occupant info for the user in question if it exists in
* the currently occupied place. Returns null if no occupant info
* exists with the specified username.
*/
public function getOccupantInfoByName (username :Name) :OccupantInfo
{
return (_place == null) ? null : _place.getOccupantInfo(username);
}
// documentation inherited
public override function clientDidLogoff (event :ClientEvent) :void
{
super.clientDidLogoff(event);
// clear things out
if (_place != null) {
_place.removeListener(this);
_place = null;
}
}
// documentation inherited from interface LocationObserver
public function locationMayChange (placeId :int) :Boolean
{
// we've got no opinion
return true;
}
// documentation inherited from interface LocationObserver
public function locationDidChange (place :PlaceObject) :void
{
// unlisten to the old place object if there was one
if (_place != null) {
_place.removeListener(this);
}
// listen to the new one
_place = place;
if (_place != null) {
_place.addListener(this);
}
}
// documentation inherited from interface LocationObserver
public function locationChangeFailed (placeId :int, reason :String) :void
{
// nothing to do here either
}
// documentation inherited from interface SetListener
public function entryAdded (event :EntryAddedEvent) :void
{
// bail if this isn't for the OCCUPANT_INFO field
if (event.getName() != PlaceObject.OCCUPANT_INFO) {
return;
}
// now let the occupant observers know what's up
var info :OccupantInfo = (event.getEntry() as OccupantInfo);
_observers.apply(function (obj :Object) :Boolean {
(obj as OccupantObserver).occupantEntered(info);
return true;
});
}
// documentation inherited from interface SetListener
public function entryUpdated (event :EntryUpdatedEvent) :void
{
// bail if this isn't for the OCCUPANT_INFO field
if (event.getName() != PlaceObject.OCCUPANT_INFO) {
return;
}
// now let the occupant observers know what's up
var info :OccupantInfo = (event.getEntry() as OccupantInfo);
var oinfo :OccupantInfo = (event.getOldEntry() as OccupantInfo);
_observers.apply(function (obj :Object) :Boolean {
(obj as OccupantObserver).occupantUpdated(oinfo, info);
return true;
});
}
// documentation inherited from interface SetListener
public function entryRemoved (event :EntryRemovedEvent) :void
{
// bail if this isn't for the OCCUPANT_INFO field
if (event.getName() != PlaceObject.OCCUPANT_INFO) {
return;
}
// let the occupant observers know what's up
var oinfo :OccupantInfo = (event.getOldEntry() as OccupantInfo);
_observers.apply(function (obj :Object) :Boolean {
(obj as OccupantObserver).occupantLeft(oinfo);
return true;
});
}
/** The occupant observers to keep abreast of occupant antics. */
protected var _observers :ObserverList =
new ObserverList(ObserverList.SAFE_IN_ORDER_NOTIFY);
/** The user's current location. */
protected var _place :PlaceObject;
}
}
@@ -0,0 +1,52 @@
//
// $Id: OccupantObserver.java 3098 2004-08-27 02:12:55Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client {
import com.threerings.crowd.data.OccupantInfo;
/**
* An entity that is interested in hearing about bodies that enter and
* leave a location (as well as disconnect and reconnect) can implement
* this interface and register itself with the {@link OccupantDirector}.
*/
public interface OccupantObserver
{
/**
* Called when a body enters the place.
*/
function occupantEntered (info :OccupantInfo) :void;
/**
* Called when a body leaves the place.
*/
function occupantLeft (info :OccupantInfo) :void;
/**
* Called when an occupant is updated.
*
* @param oldinfo the occupant info prior to the update.
* @param newinfo the newly update info record.
*/
function occupantUpdated (
oldinfo :OccupantInfo, newinfo :OccupantInfo) :void;
}
}
@@ -0,0 +1,77 @@
//
// $Id: PlaceView.java 3098 2004-08-27 02:12:55Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client {
import com.threerings.crowd.data.PlaceObject;
/**
* This interface provides a convenient means for decoupling user
* interface components that interact with a place object and that need to
* keep themselves up to date when the client moves from place to place.
*
* <p> In general, such components need to know when the client is about
* to enter a place so that they can subscribe if necessary or at least
* extract information about the place. They also need to know when a
* client has left a place so that they can unsubscribe and clean up after
* themselves. This is the information that the place view interface makes
* available to them in a decoupled way.
*
* <p> The part of the client implementation that is responsible for the
* main user interface can act as a location observer, and it can make use
* of {@link PlaceViewUtil} to dispatch notification of place changes to
* every <code>PlaceView</code> implementing user interface element in the
* user interface hierarchy with calls to {@link
* PlaceViewUtil#dispatchWillEnterPlace} and {@link
* PlaceViewUtil#dispatchDidLeavePlace}. These functions traverse the UI
* hierarchy (starting with the element provided which would generally be
* the top-level UI element, and dispatch calls to {@link #willEnterPlace}
* and {@link #didLeavePlace} respectively on any UI element they find
* that implements <code>PlaceView</code>.
*
* <p> By doing this, the client code can simply create place-sensitive
* user interface elements and stick them in the user interface and
* essentially forget about them, knowing that they will all be notified
* of place entering and exiting by virtue of the single dispatching
* calls. It is useful to note that place-sensitive user interface
* elements will also generally need a reference to the {@link
* com.threerings.crowd.util.CrowdContext} derivative in use by
* the client, but those are best supplied at construct time.
*/
public interface PlaceView
{
/**
* Called when the client has entered a place and is about to display
* the user interface for that place.
*
* @param plobj the place object that was just entered.
*/
function willEnterPlace (plobj :PlaceObject) :void;
/**
* Called after the client has left a place and needs to clean up
* after the user interface that was displaying that place.
*
* @param plobj the place object that was just left.
*/
function didLeavePlace (plobj :PlaceObject) :void;
}
}
@@ -146,6 +146,11 @@ public class TestClient extends Client
": " + arguments);
}
public function crazyArgs (num :int = 0, str :String = "lol") :void
{
Log.debug("crazyArgs " + num + ", " + str);
}
prototype var _foo :String;
protected var _savedFunc :Function;
+13
View File
@@ -5,6 +5,19 @@ package com.threerings.util {
*/
public class ArrayUtil
{
/**
* Return the first index of the specified element, or -1 if not present.
*/
public static function indexOf (arr :Array, element :Object) :int
{
for (var ii :int = 0; ii < arr.length; ii++) {
if (arr[ii] === element) {
return ii;
}
}
return -1;
}
/**
* Remove the first instance of the specified element from the array.
*/
@@ -384,3 +384,8 @@ public class MessageBundle
protected static const QUAL_SEP :String = ":";
}
}
// TODO: figure out how this is all actually going to even work
class ResourceBundle
{
}
@@ -0,0 +1,78 @@
package com.threerings.util {
public class ObserverList
{
/** A notification ordering policy indicating that the observers
* should be notified in the order they were added and that the
* notification should be done on a snapshot of the array. */
public static const SAFE_IN_ORDER_NOTIFY :int = 1;
/** A notification ordering policy wherein the observers are notified
* last to first so that they can be removed during the notification
* process and new observers added will not inadvertently be notified
* as well, but no copy of the observer list need be made. This will
* not work if observers are added or removed from arbitrary positions
* in the list during a notification call. */
public static const FAST_UNSAFE_NOTIFY :int = 2;
/**
* Constructor
*/
public function ObserverList (notifyPolicy :int = 2)
{
_notifyPolicy = notifyPolicy;
}
/**
* Add an observer to this list.
*/
public function add (observer :Object) :void
{
if (ArrayUtil.indexOf(_list, observer) == -1) {
_list.push(observer);
}
}
/**
* Remove an observer from this list.
*/
public function remove (observer :Object) :void
{
ArrayUtil.removeFirst(_list, observer);
}
/**
* Apply some operation to all observers.
* The function to be passed in should expect one argument and return a
* Boolean.
* function (observer :Object) :Boolean.
* The function should return false if the observer should be removed
* from the list.
*/
public function apply (func :Function) :void
{
var list :Array = _list;
if (_notifyPolicy == SAFE_IN_ORDER_NOTIFY) {
list = list.concat(); // make a duplicate
list.reverse(); // reverse it so that we start with earlier obs
}
for (var ii :int = list.length-1; ii >= 0; ii--) {
try {
var result :Boolean = func(list[ii]);
if (!result) {
// remove it if directed to do so
remove(list[ii]);
}
} catch (err :Error) {
Log.warning("ObserverOp choked during notification.");
}
}
}
/** Our notification policy. */
protected var _notifyPolicy :int;
/** The actual list of observers. */
protected var _list :Array = new Array();
}
}
@@ -0,0 +1,9 @@
package com.threerings.util {
public interface ResultListener
{
function requestCompleted (obj :Object) :void;
function requestFailed (cause :Error) :void;
}
}