// // $Id: LocationDirector.java,v 1.16 2001/12/16 05:18:20 mdb Exp $ package com.threerings.crowd.client; import java.util.ArrayList; import java.util.List; import com.threerings.presents.client.*; import com.threerings.presents.dobj.*; import com.threerings.crowd.Log; import com.threerings.crowd.data.*; 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 implements ClientObserver, Subscriber { /** * 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 LocationDirector (CrowdContext ctx) { // keep this around for later _ctx = ctx; // register ourselves as a client observer ctx.getClient().addObserver(this); } /** * Adds a location observer to the list. This observer will * subsequently be notified of potential, effected and failed location * changes. */ public void addLocationObserver (LocationObserver observer) { _observers.add(observer); } /** * Removes a location observer from the list. */ public void removeLocationObserver (LocationObserver observer) { _observers.remove(observer); } /** * Returns the place object for the location we currently occupy or * null if we're not currently occupying any location. */ public PlaceObject getPlaceObject () { return _plobj; } /** * 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. */ public void moveTo (int placeId) { // first check to see if our observers are happy with this move // request if (!mayMoveTo(placeId)) { return; } // complain if we're over-writing a pending request if (_pendingPlaceId != -1) { Log.warning("We appear to have a moveTo request outstanding " + "[ppid=" + _pendingPlaceId + ", npid=" + placeId + "]."); // but we're going to fall through and do it anyway because // refusing to switch rooms at this point will inevitably // result in some strange bug causing a move request to be // dropped by the server and the client that did it to be // totally hosed because they can no longer move to new // locations because they still have an outstanding request } // make a note of our pending place id _pendingPlaceId = placeId; // issue a moveTo request LocationService.moveTo(_ctx.getClient(), placeId, this); } /** * 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 boolean moveBack () { if (_previousPlaceId == -1) { return false; } else { moveTo(_previousPlaceId); 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 boolean mayMoveTo (int placeId) { for (int i = 0; i < _observers.size(); i++) { LocationObserver obs = (LocationObserver)_observers.get(i); if (!obs.locationMayChange(placeId)) { Log.info("Location change vetoed by observer " + "[pid=" + placeId + ", obs=" + obs + "]."); return false; } } 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 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) { DObjectManager omgr = _ctx.getDObjectManager(); // do some cleaning up if we were previously in a place 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 omgr.unsubscribeFromObject(_plobj.getOid(), this); _plobj = null; } // make a note that we're now mostly in the new location _previousPlaceId = _placeId; _placeId = placeId; Class cclass = config.getControllerClass(); try { // start up a new place controller to manage the new place _controller = (PlaceController)cclass.newInstance(); _controller.init(_ctx, 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 omgr.subscribeToObject(_placeId, this); } /** * 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) { // let our observers know what's up notifyFailure(placeId, reason); } public void clientDidLogon (Client client) { // get a copy of 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); } 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 } public void clientFailedToLogon (Client client, Exception cause) { // we're fair weather observers. we do nothing until logon // succeeds } public void clientConnectionFailed (Client client, Exception cause) { // nothing doing } public boolean clientWillLogoff (Client client) { // we have no objections return true; } public void clientDidLogoff (Client client) { // nothing doing } /** * Called in response to a successful moveTo request. */ public void handleMoveSucceeded (int invid, PlaceConfig config) { // handle the successful move didMoveTo(_pendingPlaceId, config); // and clear out the tracked pending oid _pendingPlaceId = -1; } /** * Called in response to a failed moveTo request. */ public void handleMoveFailed (int invid, 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); } /** * 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 for (int i = 0; i < _observers.size(); i++) { LocationObserver obs = (LocationObserver)_observers.get(i); obs.locationDidChange(_plobj); } } /** * 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 (int placeId, String reason) { for (int i = 0; i < _observers.size(); i++) { LocationObserver obs = (LocationObserver)_observers.get(i); obs.locationChangeFailed(placeId, reason); } } /** The context through which we access needed services. */ protected CrowdContext _ctx; /** Our location observer list. */ protected List _observers = new ArrayList(); /** 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 entity that deals when we fail to subscribe to a place * object. */ protected FailureHandler _failureHandler; }