Beginnings of Spot invocation services to allow moving between locations,

tracking and reporting locations of bodies within a scene and handling
chatting between users in a cluster. Other minor cleanups.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@774 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2001-12-14 00:12:32 +00:00
parent 6e962013a9
commit a38684db5e
10 changed files with 551 additions and 25 deletions
@@ -1,11 +1,11 @@
//
// $Id: Log.java,v 1.1 2001/11/13 00:12:20 mdb Exp $
// $Id: Log.java,v 1.2 2001/12/14 00:12:32 mdb Exp $
package com.threerings.whirled.spot;
/**
* A placeholder class that contains a reference to the log object used by
* the services provided by this package.
* the Whirled Spot services.
*/
public class Log
{
@@ -0,0 +1,37 @@
//
// $Id: SpotCodes.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
package com.threerings.whirled.spot.client;
import com.threerings.crowd.chat.ChatCodes;
import com.threerings.crowd.client.LocationCodes;
/**
* Contains codes used by the Spot invocation services.
*/
public interface SpotCodes extends ChatCodes, LocationCodes
{
/** The module name for the Spot services. */
public static final String MODULE_NAME = "whirled!spot";
/** The message identifier for a cluster speak request message. */
public static final String CLUSTER_SPEAK_REQUEST = "cspkreq";
/** The message identifier for a changeLoc request. */
public static final String CHANGE_LOC_REQUEST = "ChangeLoc";
/** The response identifier for a successful changeLoc request. This
* is mapped by the invocation services to a call to {@link
* SpotSceneDirector#handleChangeLocSucceeded}. */
public static final String CHANGE_LOC_SUCCEEDED_RESPONSE =
"ChangeLocSucceeded";
/** The response identifier for a failed changeLoc request. This is
* mapped by the invocation services to a call to {@link
* SpotSceneDirector#handleChangeLocFailed}. */
public static final String CHANGE_LOC_FAILED_RESPONSE = "ChangeLocFailed";
/** An error code indicating that a location is occupied. Usually
* generated by a failed changeLoc request. */
public static final String LOCATION_OCCUPIED = "m.location_occupied";
}
@@ -0,0 +1,100 @@
//
// $Id: SpotSceneDirector.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
package com.threerings.whirled.spot.client;
import java.util.Iterator;
import com.samskivert.util.StringUtil;
import com.threerings.whirled.client.DisplayScene;
import com.threerings.whirled.client.DisplaySceneFactory;
import com.threerings.whirled.client.SceneDirector;
import com.threerings.whirled.client.persist.SceneRepository;
import com.threerings.whirled.util.WhirledContext;
import com.threerings.whirled.spot.Log;
import com.threerings.whirled.spot.data.Location;
/**
* Extends the standard scene director with facilities to move between
* locations within a scene.
*/
public class SpotSceneDirector extends SceneDirector
implements SpotCodes
{
/**
* Creates a new spot scene director with the specified context.
*
* @param ctx the active client context.
* @param screp the entity from which the scene director will load
* scene data from the local client scene storage.
* @param dsfact the factory that knows which derivation of {@link
* DisplayScene} to create for the current system.
*/
public SpotSceneDirector (
WhirledContext ctx, SceneRepository screp, DisplaySceneFactory dsfact)
{
super(ctx, screp, dsfact);
}
/**
* Issues a request to change our location within the scene to the
* location identified by the specified id.
*/
public void changeLocation (int locationId)
{
// refuse if there's a pending location change
if (_pendingLocId != -1) {
return;
}
// make sure we're currently in a scene
if (_sceneId == -1) {
Log.warning("Requested to change locations, but we're not " +
"currently in any scene [locId=" + locationId + "].");
return;
}
// make sure the specified location is in the current scene
int locidx = -1;
DisplaySpotScene sscene = (DisplaySpotScene)_scene;
Iterator locs = sscene.getLocations().iterator();
for (int i = 0; locs.hasNext(); i++) {
Location loc = (Location)locs.next();
if (loc.locationId == locationId) {
locidx = i;
break;
}
}
if (locidx == -1) {
Log.warning("Requested to change to a location that's not " +
"in the current scene [locs=" + StringUtil.toString(
sscene.getLocations().iterator()) +
", locId=" + locationId + "].");
return;
}
// make a note that we're changing to this location
_pendingLocId = locationId;
// and send the location change request
SpotService.changeLoc(_ctx.getClient(), _sceneId, locationId, this);
}
/**
* Called in response to a successful <code>changeLoc</code> request.
*/
public void handleChangeLocSucceeded (int invid)
{
}
/**
* Called in response to a failed <code>changeLoc</code> request.
*/
public void handleChangeLocFailed (int invid, String reason)
{
}
/** The location id on which we have an outstanding change location
* request. */
protected int _pendingLocId = -1;
}
@@ -0,0 +1,32 @@
//
// $Id: SpotService.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
package com.threerings.whirled.spot.client;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationDirector;
import com.threerings.whirled.spot.Log;
/**
* Provides a mechanism by which the client can request to move between
* locations within a scene. These services should not be used directly,
* but instead should be accessed via the {@link SpotSceneDirector}.
*/
public class SpotService implements SpotCodes
{
/**
* Requests that that this client's body be made to occupy the
* specified location.
*/
public static void changeLoc (Client client, int sceneId, int locationId,
SpotSceneDirector rsptarget)
{
InvocationDirector invdir = client.getInvocationDirector();
Object[] args = new Object[] { new Integer(sceneId),
new Integer(locationId) };
invdir.invoke(MODULE_NAME, CHANGE_LOC_REQUEST, args, rsptarget);
Log.info("Sent changeLoc request [sceneId=" + sceneId +
", locId=" + locationId + "].");
}
}
@@ -0,0 +1,44 @@
//
// $Id: SpotOccupantInfo.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
package com.threerings.whirled.spot.data;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import com.threerings.crowd.data.OccupantInfo;
/**
* The spot services extend the basic occupant info with information on
* which location within the scene a user occupies.
*/
public class SpotOccupantInfo extends OccupantInfo
{
/** The id of the location occupied by this user or -1 if they occupy
* no location. */
public int locationId;
// documentation inherited
public void writeTo (DataOutputStream out)
throws IOException
{
super.writeTo(out);
out.writeInt(locationId);
}
// documentation inherited
public void readFrom (DataInputStream in)
throws IOException
{
super.readFrom(in);
locationId = in.readInt();
}
// documentation inherited
protected void toString (StringBuffer buf)
{
super.toString(buf);
buf.append(", locId=").append(locationId);
}
}
@@ -0,0 +1,51 @@
//
// $Id: ClusterChatMessageHandler.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
package com.threerings.whirled.spot.server;
import com.threerings.presents.dobj.MessageEvent;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.server.PlaceManager;
import com.threerings.whirled.server.WhirledServer;
import com.threerings.whirled.spot.Log;
/**
* The cluster chat message handler handles chat messages that are issued
* to the cluster associated with a particular location, with the
* intention of speaking only to bodies in that cluster.
*/
public class ClusterChatMessageHandler
implements PlaceManager.MessageHandler
{
/**
* Handles {@link SpotCodes#CLUSTER_SPEAK_REQUEST} messages.
*/
public void handleEvent (MessageEvent event, PlaceManager pmgr)
{
// presently we do no ratification of chat messages, so we just
// generate a chat notification with the message and name of the
// speaker
int soid = event.getSourceOid();
BodyObject source = (BodyObject)WhirledServer.omgr.getObject(soid);
if (source == null) {
Log.info("Chatter went away. Dropping cluster chat request " +
"[req=" + event + "].");
return;
}
// parse our incoming arguments
Object[] inargs = event.getArgs();
int reqid = ((Integer)inargs[0]).intValue();
int locid = ((Integer)inargs[1]).intValue();
String message = (String)inargs[2];
// pass this request on to the spot scene manager as it will need
// to check that the location exists and that the requester
// occupies it and so on
((SpotSceneManager)pmgr).handleClusterChatRequest(
source, locid, message);
}
}
@@ -1,5 +1,5 @@
//
// $Id: RuntimeSpotScene.java,v 1.1 2001/11/13 02:25:35 mdb Exp $
// $Id: RuntimeSpotScene.java,v 1.2 2001/12/14 00:12:32 mdb Exp $
package com.threerings.whirled.spot.server;
@@ -13,16 +13,32 @@ import com.threerings.whirled.server.RuntimeScene;
*/
public interface RuntimeSpotScene extends RuntimeScene
{
/**
* Returns the number of locations in this scene.
*/
public int getLocationCount ();
/**
* Returns the location id of the location at the specified index.
*/
public int getLocationId (int locidx);
/**
* Returns the index of the specified location id.
*/
public int getLocationIndex (int locationId);
/**
* Returns the total number of clusters in this scene.
*/
public int getClusterCount ();
/**
* Returns the cluster index associated with the specified location or
* -1 if the location is not associated with a cluster.
* Returns the cluster index associated with the specified location
* index or -1 if the location at that index is not associated with a
* cluster.
*/
public int getClusterIndex (int locationId);
public int getClusterIndex (int locationIdx);
/**
* Returns the location id of the default entrance to this scene. If a
@@ -1,5 +1,5 @@
//
// $Id: RuntimeSpotSceneImpl.java,v 1.1 2001/11/13 02:25:35 mdb Exp $
// $Id: RuntimeSpotSceneImpl.java,v 1.2 2001/12/14 00:12:32 mdb Exp $
package com.threerings.whirled.spot.server;
@@ -38,6 +38,30 @@ public class RuntimeSpotSceneImpl extends RuntimeSceneImpl
_clusterCount++;
}
// documentation inherited
public int getLocationCount ()
{
return _model.locationIds.length;
}
// documentation inherited
public int getLocationId (int locidx)
{
return _model.locationIds[locidx];
}
// documentation inherited
public int getLocationIndex (int locationId)
{
int lcount = _model.locationIds.length;
for (int i = 0; i < lcount; i++) {
if (_model.locationIds[i] == locationId) {
return i;
}
}
return -1;
}
// documentation inherited
public int getClusterCount ()
{
@@ -45,10 +69,9 @@ public class RuntimeSpotSceneImpl extends RuntimeSceneImpl
}
// documentation inherited
public int getClusterIndex (int locationId)
public int getClusterIndex (int locationIdx)
{
int lidx = getLocationIndex(locationId);
return (lidx == -1) ? -1 : _model.locationClusters[lidx];
return _model.locationClusters[locationIdx];
}
// documentation inherited
@@ -71,21 +94,6 @@ public class RuntimeSpotSceneImpl extends RuntimeSceneImpl
return (pidx == -1) ? -1 : _model.targetLocIds[pidx];
}
/**
* Returns the index of the specified location in the model's internal
* location arrays.
*/
protected int getLocationIndex (int locationId)
{
int lcount = _model.locationIds.length;
for (int i = 0; i < lcount; i++) {
if (_model.locationIds[i] == locationId) {
return i;
}
}
return -1;
}
/**
* Returns the index of the specified portal in the model's internal
* portal arrays.
@@ -0,0 +1,66 @@
//
// $Id: SpotProvider.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
package com.threerings.whirled.spot.server;
import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.InvocationProvider;
import com.threerings.presents.server.ServiceFailedException;
import com.threerings.crowd.data.BodyObject;
import com.threerings.whirled.server.SceneRegistry;
import com.threerings.whirled.spot.Log;
import com.threerings.whirled.spot.client.SpotCodes;
/**
* This class provides the server side of the spot services.
*/
public class SpotProvider extends InvocationProvider
implements SpotCodes
{
/**
* Constructs a spot provider and registers it with the invocation
* manager to handle spot services. This need be done by a server that
* wishes to make use of the Spot services.
*/
public static void init (
InvocationManager invmgr, SceneRegistry screg)
{
// we'll need this later
_screg = screg;
// register a spot provider instance
invmgr.registerProvider(MODULE_NAME, new SpotProvider());
}
/**
* Processes a request from a client to move to a new location.
*/
public void handleChangeLocRequest (
BodyObject source, int invid, int sceneId, int locationId)
{
try {
// look up the scene manager for the specified scene
SpotSceneManager smgr = (SpotSceneManager)
_screg.getSceneManager(sceneId);
if (smgr == null) {
Log.warning("User requested to change location in " +
"non-existent scene [user=" + source.username +
", sceneId=" + sceneId +
", locId=" + locationId + "].");
throw new ServiceFailedException(INTERNAL_ERROR);
}
smgr.handleChangeLocRequest(source, locationId);
sendResponse(source, invid, CHANGE_LOC_SUCCEEDED_RESPONSE);
} catch (ServiceFailedException sfe) {
sendResponse(source, invid, CHANGE_LOC_FAILED_RESPONSE,
sfe.getMessage());
}
}
/** The scene registry with which we interoperate. */
protected static SceneRegistry _screg;
}
@@ -0,0 +1,172 @@
//
// $Id: SpotSceneManager.java,v 1.1 2001/12/14 00:12:32 mdb Exp $
package com.threerings.whirled.spot.server;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.server.ServiceFailedException;
import com.threerings.crowd.chat.ChatProvider;
import com.threerings.crowd.data.BodyObject;
import com.threerings.whirled.server.SceneManager;
import com.threerings.whirled.spot.Log;
import com.threerings.whirled.spot.client.SpotCodes;
import com.threerings.whirled.spot.data.SpotOccupantInfo;
/**
* Handles the movement of bodies between locations in the scene and
* creates the necessary distributed objects to allow bodies in clusters
* to chat with one another.
*/
public class SpotSceneManager extends SceneManager
implements SpotCodes
{
// documentation inherited
protected void gotSceneData ()
{
// keep a casted reference around to our scene
_sscene = (RuntimeSpotScene)_scene;
// now that we have our scene, we create chat objects for each of
// the clusters in the scene
_clusterOids = new int[_sscene.getClusterCount()];
// create a subscriber that will grab the oids when we hear back
// about object creation
Subscriber sub = new Subscriber() {
public void objectAvailable (DObject object) {
_clusterOids[_index++] = object.getOid();
}
public void requestFailed (int oid, ObjectAccessException cause) {
Log.warning("Failed to create cluster object " +
"[cluster=" + _index + ", oid=" + oid +
", cause=" + cause + "].");
// skip to the next cluster in case others didn't fail
_index++;
}
protected int _index = 0;
};
// now issue the object creation requests
for (int i = 0; i < _clusterOids.length; i++) {
_omgr.createObject(DObject.class, sub);
}
// create an array in which to track the occupants of each
// location
_locationOccs = new int[_sscene.getLocationCount()];
}
/**
* Called by the {@link SpotProvider} when we receive a request by a
* user to occupy a particular location.
*
* @exception ServiceFailedException thrown with a reason code
* explaining the location change failure if there is a problem
* processing the location change request.
*/
protected void handleChangeLocRequest (BodyObject source, int locationId)
throws ServiceFailedException
{
// make sure no one is already in the requested location
int locidx = _sscene.getLocationIndex(locationId);
if (locidx == -1 || _locationOccs[locidx] != 0) {
Log.info("Ignoring request to change to occupied or " +
"non-existent location [user=" + source.username +
", locId=" + locationId + ", locidx=" + locidx + "].");
throw new ServiceFailedException(LOCATION_OCCUPIED);
}
// make sure they have an occupant info object in the place
int bodyOid = source.getOid();
SpotOccupantInfo soi = (SpotOccupantInfo)
_plobj.occupantInfo.get(new Integer(bodyOid));
if (soi == null) {
Log.warning("Aiya! Can't update non-existent occupant info " +
"with new location [body=" + source + "].");
throw new ServiceFailedException(INTERNAL_ERROR);
}
// stick our new friend into that location
_locationOccs[locidx] = bodyOid;
// update their occupant info
soi.locationId = locationId;
// and broadcast the update to the place
_plobj.updateOccupantInfo(soi);
}
/**
* Called by the {@link ClusterChatMessageHandler} when we receive a
* cluster chat request.
*/
protected void handleClusterChatRequest (
BodyObject source, int locationId, String message)
{
// make sure this user occupies the specified location
int locidx = _sscene.getLocationIndex(locationId);
if (locidx == -1 || _locationOccs[locidx] != source.getOid()) {
Log.warning("User not in specified location for CCREQ " +
"[body=" + source + ", locId=" + locationId +
", message=" + message + "].");
return;
}
// make sure there's a cluster associated with this location
int clusterIndex = _sscene.getClusterIndex(locidx);
if (clusterIndex == -1) {
Log.warning("User in clusterless location sent CCREQ " +
"[body=" + source + ", locId=" + locationId +
", message=" + message + "].");
return;
}
// all is well, generate a chat notification
int clusterOid = _clusterOids[clusterIndex];
if (clusterOid > 0) {
ChatProvider.sendChatMessage(clusterOid, source.username, message);
} else {
Log.warning("Have no cluster object for CCREQ " +
"[cidx=" + clusterIndex +
", chatter=" + source.username +
", message=" + message + "].");
}
}
/**
* Returns the location index of the location occupied by the
* specified body oid or -1 if they occupy no location.
*/
protected int getLocationIndex (int bodyOid)
{
for (int i = 0; i < _locationOccs.length; i++) {
if (_locationOccs[i] == bodyOid) {
return i;
}
}
return -1;
}
/**
* We need our own extended occupant info to keep track of what
* location each occupant occupies.
*/
protected Class getOccupantInfoClass ()
{
return SpotOccupantInfo.class;
}
/** A casted reference to our runtime scene instance. */
protected RuntimeSpotScene _sscene;
/** Oids of the cluster chat objects. */
protected int[] _clusterOids;
/** Oids of the bodies that occupy each of our locations. */
protected int[] _locationOccs;
}