If we're going to do this, I guess we're going to do it properly. Nixed the

notion of a global group (though we implicitly define one in InvocationCodes)
added a mechanism for directors (which generally handle the client side of
invocation services) to register their interest in bootstrap service groups so
that the whole goddamned complex business can happen magically behind the
scenes.

If you instantiate a director, it will automatically register interest in the
service group it needs and everything will work. If you don't use the director
code, you don't get the services and you can safely exclude all of that code
from your client even though the services are still in use on the server (and
presumably used by some other types of clients).

This is going to break all the builds, which I'll soon fix. Then I'll go write
all this in ActionScript. Yay!


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4552 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2007-02-11 01:17:30 +00:00
parent 9afcc526a0
commit ebc99935d5
25 changed files with 406 additions and 358 deletions
@@ -1,54 +1,56 @@
//
// $Id$
package com.threerings.admin.client; package com.threerings.admin.client;
import java.util.HashMap; import java.util.HashMap;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.threerings.admin.Log;
import com.threerings.admin.data.ConfigObject;
import com.threerings.presents.client.Client; import com.threerings.presents.client.Client;
import com.threerings.presents.client.SessionObserver; import com.threerings.presents.client.ClientAdapter;
import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DObjectManager; import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.dobj.ObjectAccessException; import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.Subscriber; import com.threerings.presents.dobj.Subscriber;
import com.threerings.admin.Log;
import com.threerings.admin.data.AdminCodes;
import com.threerings.admin.data.ConfigObject;
/**
* Handles subscribing to admin config objects.
*/
public class ConfigObjectManager implements AdminService.ConfigInfoListener public class ConfigObjectManager implements AdminService.ConfigInfoListener
{ {
public ConfigObjectManager(Client client) { public ConfigObjectManager(Client client)
{
_serverconfig = new HashMap<String,ConfigObject>(); _serverconfig = new HashMap<String,ConfigObject>();
_client = client; _client = client;
_client.addClientObserver(new SessionObserver() { _client.addClientObserver(new ClientAdapter() {
// documentation inherited from interface SessionObserver public void clientWillLogon (Client client) {
client.addServiceGroup(AdminCodes.ADMIN_GROUP);
}
public void clientDidLogon (Client client) { public void clientDidLogon (Client client) {
// Initialize the dobjmgr
_dobjmgr = _client.getDObjectManager(); _dobjmgr = _client.getDObjectManager();
// Now that we're logged on, let's grab the server configs _service = (AdminService)client.requireService(AdminService.class);
_service = (AdminService)
client.requireService(AdminService.class);
getConfigInfo(); getConfigInfo();
} }
// documentation inherited from interface SessionObserver
public void clientDidLogoff (Client client) { public void clientDidLogoff (Client client) {
// Clean up our subscription to the server's configuration // Clean up our subscription to the server's configuration
for (int ii = 0; ii < _csubscribers.length; ii++) { for (int ii = 0; ii < _csubscribers.length; ii++) {
_csubscribers[ii].cleanup(); _csubscribers[ii].cleanup();
} }
} }
// documentation inherited from interface SessionObserver
public void clientObjectDidChange (Client client) {}
}); });
} }
/** /**
* Convenience: generate a getConfigInfo request to the AdminService * Returns the ConfigObject identified by the given key
* from the external class, instead from within the anonymous inner class
*/ */
protected void getConfigInfo() { public ConfigObject getServerConfig (String key)
_service.getConfigInfo(_client, this); {
return _serverconfig.get(key);
} }
// documentation inherited from interface AdminService.ConfigInfoListener // documentation inherited from interface AdminService.ConfigInfoListener
@@ -60,29 +62,32 @@ public class ConfigObjectManager implements AdminService.ConfigInfoListener
_csubscribers[ii].subscribeConfig(keys[ii], oids[ii]); _csubscribers[ii].subscribeConfig(keys[ii], oids[ii]);
} }
} }
// documentation inherited from interface AdminService.ConfigInfoListener // documentation inherited from interface AdminService.ConfigInfoListener
public void requestFailed (String reason) { public void requestFailed (String reason)
{
Log.warning("Oh bugger, we didn't get the config data: " + reason); Log.warning("Oh bugger, we didn't get the config data: " + reason);
} }
/** /**
* Returns the ConfigObject identified by the given key * Convenience: generate a getConfigInfo request to the AdminService from the external class,
* instead from within the anonymous inner class
*/ */
public ConfigObject getServerConfig (String key) { protected void getConfigInfo()
return _serverconfig.get(key); {
_service.getConfigInfo(_client, this);
} }
/** /**
* This class takes care of the details of subscribing to and placing * This class takes care of the details of subscribing to and placing an individual
* an individual ConfigObject that the server knows about into a HashMap * ConfigObject that the server knows about into a HashMap
*/ */
protected class ConfigObjectSubscriber implements Subscriber<ConfigObject> protected class ConfigObjectSubscriber implements Subscriber<ConfigObject>
{ {
/** /**
* This method requests that we place a subscription to the * This method requests that we place a subscription to the ConfigObject with the given
* ConfigObject with the given oid, identified by the key; when the * oid, identified by the key; when the object becomes available, it's added to our
* object becomes available, it's added to our serverconfig map. * serverconfig map.
*/ */
public void subscribeConfig (String key, int oid) { public void subscribeConfig (String key, int oid) {
_key = key; _key = key;
@@ -95,15 +100,15 @@ public class ConfigObjectManager implements AdminService.ConfigInfoListener
_cobj = object; _cobj = object;
_serverconfig.put(_key, _cobj); _serverconfig.put(_key, _cobj);
} }
// documentation inherited from interface Subscriber // documentation inherited from interface Subscriber
public void requestFailed (int oid, ObjectAccessException cause) { public void requestFailed (int oid, ObjectAccessException cause) {
Log.warning("Unable to subscribe to config object " + _key); Log.warning("Unable to subscribe to config object " + _key);
} }
/** /**
* Signals that we should stop subscribing to our ConfigObject, * Signals that we should stop subscribing to our ConfigObject, and flush out the entry
* and flush out the entry from the serverconfig map. * from the serverconfig map.
*/ */
public void cleanup () { public void cleanup () {
// clear out our subscription // clear out our subscription
@@ -114,26 +119,26 @@ public class ConfigObjectManager implements AdminService.ConfigInfoListener
/** The object that we are tracking */ /** The object that we are tracking */
protected ConfigObject _cobj; protected ConfigObject _cobj;
/** The name of the config object that we are subscribing to */ /** The name of the config object that we are subscribing to */
protected String _key; protected String _key;
/** The oid of the object that we're tracking */ /** The oid of the object that we're tracking */
protected int _oid; protected int _oid;
} }
/** An array of handlers that each subscribe to a single ConfigObject */ /** An array of handlers that each subscribe to a single ConfigObject */
protected ConfigObjectSubscriber[] _csubscribers; protected ConfigObjectSubscriber[] _csubscribers;
/** Our local copy of the server-side runtime configuration */ /** Our local copy of the server-side runtime configuration */
protected HashMap<String,ConfigObject> _serverconfig; protected HashMap<String,ConfigObject> _serverconfig;
/** Our distributed object manager */ /** Our distributed object manager */
protected DObjectManager _dobjmgr; protected DObjectManager _dobjmgr;
/** Our admin service that we're using to fetch data */ /** Our admin service that we're using to fetch data */
protected AdminService _service; protected AdminService _service;
/** Our client object */ /** Our client object */
protected Client _client; protected Client _client;
} }
@@ -0,0 +1,33 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2006 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.admin.data;
import com.threerings.presents.data.InvocationCodes;
/**
* Codes and consants relating to the admin services.
*/
public interface AdminCodes extends InvocationCodes
{
/** Defines our invocation service group. */
public static final String ADMIN_GROUP = "presents.admin";
}
@@ -26,36 +26,32 @@ import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.InvocationProvider; import com.threerings.presents.server.InvocationProvider;
import com.threerings.admin.client.AdminService; import com.threerings.admin.client.AdminService;
import com.threerings.admin.data.AdminCodes;
/** /**
* Provides the server-side implementation of various administrator * Provides the server-side implementation of various administrator services.
* services.
*/ */
public class AdminProvider implements InvocationProvider public class AdminProvider implements InvocationProvider
{ {
/** /**
* Constructs an admin provider and registers it with the invocation * Constructs an admin provider and registers it with the invocation manager to handle admin
* manager to handle admin services. This must be called by any server * services. This must be called by any server that wishes to make use of the admin services.
* that wishes to make use of the admin services.
*/ */
public static void init (InvocationManager invmgr, ConfigRegistry registry) public static void init (InvocationManager invmgr, ConfigRegistry registry)
{ {
invmgr.registerDispatcher( invmgr.registerDispatcher(
new AdminDispatcher(new AdminProvider(registry)), true); new AdminDispatcher(new AdminProvider(registry)), AdminCodes.ADMIN_GROUP);
} }
/** /**
* Handles a request for the list of config objects. * Handles a request for the list of config objects.
*/ */
public void getConfigInfo ( public void getConfigInfo (ClientObject caller, AdminService.ConfigInfoListener listener)
ClientObject caller, AdminService.ConfigInfoListener listener)
{ {
// we don't have to validate the request because the user can't do // we don't have to validate the request because the user can't do anything with the keys
// anything with the keys or oids unless they're an admin (we put the // or oids unless they're an admin (we put the burden of doing that checking on the creator
// burden of doing that checking on the creator of the config object // of the config object because we would otherwise need some mechanism to determine whether
// because we would otherwise need some mechanism to determine whether // a user is an admin and we don't want to force some primitive system on the service user)
// a user is an admin and we don't want to force some primitive system
// on the service user)
String[] keys = _registry.getKeys(); String[] keys = _registry.getKeys();
int[] oids = new int[keys.length]; int[] oids = new int[keys.length];
for (int ii = 0; ii < keys.length; ii++) { for (int ii = 0; ii < keys.length; ii++) {
@@ -52,6 +52,7 @@ import com.threerings.util.TimeUtil;
import com.threerings.crowd.Log; import com.threerings.crowd.Log;
import com.threerings.crowd.client.LocationObserver; import com.threerings.crowd.client.LocationObserver;
import com.threerings.crowd.data.BodyObject; import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.CrowdCodes;
import com.threerings.crowd.data.PlaceObject; import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.util.CrowdContext; import com.threerings.crowd.util.CrowdContext;
@@ -1002,7 +1003,13 @@ public class ChatDirector extends BasicDirector
return (type == null) ? PLACE_CHAT_TYPE : type; return (type == null) ? PLACE_CHAT_TYPE : type;
} }
// documentation inherited from interface @Override // from BasicDirector
protected void registerServices (Client client)
{
client.addServiceGroup(CrowdCodes.CROWD_GROUP);
}
@Override // from BasicDirector
protected void fetchServices (Client client) protected void fetchServices (Client client)
{ {
// get a handle on our chat service // get a handle on our chat service
@@ -33,19 +33,19 @@ import com.threerings.crowd.chat.client.SpeakService;
*/ */
public interface ChatCodes extends InvocationCodes public interface ChatCodes extends InvocationCodes
{ {
/** A return value used by the ChatDirector and possibly other entities /** A return value used by the ChatDirector and possibly other entities to indicate successful
* to indicate successful processing of chat. */ * processing of chat. */
public static final String SUCCESS = "success"; public static final String SUCCESS = "success";
/** The message identifier for a chat notification message. */ /** The message identifier for a chat notification message. */
public static final String CHAT_NOTIFICATION = "chat"; public static final String CHAT_NOTIFICATION = "chat";
/** The access control identifier for normal chat privileges. See /** The access control identifier for normal chat privileges. See {@link
* {@link BodyObject#checkAccess}. */ * BodyObject#checkAccess}. */
public static final String CHAT_ACCESS = "crowd.chat.chat"; public static final String CHAT_ACCESS = "crowd.chat.chat";
/** The access control identifier for broadcast chat privileges. See /** The access control identifier for broadcast chat privileges. See {@link
* {@link BodyObject#checkAccess}. */ * BodyObject#checkAccess}. */
public static final String BROADCAST_ACCESS = "crowd.chat.broadcast"; public static final String BROADCAST_ACCESS = "crowd.chat.broadcast";
/** The configuration key for idle time. */ /** The configuration key for idle time. */
@@ -54,10 +54,9 @@ public interface ChatCodes extends InvocationCodes
/** The default time after which a player is assumed idle. */ /** The default time after which a player is assumed idle. */
public static final long DEFAULT_IDLE_TIME = 3 * 60 * 1000L; public static final long DEFAULT_IDLE_TIME = 3 * 60 * 1000L;
/** The chat localtype code for chat messages delivered on the place /** The chat localtype code for chat messages delivered on the place object currently occupied
* object currently occupied by the client. This is the only type of * by the client. This is the only type of chat message that will be delivered unless the chat
* chat message that will be delivered unless the chat director is * director is explicitly provided with other chat message sources via {@link
* explicitly provided with other chat message sources via {@link
* ChatDirector#addAuxiliarySource}. */ * ChatDirector#addAuxiliarySource}. */
public static final String PLACE_CHAT_TYPE = "placeChat"; public static final String PLACE_CHAT_TYPE = "placeChat";
@@ -67,21 +66,18 @@ public interface ChatCodes extends InvocationCodes
/** The default mode used by {@link SpeakService#speak} requests. */ /** The default mode used by {@link SpeakService#speak} requests. */
public static final byte DEFAULT_MODE = 0; public static final byte DEFAULT_MODE = 0;
/** A {@link SpeakService#speak} mode to indicate that the user is /** A {@link SpeakService#speak} mode to indicate that the user is thinking what they're
* thinking what they're saying, or is it that they're saying what * saying, or is it that they're saying what they're thinking? */
* they're thinking? */
public static final byte THINK_MODE = 1; public static final byte THINK_MODE = 1;
/** A {@link SpeakService#speak} mode to indicate that a speak is /** A {@link SpeakService#speak} mode to indicate that a speak is actually an emote. */
* actually an emote. */
public static final byte EMOTE_MODE = 2; public static final byte EMOTE_MODE = 2;
/** A {@link SpeakService#speak} mode to indicate that a speak is /** A {@link SpeakService#speak} mode to indicate that a speak is actually a shout. */
* actually a shout. */
public static final byte SHOUT_MODE = 3; public static final byte SHOUT_MODE = 3;
/** A {@link SpeakService#speak} mode to indicate that a speak is /** A {@link SpeakService#speak} mode to indicate that a speak is actually a server-wide
* actually a server-wide broadcast. */ * broadcast. */
public static final byte BROADCAST_MODE = 4; public static final byte BROADCAST_MODE = 4;
/** String translations for the various chat modes. */ /** String translations for the various chat modes. */
@@ -89,11 +85,9 @@ public interface ChatCodes extends InvocationCodes
"default", "think", "emote", "shout", "broadcast" "default", "think", "emote", "shout", "broadcast"
}; };
/** An error code delivered when the user targeted for a tell /** An error code delivered when the user targeted for a tell notification is not online. */
* notification is not online. */
public static final String USER_NOT_ONLINE = "m.user_not_online"; public static final String USER_NOT_ONLINE = "m.user_not_online";
/** An error code delivered when the user targeted for a tell /** An error code delivered when the user targeted for a tell notification is disconnected. */
* notification is disconnected. */
public static final String USER_DISCONNECTED = "m.user_disconnected"; public static final String USER_DISCONNECTED = "m.user_disconnected";
} }
@@ -38,6 +38,7 @@ import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.InvocationProvider; import com.threerings.presents.server.InvocationProvider;
import com.threerings.crowd.data.BodyObject; import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.CrowdCodes;
import com.threerings.crowd.data.OccupantInfo; import com.threerings.crowd.data.OccupantInfo;
import com.threerings.crowd.data.PlaceObject; import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.server.CrowdServer; import com.threerings.crowd.server.CrowdServer;
@@ -58,32 +59,29 @@ public class ChatProvider
public static interface TellAutoResponder public static interface TellAutoResponder
{ {
/** /**
* Called following the delivery of <code>message</code> from * Called following the delivery of <code>message</code> from <code>teller</code> to
* <code>teller</code> to <code>tellee</code>. * <code>tellee</code>.
*/ */
public void sentTell (BodyObject teller, BodyObject tellee, public void sentTell (BodyObject teller, BodyObject tellee, String message);
String message);
} }
/** Used to forward tells between servers in a multi-server setup. */ /** Used to forward tells between servers in a multi-server setup. */
public static interface TellForwarder public static interface TellForwarder
{ {
/** /**
* Requests that the supplied tell message be delivered to the * Requests that the supplied tell message be delivered to the appropriate destination.
* appropriate destination.
* *
* @return true if the tell was delivered, false otherwise. * @return true if the tell was delivered, false otherwise.
*/ */
public boolean forwardTell (UserMessage message, Name target, public boolean forwardTell (UserMessage message, Name target, TellListener listener);
TellListener listener);
} }
/** /**
* Set an object to which all broadcasts should be sent, rather * Set an object to which all broadcasts should be sent, rather than iterating over the place
* than iterating over the place objects and sending to each of them. * objects and sending to each of them.
* *
* @param object an object to send all broadcasts, or null to send to * @param object an object to send all broadcasts, or null to send to each place object
* each place object instead. * instead.
*/ */
public void setAlternateBroadcastObject (DObject object) public void setAlternateBroadcastObject (DObject object)
{ {
@@ -91,11 +89,10 @@ public class ChatProvider
} }
/** /**
* Set the auto tell responder for the chat provider. Only one auto * Set the auto tell responder for the chat provider. Only one auto responder is allowed.
* responder is allowed. <em>Note:</em> this only works for same-server * <em>Note:</em> this only works for same-server tells. If the tell is forwarded to another
* tells. If the tell is forwarded to another server, no auto-response * server, no auto-response opportunity is provided (because we never have both body objects in
* opportunity is provided (because we never have both body objects in the * the same place).
* same place).
*/ */
public void setTellAutoResponder (TellAutoResponder autoRespond) public void setTellAutoResponder (TellAutoResponder autoRespond)
{ {
@@ -103,9 +100,8 @@ public class ChatProvider
} }
/** /**
* Configures the tell forwarded for the chat provider. This is used by the * Configures the tell forwarded for the chat provider. This is used by the Crowd peer services
* Crowd peer services to forward tells between servers in a multi-server * to forward tells between servers in a multi-server cluster.
* cluster.
*/ */
public void setTellForwarder (TellForwarder forwarder) public void setTellForwarder (TellForwarder forwarder)
{ {
@@ -113,21 +109,18 @@ public class ChatProvider
} }
/** /**
* Initializes the chat services and registers a chat provider with * Initializes the chat services and registers a chat provider with the invocation manager.
* the invocation manager.
*/ */
public void init (InvocationManager invmgr, DObjectManager omgr) public void init (InvocationManager invmgr, DObjectManager omgr)
{ {
// register a chat provider with the invocation manager // register a chat provider with the invocation manager
invmgr.registerDispatcher(new ChatDispatcher(this), true); invmgr.registerDispatcher(new ChatDispatcher(this), CrowdCodes.CROWD_GROUP);
} }
/** /**
* Processes a request from a client to deliver a tell message to * Processes a request from a client to deliver a tell message to another client.
* another client.
*/ */
public void tell (ClientObject caller, Name target, String message, public void tell (ClientObject caller, Name target, String message, TellListener listener)
TellListener listener)
throws InvocationException throws InvocationException
{ {
// ensure that the caller has normal chat privileges // ensure that the caller has normal chat privileges
@@ -142,8 +135,7 @@ public class ChatProvider
// inform the auto-responder if needed // inform the auto-responder if needed
BodyObject targobj; BodyObject targobj;
if (_autoRespond != null && if (_autoRespond != null && (targobj = CrowdServer.lookupBody(target)) != null) {
(targobj = CrowdServer.lookupBody(target)) != null) {
_autoRespond.sentTell(source, targobj, message); _autoRespond.sentTell(source, targobj, message);
} }
} }
@@ -151,8 +143,7 @@ public class ChatProvider
/** /**
* Processes a {@link ChatService#broadcast} request. * Processes a {@link ChatService#broadcast} request.
*/ */
public void broadcast (ClientObject caller, String message, public void broadcast (ClientObject caller, String message, InvocationListener listener)
InvocationListener listener)
throws InvocationException throws InvocationException
{ {
// make sure the requesting user has broadcast privileges // make sure the requesting user has broadcast privileges
@@ -170,20 +161,19 @@ public class ChatProvider
public void away (ClientObject caller, String message) public void away (ClientObject caller, String message)
{ {
BodyObject body = (BodyObject)caller; BodyObject body = (BodyObject)caller;
// we modify this field via an invocation service request because // we modify this field via an invocation service request because a body object is not
// a body object is not modifiable by the client // modifiable by the client
body.setAwayMessage(message); body.setAwayMessage(message);
} }
/** /**
* Broadcast the specified message to all places in the game. * Broadcast the specified message to all places in the game.
* *
* @param from the user the broadcast is from, or null to send the message * @param from the user the broadcast is from, or null to send the message as a system message.
* as a system message.
* @param bundle the bundle, or null if the message needs no translation. * @param bundle the bundle, or null if the message needs no translation.
* @param msg the content of the message to broadcast. * @param msg the content of the message to broadcast.
* @param attention if true, the message is sent as ATTENTION level, * @param attention if true, the message is sent as ATTENTION level, otherwise as INFO. Ignored
* otherwise as INFO. Ignored if from is non-null. * if from is non-null.
*/ */
public void broadcast (Name from, String bundle, String msg, public void broadcast (Name from, String bundle, String msg,
boolean attention) boolean attention)
@@ -203,12 +193,10 @@ public class ChatProvider
} }
/** /**
* Delivers a tell message to the specified target and notifies the * Delivers a tell message to the specified target and notifies the supplied listener of the
* supplied listener of the result. It is assumed that the teller has * result. It is assumed that the teller has already been permissions checked.
* already been permissions checked.
*/ */
public void deliverTell (UserMessage message, Name target, public void deliverTell (UserMessage message, Name target, TellListener listener)
TellListener listener)
throws InvocationException throws InvocationException
{ {
// make sure the target user is online // make sure the target user is online
@@ -225,8 +213,7 @@ public class ChatProvider
if (tobj.status == OccupantInfo.DISCONNECTED) { if (tobj.status == OccupantInfo.DISCONNECTED) {
String errmsg = MessageBundle.compose( String errmsg = MessageBundle.compose(
USER_DISCONNECTED, TimeUtil.getTimeOrderString( USER_DISCONNECTED, TimeUtil.getTimeOrderString(
System.currentTimeMillis() - tobj.statusTime, System.currentTimeMillis() - tobj.statusTime, TimeUtil.SECOND));
TimeUtil.SECOND));
throw new InvocationException(errmsg); throw new InvocationException(errmsg);
} }
@@ -246,9 +233,9 @@ public class ChatProvider
} }
/** /**
* Delivers a tell notification to the specified target player. It is * Delivers a tell notification to the specified target player. It is assumed that the message
* assumed that the message is coming from some server entity and need not * is coming from some server entity and need not be permissions checked or notified of the
* be permissions checked or notified of the result. * result.
*/ */
public void deliverTell (BodyObject target, UserMessage message) public void deliverTell (BodyObject target, UserMessage message)
{ {
@@ -269,8 +256,8 @@ public class ChatProvider
/** /**
* Direct a broadcast to the specified object. * Direct a broadcast to the specified object.
*/ */
protected void broadcastTo (DObject object, Name from, String bundle, protected void broadcastTo (DObject object, Name from, String bundle, String msg,
String msg, boolean attention) boolean attention)
{ {
if (from == null) { if (from == null) {
if (attention) { if (attention) {
@@ -280,8 +267,7 @@ public class ChatProvider
} }
} else { } else {
SpeakProvider.sendSpeak(object, from, bundle, msg, SpeakProvider.sendSpeak(object, from, bundle, msg, BROADCAST_MODE);
BROADCAST_MODE);
} }
} }
@@ -34,6 +34,7 @@ import com.threerings.presents.dobj.Subscriber;
import com.threerings.crowd.Log; import com.threerings.crowd.Log;
import com.threerings.crowd.data.BodyObject; import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.CrowdCodes;
import com.threerings.crowd.data.LocationCodes; import com.threerings.crowd.data.LocationCodes;
import com.threerings.crowd.data.PlaceConfig; import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject; import com.threerings.crowd.data.PlaceObject;
@@ -416,12 +417,17 @@ public class LocationDirector extends BasicDirector
_lservice = null; _lservice = null;
} }
@Override // from BasicDirector
protected void registerServices (Client client)
{
client.addServiceGroup(CrowdCodes.CROWD_GROUP);
}
// documentation inherited // documentation inherited
protected void fetchServices (Client client) protected void fetchServices (Client client)
{ {
// obtain our service handle // obtain our service handle
_lservice = (LocationService) _lservice = (LocationService)client.requireService(LocationService.class);
client.requireService(LocationService.class);
} }
protected void gotBodyObject (BodyObject clobj) protected void gotBodyObject (BodyObject clobj)
@@ -0,0 +1,33 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2006 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.data;
import com.threerings.presents.data.InvocationCodes;
/**
* Codes and constants global to the Crowd services.
*/
public interface CrowdCodes extends InvocationCodes
{
/** Defines our invocation services group. */
public static final String CROWD_GROUP = "crowd";
}
@@ -99,8 +99,7 @@ public class CrowdPeerManager extends PeerManager
// unregister our invocation service // unregister our invocation service
if (_nodeobj != null) { if (_nodeobj != null) {
CrowdServer.invmgr.clearDispatcher( CrowdServer.invmgr.clearDispatcher(((CrowdNodeObject)_nodeobj).crowdPeerService);
((CrowdNodeObject)_nodeobj).crowdPeerService);
} }
// clear our tell forwarder registration // clear our tell forwarder registration
@@ -136,7 +135,7 @@ public class CrowdPeerManager extends PeerManager
CrowdNodeObject cnobj = (CrowdNodeObject)_nodeobj; CrowdNodeObject cnobj = (CrowdNodeObject)_nodeobj;
cnobj.setCrowdPeerService( cnobj.setCrowdPeerService(
(CrowdPeerMarshaller)CrowdServer.invmgr.registerDispatcher( (CrowdPeerMarshaller)CrowdServer.invmgr.registerDispatcher(
new CrowdPeerDispatcher(this), false)); new CrowdPeerDispatcher(this)));
// register ourselves as a tell forwarder // register ourselves as a tell forwarder
CrowdServer.chatprov.setTellForwarder(this); CrowdServer.chatprov.setTellForwarder(this);
@@ -28,6 +28,7 @@ import com.threerings.presents.server.InvocationProvider;
import com.threerings.crowd.Log; import com.threerings.crowd.Log;
import com.threerings.crowd.data.BodyObject; import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.CrowdCodes;
import com.threerings.crowd.data.OccupantInfo; import com.threerings.crowd.data.OccupantInfo;
/** /**
@@ -54,7 +55,7 @@ public class BodyProvider
public static void init (InvocationManager invmgr) public static void init (InvocationManager invmgr)
{ {
// register a provider instance // register a provider instance
invmgr.registerDispatcher(new BodyDispatcher(new BodyProvider()), true); invmgr.registerDispatcher(new BodyDispatcher(new BodyProvider()), CrowdCodes.CROWD_GROUP);
} }
/** /**
@@ -59,35 +59,28 @@ import com.threerings.crowd.chat.server.SpeakDispatcher;
import com.threerings.crowd.chat.server.SpeakProvider; import com.threerings.crowd.chat.server.SpeakProvider;
/** /**
* The place manager is the server-side entity that handles all * The place manager is the server-side entity that handles all place-related interaction. It
* place-related interaction. It subscribes to the place object and reacts * subscribes to the place object and reacts to message and other events. Behavior specific to a
* to message and other events. Behavior specific to a place (or class of * place (or class of places) should live in the place manager. An intelligently constructed
* places) should live in the place manager. An intelligently constructed * hierarchy of place manager classes working in concert with invocation services should provide
* hierarchy of place manager classes working in concert with invocation * the majority of the server-side functionality of an application built on the Presents platform.
* services should provide the majority of the server-side functionality
* of an application built on the Presents platform.
* *
* <p> The base place manager class takes care of the necessary * <p> The base place manager class takes care of the necessary interactions with the place
* interactions with the place registry to manage place registration. It * registry to manage place registration. It handles the place-related component of chatting. It
* handles the place-related component of chatting. It also provides the * also provides the basis for place-based access control.
* basis for place-based access control.
* *
* <p> A derived class is expected to handle initialization, cleanup and * <p> A derived class is expected to handle initialization, cleanup and operational functionality
* operational functionality via the calldown functions {@link #didInit}, * via the calldown functions {@link #didInit}, {@link #didStartup}, and {@link #didShutdown} as
* {@link #didStartup}, and {@link #didShutdown} as well as through event * well as through event listeners.
* listeners.
*/ */
public class PlaceManager public class PlaceManager
implements MessageListener, OidListListener, ObjectDeathListener, implements MessageListener, OidListListener, ObjectDeathListener, SpeakProvider.SpeakerValidator
SpeakProvider.SpeakerValidator
{ {
/** /**
* An interface used to allow the registration of standard message handlers * An interface used to allow the registration of standard message handlers to be invoked by
* to be invoked by the place manager when particular types of message * the place manager when particular types of message events are received.
* events are received.
* *
* @deprecated Use dynamically bound methods instead. See {@link * @deprecated Use dynamically bound methods instead. See {@link DynamicListener}.
* DynamicListener}.
*/ */
@Deprecated @Deprecated
public static interface MessageHandler public static interface MessageHandler
@@ -96,8 +89,7 @@ public class PlaceManager
* Invokes this message handler on the supplied event. * Invokes this message handler on the supplied event.
* *
* @param event the message event received. * @param event the message event received.
* @param pmgr the place manager for which the message is being * @param pmgr the place manager for which the message is being handled.
* handled.
*/ */
public void handleEvent (MessageEvent event, PlaceManager pmgr); public void handleEvent (MessageEvent event, PlaceManager pmgr);
} }
@@ -119,9 +111,8 @@ public class PlaceManager
} }
/** /**
* Returns the occupant info record for the user with the specified * Returns the occupant info record for the user with the specified body oid, if they are an
* body oid, if they are an occupant of this room. Returns null * occupant of this room. Returns null otherwise.
* otherwise.
*/ */
public OccupantInfo getOccupantInfo (int bodyOid) public OccupantInfo getOccupantInfo (int bodyOid)
{ {
@@ -129,8 +120,7 @@ public class PlaceManager
} }
/** /**
* Applies the supplied occupant operation to each occupant currently * Applies the supplied occupant operation to each occupant currently present in this place.
* present in this place.
*/ */
public void applyToOccupants (OccupantOp op) public void applyToOccupants (OccupantOp op)
{ {
@@ -143,11 +133,10 @@ public class PlaceManager
} }
/** /**
* Updates the occupant info for this room occupant. <em>Note:</em> * Updates the occupant info for this room occupant. <em>Note:</em> This must be used rather
* This must be used rather than setting the occupant info directly to * than setting the occupant info directly to avoid possible complications due to rapid fire
* avoid possible complications due to rapid fire changes to a user's * changes to a user's occupant info. The occupant info record supplied to this method must be
* occupant info. The occupant info record supplied to this method * one returned from {@link #getOccupantInfo}. For example:
* must be one returned from {@link #getOccupantInfo}. For example:
* *
* <pre> * <pre>
* OccupantInfo info = _plmgr.getOccupantInfo(bodyOid); * OccupantInfo info = _plmgr.getOccupantInfo(bodyOid);
@@ -159,17 +148,16 @@ public class PlaceManager
{ {
// update the canonical copy // update the canonical copy
_occInfo.put(occInfo.getBodyOid(), occInfo); _occInfo.put(occInfo.getBodyOid(), occInfo);
// clone the canonical copy and send out an event updating the // clone the canonical copy and send out an event updating the distributed set with that
// distributed set with that clone // clone
_plobj.updateOccupantInfo((OccupantInfo)occInfo.clone()); _plobj.updateOccupantInfo((OccupantInfo)occInfo.clone());
} }
/** /**
* Called by the place registry after creating this place manager. * Called by the place registry after creating this place manager.
*/ */
public void init ( public void init (PlaceRegistry registry, InvocationManager invmgr,
PlaceRegistry registry, InvocationManager invmgr, DObjectManager omgr, PlaceConfig config)
DObjectManager omgr, PlaceConfig config)
{ {
_registry = registry; _registry = registry;
_invmgr = invmgr; _invmgr = invmgr;
@@ -181,11 +169,10 @@ public class PlaceManager
} }
/** /**
* Called after this place manager has been initialized with its * Called after this place manager has been initialized with its configuration information but
* configuration information but before it has been started up with * before it has been started up with its place object reference. Derived classes can override
* its place object reference. Derived classes can override this * this function and perform any basic initialization that they desire. They should of course
* function and perform any basic initialization that they desire. * be sure to call <code>super.didInit()</code>.
* They should of course be sure to call <code>super.didInit()</code>.
*/ */
protected void didInit () protected void didInit ()
{ {
@@ -209,14 +196,12 @@ public class PlaceManager
} }
/** /**
* Provides an opportunity for place managers to ratify the creation * Provides an opportunity for place managers to ratify the creation of a place based on
* of a place based on whatever criterion they may require (based on * whatever criterion they may require (based on information available to the manager at this
* information available to the manager at this post-init() but * post-init() but pre-startup() phase of initialization).
* pre-startup() phase of initialization).
* *
* @return If a permissions check is to fail, the manager should * @return If a permissions check is to fail, the manager should return a translatable string
* return a translatable string explaining the failure. * explaining the failure. <code>null</code> should be returned if initialization is to be
* <code>null</code> should be returned if initialization is to be
* allowed to proceed. * allowed to proceed.
*/ */
public String checkPermissions () public String checkPermissions ()
@@ -225,19 +210,18 @@ public class PlaceManager
} }
/** /**
* Called by the place manager after the place object has been * Called by the place manager after the place object has been successfully created.
* successfully created.
*/ */
public void startup (PlaceObject plobj) public void startup (PlaceObject plobj)
{ {
// keep track of this // keep track of this
_plobj = plobj; _plobj = plobj;
// we usually want to create and register a speaker service instance // we usually want to create and register a speaker service instance that clients can use
// that clients can use to speak in this place // to speak in this place
if (shouldCreateSpeakService()) { if (shouldCreateSpeakService()) {
plobj.setSpeakService((SpeakMarshaller) _invmgr.registerDispatcher( plobj.setSpeakService((SpeakMarshaller)_invmgr.registerDispatcher(
new SpeakDispatcher(new SpeakProvider(plobj, this)), false)); new SpeakDispatcher(new SpeakProvider(plobj, this))));
} }
// we'll need to hear about place object events // we'll need to hear about place object events
@@ -255,8 +239,8 @@ public class PlaceManager
} }
/** /**
* Causes the place object being managed by this place manager to be * Causes the place object being managed by this place manager to be destroyed and the place
* destroyed and the place manager to shut down. * manager to shut down.
*/ */
public void shutdown () public void shutdown ()
{ {
@@ -273,12 +257,10 @@ public class PlaceManager
} }
/** /**
* Provides an opportunity for the place manager to prevent bodies from * Provides an opportunity for the place manager to prevent bodies from entering.
* entering.
* *
* @return <code>null</code> if the body can enter, otherwise a * @return <code>null</code> if the body can enter, otherwise a translatable message explaining
* translatable message explaining the reason the body is blocked * the reason the body is blocked from entering
* from entering
*/ */
public String ratifyBodyEntry (BodyObject body) public String ratifyBodyEntry (BodyObject body)
{ {
@@ -286,12 +268,11 @@ public class PlaceManager
} }
/** /**
* Builds an {@link OccupantInfo} record for the specified body object and * Builds an {@link OccupantInfo} record for the specified body object and inserts it into our
* inserts it into our place object. This is called by the location * place object. This is called by the location services when a body enters a place. If a
* services when a body enters a place. If a derived class wishes to * derived class wishes to perform custom actions when an occupant is being inserted into a
* perform custom actions when an occupant is being inserted into a room, * room, they should override {@link #insertOccupantInfo}, if they want to react to a body
* they should override {@link #insertOccupantInfo}, if they want to react * having entered, they should override {@link #bodyEntered}.
* to a body having entered, they should override {@link #bodyEntered}.
*/ */
public OccupantInfo buildOccupantInfo (BodyObject body) public OccupantInfo buildOccupantInfo (BodyObject body)
{ {
@@ -299,8 +280,8 @@ public class PlaceManager
// create a new occupant info instance // create a new occupant info instance
OccupantInfo info = body.createOccupantInfo(_plobj); OccupantInfo info = body.createOccupantInfo(_plobj);
// insert the occupant info into our canonical table; this is done // insert the occupant info into our canonical table; this is done in a method so that
// in a method so that derived classes // derived classes
insertOccupantInfo(info, body); insertOccupantInfo(info, body);
// clone the canonical copy and insert it into the DSet // clone the canonical copy and insert it into the DSet
@@ -309,23 +290,21 @@ public class PlaceManager
return info; return info;
} catch (Exception e) { } catch (Exception e) {
Log.warning("Failure building occupant info " + Log.warning("Failure building occupant info [where=" + where() +
"[where=" + where() + ", body=" + body + "]."); ", body=" + body + "].");
Log.logStackTrace(e); Log.logStackTrace(e);
return null; return null;
} }
} }
/** /**
* Registers a particular message handler instance to be used when * Registers a particular message handler instance to be used when processing message events
* processing message events with the specified name. * with the specified name.
* *
* @param name the message name of the message events that should be * @param name the message name of the message events that should be handled by this handler.
* handled by this handler.
* @param handler the handler to be registered. * @param handler the handler to be registered.
* *
* @deprecated Use dynamically bound methods instead. See {@link * @deprecated Use dynamically bound methods instead. See {@link DynamicListener}.
* DynamicListener}.
*/ */
@Deprecated @Deprecated
public void registerMessageHandler (String name, MessageHandler handler) public void registerMessageHandler (String name, MessageHandler handler)
@@ -348,11 +327,10 @@ public class PlaceManager
handler.handleEvent(event, this); handler.handleEvent(event, this);
} }
// the first argument should be the client object of the caller or null // the first argument should be the client object of the caller or null if it is a server
// if it is a server originated event // originated event
int srcoid = event.getSourceOid(); int srcoid = event.getSourceOid();
DObject source = (srcoid <= 0) ? DObject source = (srcoid <= 0) ? null : CrowdServer.omgr.getObject(srcoid);
null : CrowdServer.omgr.getObject(srcoid);
Object[] args = event.getArgs(), nargs; Object[] args = event.getArgs(), nargs;
if (args == null) { if (args == null) {
nargs = new Object[] { source }; nargs = new Object[] { source };
@@ -391,30 +369,26 @@ public class PlaceManager
} }
// documentation inherited from interface // documentation inherited from interface
public boolean isValidSpeaker ( public boolean isValidSpeaker (DObject speakObj, ClientObject speaker, byte mode)
DObject speakObj, ClientObject speaker, byte mode)
{ {
// only allow people in the room to speak // only allow people in the room to speak
return _plobj.occupants.contains(speaker.getOid()); return _plobj.occupants.contains(speaker.getOid());
} }
/** /**
* Returns a string that can be used in log messages to identify the * Returns a string that can be used in log messages to identify the place as sensibly as
* place as sensibly as possible to the developer who has to puzzle * possible to the developer who has to puzzle over log output trying to figure out what's
* over log output trying to figure out what's going on. Derived place * going on. Derived place managers can override this and augment the default value (which is
* managers can override this and augment the default value (which is
* simply the place object id) with useful identifying information. * simply the place object id) with useful identifying information.
*/ */
public String where () public String where ()
{ {
return (_plobj == null) ? return (_plobj == null) ? StringUtil.shortClassName(this) + ":-1" : _plobj.which();
StringUtil.shortClassName(this) + ":-1" : _plobj.which();
} }
/** /**
* Generates a string representation of this manager. Does so in a way * Generates a string representation of this manager. Does so in a way that makes it easier for
* that makes it easier for derived classes to add to the string * derived classes to add to the string representation.
* representation.
* *
* @see #toString(StringBuilder) * @see #toString(StringBuilder)
*/ */
@@ -428,8 +402,8 @@ public class PlaceManager
} }
/** /**
* Derived classes will generally override this method to create a custom * Derived classes will generally override this method to create a custom {@link PlaceObject}
* {@link PlaceObject} derivation that contains extra information. * derivation that contains extra information.
*/ */
protected PlaceObject createPlaceObject () protected PlaceObject createPlaceObject ()
{ {
@@ -450,17 +424,16 @@ public class PlaceManager
} }
/** /**
* Called if the permissions check failed, to give place managers a * Called if the permissions check failed, to give place managers a chance to do any cleanup
* chance to do any cleanup that might be necessary due to their early * that might be necessary due to their early initialization or permissions checking code.
* initialization or permissions checking code.
*/ */
protected void permissionsFailed () protected void permissionsFailed ()
{ {
} }
/** /**
* @return true if we should create a speaker service for our place object * @return true if we should create a speaker service for our place object so that clients can
* so that clients can use it to speak in this place. * use it to speak in this place.
*/ */
protected boolean shouldCreateSpeakService () protected boolean shouldCreateSpeakService ()
{ {
@@ -468,8 +441,8 @@ public class PlaceManager
} }
/** /**
* Creates an access controller for this place's distributed object, which * Creates an access controller for this place's distributed object, which by default is {@link
* by default is {@link CrowdObjectAccess#PLACE}. * CrowdObjectAccess#PLACE}.
*/ */
protected AccessController getAccessController () protected AccessController getAccessController ()
{ {
@@ -477,10 +450,9 @@ public class PlaceManager
} }
/** /**
* Derived classes should override this (and be sure to call * Derived classes should override this (and be sure to call <code>super.didStartup()</code>)
* <code>super.didStartup()</code>) to perform any startup time * to perform any startup time initialization. The place object will be available by the time
* initialization. The place object will be available by the time this * this method is executed.
* method is executed.
*/ */
protected void didStartup () protected void didStartup ()
{ {
@@ -493,10 +465,9 @@ public class PlaceManager
} }
/** /**
* Called when this place has been destroyed and the place manager has * Called when this place has been destroyed and the place manager has shut down (via a call to
* shut down (via a call to {@link #shutdown}). Derived classes can * {@link #shutdown}). Derived classes can override this method and perform any necessary
* override this method and perform any necessary shutdown time * shutdown time processing.
* processing.
*/ */
protected void didShutdown () protected void didShutdown ()
{ {
@@ -509,10 +480,9 @@ public class PlaceManager
} }
/** /**
* Called when an occupant is being added to this place. This will be * Called when an occupant is being added to this place. This will be before the call to {@link
* before the call to {@link #bodyEntered} and gives the derived class a * #bodyEntered} and gives the derived class a chance to set up additional information about
* chance to set up additional information about the occupant that might * the occupant that might not be tracked in the occupant info.
* not be tracked in the occupant info.
*/ */
protected void insertOccupantInfo (OccupantInfo info, BodyObject body) protected void insertOccupantInfo (OccupantInfo info, BodyObject body)
{ {
@@ -525,8 +495,7 @@ public class PlaceManager
protected void bodyEntered (final int bodyOid) protected void bodyEntered (final int bodyOid)
{ {
if (Log.log.getLevel() == Level.FINE) { if (Log.log.getLevel() == Level.FINE) {
Log.debug("Body entered [where=" + where() + Log.debug("Body entered [where=" + where() + ", oid=" + bodyOid + "].");
", oid=" + bodyOid + "].");
} }
// let our delegates know what's up // let our delegates know what's up
@@ -546,13 +515,11 @@ public class PlaceManager
protected void bodyLeft (final int bodyOid) protected void bodyLeft (final int bodyOid)
{ {
if (Log.log.getLevel() == Level.FINE) { if (Log.log.getLevel() == Level.FINE) {
Log.debug("Body left [where=" + where() + Log.debug("Body left [where=" + where() + ", oid=" + bodyOid + "].");
", oid=" + bodyOid + "].");
} }
// if their occupant info hasn't been removed (which may be the // if their occupant info hasn't been removed (which may be the case if they logged off
// case if they logged off rather than left via a MoveTo request), // rather than left via a MoveTo request), we need to get it on out of here
// we need to get it on out of here
Integer key = Integer.valueOf(bodyOid); Integer key = Integer.valueOf(bodyOid);
if (_plobj.occupantInfo.containsKey(key)) { if (_plobj.occupantInfo.containsKey(key)) {
_plobj.removeFromOccupantInfo(key); _plobj.removeFromOccupantInfo(key);
@@ -575,8 +542,7 @@ public class PlaceManager
} }
/** /**
* Returns whether the location should be marked as empty and potentially * Returns whether the location should be marked as empty and potentially shutdown.
* shutdown.
*/ */
protected boolean shouldDeclareEmpty (OccupantInfo leaver) protected boolean shouldDeclareEmpty (OccupantInfo leaver)
{ {
@@ -597,10 +563,9 @@ public class PlaceManager
} }
/** /**
* Called when we transition from having bodies in the place to not * Called when we transition from having bodies in the place to not having any bodies in the
* having any bodies in the place. Some places may take this as a sign * place. Some places may take this as a sign to pack it in, others may wish to stick
* to pack it in, others may wish to stick around. In any case, they * around. In any case, they can override this method to do their thing.
* can override this method to do their thing.
*/ */
protected void placeBecameEmpty () protected void placeBecameEmpty ()
{ {
@@ -646,9 +611,9 @@ public class PlaceManager
} }
/** /**
* Returns the period (in milliseconds) of emptiness after which this * Returns the period (in milliseconds) of emptiness after which this place manager will unload
* place manager will unload itself and shutdown. Returning * itself and shutdown. Returning <code>0</code> indicates that the place should never be
* <code>0</code> indicates that the place should never be shutdown. * shutdown.
*/ */
protected long idleUnloadPeriod () protected long idleUnloadPeriod ()
{ {
@@ -656,9 +621,8 @@ public class PlaceManager
} }
/** /**
* An extensible way to add to the string representation of this * An extensible way to add to the string representation of this class. Override this (being
* class. Override this (being sure to call super) and append your * sure to call super) and append your info to the buffer.
* info to the buffer.
*/ */
protected void toString (StringBuilder buf) protected void toString (StringBuilder buf)
{ {
@@ -699,8 +663,7 @@ public class PlaceManager
/** A reference to the place registry with which we're registered. */ /** A reference to the place registry with which we're registered. */
protected PlaceRegistry _registry; protected PlaceRegistry _registry;
/** The invocation manager with whom we register our game invocation /** The invocation manager with whom we register our game invocation services. */
* services. */
protected InvocationManager _invmgr; protected InvocationManager _invmgr;
/** A distributed object manager for doing dobj stuff. */ /** A distributed object manager for doing dobj stuff. */
@@ -719,12 +682,10 @@ public class PlaceManager
protected ArrayList<PlaceManagerDelegate> _delegates; protected ArrayList<PlaceManagerDelegate> _delegates;
/** Used to keep a canonical copy of the occupant info records. */ /** Used to keep a canonical copy of the occupant info records. */
protected HashIntMap<OccupantInfo> _occInfo = protected HashIntMap<OccupantInfo> _occInfo = new HashIntMap<OccupantInfo>();
new HashIntMap<OccupantInfo>();
/** The interval currently registered to shut this place /** The interval currently registered to shut this place down after a certain period of
* down after a certain period of idility, or null if no * idility, or null if no interval is currently registered. */
* interval is currently registered. */
protected Interval _shutdownInterval; protected Interval _shutdownInterval;
/** Used to do method lookup magic when we receive message events. */ /** Used to do method lookup magic when we receive message events. */
@@ -30,6 +30,7 @@ import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationManager; import com.threerings.presents.server.InvocationManager;
import com.threerings.crowd.Log; import com.threerings.crowd.Log;
import com.threerings.crowd.data.CrowdCodes;
import com.threerings.crowd.data.PlaceConfig; import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject; import com.threerings.crowd.data.PlaceObject;
@@ -59,7 +60,7 @@ public class PlaceRegistry
{ {
// create and register our location provider // create and register our location provider
locprov = new LocationProvider(invmgr, omgr, this); locprov = new LocationProvider(invmgr, omgr, this);
invmgr.registerDispatcher(new LocationDispatcher(locprov), true); invmgr.registerDispatcher(new LocationDispatcher(locprov), CrowdCodes.CROWD_GROUP);
// we'll need these later // we'll need these later
_omgr = omgr; _omgr = omgr;
@@ -24,19 +24,17 @@ package com.threerings.presents.client;
import com.threerings.presents.util.PresentsContext; import com.threerings.presents.util.PresentsContext;
/** /**
* Handles functionality common to nearly all client directors. They * Handles functionality common to nearly all client directors. They generally need to be session
* generally need to be session observers so that they can set themselves * observers so that they can set themselves up when the client logs on (by overriding {@link
* up when the client logs on (by overriding {@link #clientDidLogon}) and * #clientDidLogon}) and clean up after themselves when the client logs off (by overriding {@link
* clean up after themselves when the client logs off (by overriding * #clientDidLogoff}).
* {@link #clientDidLogoff}).
*/ */
public class BasicDirector public class BasicDirector
implements SessionObserver implements SessionObserver
{ {
/** /**
* Derived directors will need to provide the basic director with a * Derived directors will need to provide the basic director with a context that it can use to
* context that it can use to register itself with the necessary * register itself with the necessary entities.
* entities.
*/ */
protected BasicDirector (PresentsContext ctx) protected BasicDirector (PresentsContext ctx)
{ {
@@ -56,6 +54,12 @@ public class BasicDirector
} }
} }
// documentation inherited from interface
public void clientWillLogon (Client client)
{
registerServices(client);
}
// documentation inherited from interface // documentation inherited from interface
public void clientDidLogon (Client client) public void clientDidLogon (Client client)
{ {
@@ -85,8 +89,7 @@ public class BasicDirector
} }
/** /**
* Checks whether or not this director is available in standalone mode * Checks whether or not this director is available in standalone mode (defaults to false).
* (defaults to false).
*/ */
public boolean isAvailableInStandalone () public boolean isAvailableInStandalone ()
{ {
@@ -102,8 +105,7 @@ public class BasicDirector
} }
/** /**
* If this director is not currently available, throws a * If this director is not currently available, throws a {@link RuntimeException}.
* {@link RuntimeException}.
*/ */
protected void assertAvailable () protected void assertAvailable ()
{ {
@@ -114,20 +116,27 @@ public class BasicDirector
} }
/** /**
* Called in three circumstances: when a director is created and we've * Called in three circumstances: when a director is created and we've already logged on; when
* already logged on; when we first log on and when the client object * we first log on and when the client object changes after we've already logged on.
* changes after we've already logged on.
*/ */
protected void clientObjectUpdated (Client client) protected void clientObjectUpdated (Client client)
{ {
} }
/** /**
* Derived directors can override this method and obtain any services * If a director makes use of bootstrap invocation services which are part of a bootstrap
* they'll need during their operation via calls to {@link * service group, it should register interest in that group here with a call to {@link
* Client#getService}. If the director is available, it will automatically * Client#addServiceGroup}.
* be called when the client logs on or when the director is constructed */
* if it is constructed after the client is already logged on. protected void registerServices (Client client)
{
}
/**
* Derived directors can override this method and obtain any services they'll need during their
* operation via calls to {@link Client#getService}. If the director is available, it will
* automatically be called when the client logs on or when the director is constructed if it is
* constructed after the client is already logged on.
*/ */
protected void fetchServices (Client client) protected void fetchServices (Client client)
{ {
@@ -30,6 +30,7 @@ import com.samskivert.util.RunQueue;
import com.threerings.presents.Log; import com.threerings.presents.Log;
import com.threerings.presents.data.ClientObject; import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.InvocationCodes;
import com.threerings.presents.dobj.DObjectManager; import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.net.AuthResponseData; import com.threerings.presents.net.AuthResponseData;
import com.threerings.presents.net.BootstrapData; import com.threerings.presents.net.BootstrapData;
@@ -44,8 +45,7 @@ import com.threerings.presents.net.PongResponse;
*/ */
public class Client public class Client
{ {
/** The default ports on which the server listens for client /** The default ports on which the server listens for client connections. */
* connections. */
public static final int[] DEFAULT_SERVER_PORTS = { 47624 }; public static final int[] DEFAULT_SERVER_PORTS = { 47624 };
/** /**
@@ -193,16 +193,6 @@ public class Client
} }
} }
/**
* Marks this client as interested in the specified bootstrap services group. Any services
* registered as bootstrap services with the supplied group name will be included in this
* clients bootstrap services set. This must be called before {@link #logon}.
*/
public void addBootstrapGroup (String group)
{
_bootGroups.add(group);
}
/** /**
* Returns the data associated with our authentication response. Users of the Presents system * Returns the data associated with our authentication response. Users of the Presents system
* may wish to communicate authentication related information to their client by extending and * may wish to communicate authentication related information to their client by extending and
@@ -269,6 +259,16 @@ public class Client
return _invdir; return _invdir;
} }
/**
* Marks this client as interested in the specified bootstrap services group. Any services
* registered as bootstrap services with the supplied group name will be included in this
* clients bootstrap services set. This must be called before {@link #logon}.
*/
public void addServiceGroup (String group)
{
_bootGroups.add(group);
}
/** /**
* Returns the first bootstrap service that could be located that implements the supplied * Returns the first bootstrap service that could be located that implements the supplied
* {@link InvocationService} derivation. <code>null</code> is returned if no such service * {@link InvocationService} derivation. <code>null</code> is returned if no such service
@@ -281,8 +281,7 @@ public class Client
} }
int scount = _bstrap.services.size(); int scount = _bstrap.services.size();
for (int ii = 0; ii < scount; ii++) { for (int ii = 0; ii < scount; ii++) {
InvocationService service = (InvocationService) InvocationService service = (InvocationService)_bstrap.services.get(ii);
_bstrap.services.get(ii);
if (sclass.isInstance(service)) { if (sclass.isInstance(service)) {
return service; return service;
} }
@@ -300,8 +299,7 @@ public class Client
InvocationService isvc = getService(sclass); InvocationService isvc = getService(sclass);
if (isvc == null) { if (isvc == null) {
throw new RuntimeException( throw new RuntimeException(
sclass.getName() + " isn't available. " + sclass.getName() + " isn't available. I can't bear to go on.");
"I can't bear to go on.");
} }
return isvc; return isvc;
} }
@@ -744,7 +742,9 @@ public class Client
protected BootstrapData _bstrap; protected BootstrapData _bstrap;
/** The set of bootstrap service groups this client cares about. */ /** The set of bootstrap service groups this client cares about. */
protected HashSet<String> _bootGroups = new HashSet<String>(); protected HashSet<String> _bootGroups = new HashSet<String>(); {
_bootGroups.add(InvocationCodes.GLOBAL_GROUP);
}
/** Manages invocation services. */ /** Manages invocation services. */
protected InvocationDirector _invdir = new InvocationDirector(); protected InvocationDirector _invdir = new InvocationDirector();
@@ -32,6 +32,11 @@ package com.threerings.presents.client;
*/ */
public class ClientAdapter implements ClientObserver public class ClientAdapter implements ClientObserver
{ {
// documentation inherited
public void clientWillLogon (Client client)
{
}
// documentation inherited // documentation inherited
public void clientDidLogon (Client client) public void clientDidLogon (Client client)
{ {
@@ -30,6 +30,11 @@ package com.threerings.presents.client;
*/ */
public interface SessionObserver public interface SessionObserver
{ {
/**
* Called immediately before a logon is attempted.
*/
public void clientWillLogon (Client client);
/** /**
* Called after the client successfully connected to and authenticated * Called after the client successfully connected to and authenticated
* with the server. The entire object system is up and running by the * with the server. The entire object system is up and running by the
@@ -29,6 +29,10 @@ package com.threerings.presents.data;
*/ */
public interface InvocationCodes public interface InvocationCodes
{ {
/** Defines a global invocation services group that can be used by clients and services that do
* not care to make a distinction between groups of invocation services. */
public static final String GLOBAL_GROUP = "presents";
/** An error code returned to clients when a service cannot be performed because of some /** An error code returned to clients when a service cannot be performed because of some
* internal server error that we couldn't explain in any meaningful way (things like null * internal server error that we couldn't explain in any meaningful way (things like null
* pointer exceptions). */ * pointer exceptions). */
@@ -160,8 +160,7 @@ public class PeerManager
// set the invocation service // set the invocation service
_nodeobj.setPeerService( _nodeobj.setPeerService(
(PeerMarshaller)PresentsServer.invmgr.registerDispatcher( (PeerMarshaller)PresentsServer.invmgr.registerDispatcher(new PeerDispatcher(this)));
new PeerDispatcher(this), false));
// register ourselves as a client observer // register ourselves as a client observer
PresentsServer.clmgr.addClientObserver(this); PresentsServer.clmgr.addClientObserver(this);
@@ -668,6 +667,12 @@ public class PeerManager
log.warning("Peer connection failed " + _record + ": " + cause); log.warning("Peer connection failed " + _record + ": " + cause);
} }
// documentation inherited from interface ClientObserver
public void clientWillLogon (Client client)
{
// nothing doing
}
// documentation inherited from interface ClientObserver // documentation inherited from interface ClientObserver
public void clientDidLogon (Client client) public void clientDidLogon (Client client)
{ {
@@ -62,9 +62,6 @@ import java.util.HashMap;
public class InvocationManager public class InvocationManager
implements EventListener implements EventListener
{ {
/** Defines the name of the global bootstrap group. */
public static final String GLOBAL_BOOTSTRAP_GROUP = "global";
/** A mapping from bootstrap group to lists of services that are to be provided to clients at /** A mapping from bootstrap group to lists of services that are to be provided to clients at
* boot time. Don't mess with these lists! */ * boot time. Don't mess with these lists! */
public HashMap<String,StreamableArrayList<InvocationMarshaller>> bootlists = public HashMap<String,StreamableArrayList<InvocationMarshaller>> bootlists =
@@ -100,13 +97,10 @@ public class InvocationManager
* send requests to the provider for whom the dispatcher is proxying. * send requests to the provider for whom the dispatcher is proxying.
* *
* @param dispatcher the dispatcher to be registered. * @param dispatcher the dispatcher to be registered.
* @param bootstrap if true, the service instance will be added to the list of invocation
* service objects provided to the client in the bootstrap data.
*/ */
public InvocationMarshaller registerDispatcher ( public InvocationMarshaller registerDispatcher (InvocationDispatcher dispatcher)
InvocationDispatcher dispatcher, boolean bootstrap)
{ {
return registerDispatcher(dispatcher, bootstrap ? GLOBAL_BOOTSTRAP_GROUP : null); return registerDispatcher(dispatcher, null);
} }
/** /**
@@ -140,8 +134,7 @@ public class InvocationManager
list.add(marsh); list.add(marsh);
} }
_recentRegServices.put(Integer.valueOf(invCode), _recentRegServices.put(Integer.valueOf(invCode), marsh.getClass().getName());
marsh.getClass().getName());
// Log.info("Registered service [marsh=" + marsh + "]."); // Log.info("Registered service [marsh=" + marsh + "].");
return marsh; return marsh;
@@ -292,8 +285,7 @@ public class InvocationManager
/** The distributed object manager with which we're working. */ /** The distributed object manager with which we're working. */
protected RootDObjectManager _omgr; protected RootDObjectManager _omgr;
/** The object id of the object on which we receive invocation service /** The object id of the object on which we receive invocation service requests. */
* requests. */
protected int _invoid = -1; protected int _invoid = -1;
/** Used to generate monotonically increasing provider ids. */ /** Used to generate monotonically increasing provider ids. */
@@ -303,7 +295,7 @@ public class InvocationManager
protected HashIntMap<InvocationDispatcher> _dispatchers = protected HashIntMap<InvocationDispatcher> _dispatchers =
new HashIntMap<InvocationDispatcher>(); new HashIntMap<InvocationDispatcher>();
/** The text that is appended to the procedure name when automatically /** The text that is appended to the procedure name when automatically generating a failure
* generating a failure response. */ * response. */
protected static final String FAILED_SUFFIX = "Failed"; protected static final String FAILED_SUFFIX = "Failed";
} }
@@ -651,13 +651,9 @@ public class PresentsClient
// fill in the list of bootstrap services // fill in the list of bootstrap services
data.services = new StreamableArrayList<InvocationMarshaller>(); data.services = new StreamableArrayList<InvocationMarshaller>();
StreamableArrayList<InvocationMarshaller> list =
PresentsServer.invmgr.bootlists.get(InvocationManager.GLOBAL_BOOTSTRAP_GROUP);
if (list != null) {
data.services.addAll(list);
}
for (String group : _areq.getBootGroups()) { for (String group : _areq.getBootGroups()) {
list = PresentsServer.invmgr.bootlists.get(group); StreamableArrayList<InvocationMarshaller> list =
PresentsServer.invmgr.bootlists.get(group);
if (list != null) { if (list != null) {
data.services.addAll(list); data.services.addAll(list);
} }
@@ -34,17 +34,16 @@ import com.threerings.presents.data.TimeBaseObject;
import com.threerings.presents.dobj.RootDObjectManager; import com.threerings.presents.dobj.RootDObjectManager;
/** /**
* Provides the server-side of the time base services. The time base * Provides the server-side of the time base services. The time base services provide a means by
* services provide a means by which delta times can be sent over the * which delta times can be sent over the network which are expanded based on a shared base time
* network which are expanded based on a shared base time into full time * into full time stamps.
* stamps.
*/ */
public class TimeBaseProvider public class TimeBaseProvider
implements InvocationProvider, TimeBaseCodes implements InvocationProvider, TimeBaseCodes
{ {
/** /**
* Registers the time provider with the appropriate managers. Called * Registers the time provider with the appropriate managers. Called by the presents server at
* by the presents server at startup. * startup.
*/ */
public static void init (InvocationManager invmgr, RootDObjectManager omgr) public static void init (InvocationManager invmgr, RootDObjectManager omgr)
{ {
@@ -53,13 +52,12 @@ public class TimeBaseProvider
_omgr = omgr; _omgr = omgr;
// register a provider instance // register a provider instance
invmgr.registerDispatcher( invmgr.registerDispatcher(new TimeBaseDispatcher(new TimeBaseProvider()), GLOBAL_GROUP);
new TimeBaseDispatcher(new TimeBaseProvider()), true);
} }
/** /**
* Creates a time base object which can subsequently be fetched by the * Creates a time base object which can subsequently be fetched by the client and used to send
* client and used to send delta times. * delta times.
* *
* @param timeBase the name of the time base to create. * @param timeBase the name of the time base to create.
* *
@@ -73,8 +71,8 @@ public class TimeBaseProvider
} }
/** /**
* Returns the named timebase object, or null if no time base object * Returns the named timebase object, or null if no time base object has been created with that
* has been created with that name. * name.
*/ */
public static TimeBaseObject getTimeBase (String timeBase) public static TimeBaseObject getTimeBase (String timeBase)
{ {
@@ -82,11 +80,9 @@ public class TimeBaseProvider
} }
/** /**
* Processes a request from a client to fetch the oid of the specified * Processes a request from a client to fetch the oid of the specified time object.
* time object.
*/ */
public void getTimeOid ( public void getTimeOid (ClientObject source, String timeBase, GotTimeBaseListener listener)
ClientObject source, String timeBase, GotTimeBaseListener listener)
throws InvocationException throws InvocationException
{ {
// look up the time base object in question // look up the time base object in question
@@ -100,6 +100,11 @@ public class JabberClient
return _ctx; return _ctx;
} }
// documentation inherited from interface SessionObserver
public void clientWillLogon (Client client)
{
}
// documentation inherited from interface SessionObserver // documentation inherited from interface SessionObserver
public void clientDidLogon (Client client) public void clientDidLogon (Client client)
{ {
@@ -83,6 +83,10 @@ public class TestClient
return _main == Thread.currentThread(); return _main == Thread.currentThread();
} }
public void clientWillLogon (Client client)
{
}
public void clientDidLogon (Client client) public void clientDidLogon (Client client)
{ {
Log.info("Client did logon [client=" + client + "]."); Log.info("Client did logon [client=" + client + "].");
@@ -67,6 +67,11 @@ public class TestClient
} }
} }
public void clientWillLogon (Client client)
{
client.addServiceGroup("test");
}
public void clientDidLogon (Client client) public void clientDidLogon (Client client)
{ {
Log.info("Client did logon [client=" + client + "]."); Log.info("Client did logon [client=" + client + "].");
@@ -35,7 +35,7 @@ public class TestServer extends PresentsServer
super.init(); super.init();
// register our test provider // register our test provider
invmgr.registerDispatcher(new TestDispatcher(new TestManager()), true); invmgr.registerDispatcher(new TestDispatcher(new TestManager()), "test");
// create a test object // create a test object
testobj = omgr.registerObject(new TestObject()); testobj = omgr.registerObject(new TestObject());