More.
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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user