ca89a3c122
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@2935 542714f4-19e9-0310-aa3c-eee0fc999fb1
681 lines
22 KiB
Java
681 lines
22 KiB
Java
//
|
|
// $Id: PlaceManager.java,v 1.49 2004/01/11 17:43:12 ray Exp $
|
|
|
|
package com.threerings.crowd.server;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.Properties;
|
|
|
|
import com.samskivert.util.HashIntMap;
|
|
import com.samskivert.util.IntervalManager;
|
|
import com.samskivert.util.StringUtil;
|
|
|
|
import com.threerings.presents.data.ClientObject;
|
|
import com.threerings.presents.server.InvocationManager;
|
|
import com.threerings.presents.server.PresentsDObjectMgr;
|
|
import com.threerings.presents.server.util.SafeInterval;
|
|
|
|
import com.threerings.presents.dobj.DObject;
|
|
import com.threerings.presents.dobj.DObjectManager;
|
|
import com.threerings.presents.dobj.EntryAddedEvent;
|
|
import com.threerings.presents.dobj.EntryRemovedEvent;
|
|
import com.threerings.presents.dobj.EntryUpdatedEvent;
|
|
import com.threerings.presents.dobj.MessageEvent;
|
|
import com.threerings.presents.dobj.MessageListener;
|
|
import com.threerings.presents.dobj.ObjectAddedEvent;
|
|
import com.threerings.presents.dobj.ObjectDeathListener;
|
|
import com.threerings.presents.dobj.ObjectDestroyedEvent;
|
|
import com.threerings.presents.dobj.ObjectRemovedEvent;
|
|
import com.threerings.presents.dobj.OidListListener;
|
|
import com.threerings.presents.dobj.SetListener;
|
|
|
|
import com.threerings.crowd.Log;
|
|
import com.threerings.crowd.data.BodyObject;
|
|
import com.threerings.crowd.data.OccupantInfo;
|
|
import com.threerings.crowd.data.PlaceConfig;
|
|
import com.threerings.crowd.data.PlaceObject;
|
|
|
|
import com.threerings.crowd.chat.data.SpeakMarshaller;
|
|
import com.threerings.crowd.chat.server.SpeakDispatcher;
|
|
import com.threerings.crowd.chat.server.SpeakProvider;
|
|
|
|
/**
|
|
* The place manager is the server-side entity that handles all
|
|
* place-related interaction. It subscribes to the place object and reacts
|
|
* to message and other events. Behavior specific to a place (or class of
|
|
* places) should live in the place manager. An intelligently constructed
|
|
* hierarchy of place manager classes working in concert with invocation
|
|
* 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
|
|
* interactions with the place registry to manage place registration. It
|
|
* handles the place-related component of chatting. It also provides the
|
|
* basis for place-based access control.
|
|
*
|
|
* <p> A derived class is expected to handle initialization, cleanup and
|
|
* operational functionality via the calldown functions {@link #didInit},
|
|
* {@link #didStartup}, and {@link #didShutdown} as well as through event
|
|
* listeners.
|
|
*/
|
|
public class PlaceManager
|
|
implements MessageListener, OidListListener, ObjectDeathListener,
|
|
SetListener, SpeakProvider.SpeakerValidator
|
|
{
|
|
/**
|
|
* An interface used to allow the registration of standard message
|
|
* handlers to be invoked by the place manager when particular types
|
|
* of message events are received.
|
|
*/
|
|
public static interface MessageHandler
|
|
{
|
|
/**
|
|
* Invokes this message handler on the supplied event.
|
|
*
|
|
* @param event the message event received.
|
|
* @param pmgr the place manager for which the message is being
|
|
* handled.
|
|
*/
|
|
public void handleEvent (MessageEvent event, PlaceManager pmgr);
|
|
}
|
|
|
|
/**
|
|
* Returns a reference to our place configuration object.
|
|
*/
|
|
public PlaceConfig getConfig ()
|
|
{
|
|
return _config;
|
|
}
|
|
|
|
/**
|
|
* Returns the place object managed by this place manager.
|
|
*/
|
|
public PlaceObject getPlaceObject ()
|
|
{
|
|
return _plobj;
|
|
}
|
|
|
|
/**
|
|
* Returns the occupant info record for the user with the specified
|
|
* body oid, if they are an occupant of this room. Returns null
|
|
* otherwise.
|
|
*/
|
|
public OccupantInfo getOccupantInfo (int bodyOid)
|
|
{
|
|
return (OccupantInfo)_occInfo.get(bodyOid);
|
|
}
|
|
|
|
/**
|
|
* Applies the supplied occupant operation to each occupant currently
|
|
* present in this place.
|
|
*/
|
|
public void applyToOccupants (OccupantOp op)
|
|
{
|
|
if (_plobj != null) {
|
|
Iterator iter = _plobj.occupantInfo.entries();
|
|
while (iter.hasNext()) {
|
|
op.apply((OccupantInfo)iter.next());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the occupant info for this room occupant. <em>Note:</em>
|
|
* This must be used rather than setting the occupant info directly to
|
|
* avoid possible complications due to rapid fire changes to a user's
|
|
* occupant info. The occupant info record supplied to this method
|
|
* must be one returned from {@link #getOccupantInfo}. For example:
|
|
*
|
|
* <pre>
|
|
* OccupantInfo info = _plmgr.getOccupantInfo(bodyOid);
|
|
* // ... modifications made to 'info'
|
|
* _plmgr.updateOccupantInfo(info);
|
|
* </pre>
|
|
*/
|
|
public void updateOccupantInfo (OccupantInfo occInfo)
|
|
{
|
|
// update the canonical copy
|
|
_occInfo.put(occInfo.getBodyOid(), occInfo);
|
|
// clone the canonical copy and send out an event updating the
|
|
// distributed set with that clone
|
|
_plobj.updateOccupantInfo((OccupantInfo)occInfo.clone());
|
|
}
|
|
|
|
/**
|
|
* A place manager derived class is likely to have a corresponding
|
|
* derived class of {@link com.threerings.crowd.data.PlaceObject} that
|
|
* it will be managing. Derived classes should override this method
|
|
* and return the class object for the place object derived class they
|
|
* desire to use. The place registry will use this method to create
|
|
* the proper place object during the place creation process.
|
|
*
|
|
* @return the class of the class, derived from {@link PlaceObject},
|
|
* that this manager wishes to manage.
|
|
*
|
|
* @see PlaceRegistry#createPlace
|
|
*/
|
|
protected Class getPlaceObjectClass ()
|
|
{
|
|
return PlaceObject.class;
|
|
}
|
|
|
|
/**
|
|
* Called by the place registry after creating this place manager.
|
|
*/
|
|
public void init (
|
|
PlaceRegistry registry, InvocationManager invmgr,
|
|
DObjectManager omgr, PlaceConfig config)
|
|
{
|
|
_registry = registry;
|
|
_invmgr = invmgr;
|
|
_omgr = omgr;
|
|
_config = config;
|
|
|
|
// let derived classes do initialization stuff
|
|
didInit();
|
|
}
|
|
|
|
/**
|
|
* Called after this place manager has been initialized with its
|
|
* configuration information but before it has been started up with
|
|
* its place object reference. Derived classes can override this
|
|
* function and perform any basic initialization that they desire.
|
|
* They should of course be sure to call <code>super.didInit()</code>.
|
|
*/
|
|
protected void didInit ()
|
|
{
|
|
// initialize our delegates
|
|
applyToDelegates(new DelegateOp() {
|
|
public void apply (PlaceManagerDelegate delegate) {
|
|
delegate.didInit(_config);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Provides an opportunity for place managers to ratify the creation
|
|
* of a place based on whatever criterion they may require (based on
|
|
* information available to the manager at this post-init() but
|
|
* pre-startup() phase of initialization).
|
|
*
|
|
* @return If a permissions check is to fail, the manager should
|
|
* return a translatable string explaining the failure.
|
|
* <code>null</code> should be returned if initialization is to be
|
|
* allowed to proceed.
|
|
*/
|
|
public String checkPermissions ()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Called if the permissions check failed, to give place managers a
|
|
* chance to do any cleanup that might be necessary due to their early
|
|
* initialization or permissions checking code.
|
|
*/
|
|
protected void permissionsFailed ()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Called by the place manager after the place object has been
|
|
* successfully created.
|
|
*/
|
|
public void startup (PlaceObject plobj)
|
|
{
|
|
// keep track of this
|
|
_plobj = plobj;
|
|
|
|
// create and register a speaker service instance that clients can
|
|
// use to speak in this place
|
|
SpeakMarshaller speakService =
|
|
(SpeakMarshaller)_invmgr.registerDispatcher(
|
|
new SpeakDispatcher(new SpeakProvider(_plobj, this)), false);
|
|
plobj.setSpeakService(speakService);
|
|
|
|
// we'll need to hear about place object events
|
|
plobj.addListener(this);
|
|
|
|
// let our derived classes do their thang
|
|
didStartup();
|
|
}
|
|
|
|
/**
|
|
* Derived classes should override this (and be sure to call
|
|
* <code>super.didStartup()</code>) to perform any startup time
|
|
* initialization. The place object will be available by the time this
|
|
* method is executed.
|
|
*/
|
|
protected void didStartup ()
|
|
{
|
|
// let our delegates know that we've started up
|
|
applyToDelegates(new DelegateOp() {
|
|
public void apply (PlaceManagerDelegate delegate) {
|
|
delegate.didStartup(_plobj);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Causes the place object being managed by this place manager to be
|
|
* destroyed and the place manager to shut down.
|
|
*/
|
|
public void shutdown ()
|
|
{
|
|
// destroy the object and everything will follow from that
|
|
CrowdServer.omgr.destroyObject(_plobj.getOid());
|
|
|
|
// clear out our services
|
|
_invmgr.clearDispatcher(_plobj.speakService);
|
|
|
|
// make sure we don't have any shutdowner in the queue
|
|
cancelShutdowner();
|
|
}
|
|
|
|
/**
|
|
* Called when this place has been destroyed and the place manager has
|
|
* shut down (via a call to {@link #shutdown}). Derived classes can
|
|
* override this method and perform any necessary shutdown time
|
|
* processing.
|
|
*/
|
|
protected void didShutdown ()
|
|
{
|
|
// let our delegates know that we've shut down
|
|
applyToDelegates(new DelegateOp() {
|
|
public void apply (PlaceManagerDelegate delegate) {
|
|
delegate.didShutdown();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns the appropriate derived class of {@link OccupantInfo} that
|
|
* will be used to provide occupant info for this body. An occupant
|
|
* info record is created when a body enters a place.
|
|
*
|
|
* @param body the body that is entering the place and for whom we are
|
|
* creating an occupant info record.
|
|
*/
|
|
protected Class getOccupantInfoClass (BodyObject body)
|
|
{
|
|
return OccupantInfo.class;
|
|
}
|
|
|
|
/**
|
|
* Builds an occupant info record for the specified body object and
|
|
* inserts it into our place object. This is called by the location
|
|
* services when a body enters a place. It should not be overridden by
|
|
* derived classes, they should override {@link
|
|
* #populateOccupantInfo}, which is set up for that sort of thing.
|
|
*/
|
|
public OccupantInfo buildOccupantInfo (BodyObject body)
|
|
{
|
|
try {
|
|
// create a new occupant info instance
|
|
OccupantInfo info = (OccupantInfo)
|
|
getOccupantInfoClass(body).newInstance();
|
|
|
|
// configure it with the appropriate values
|
|
populateOccupantInfo(info, body);
|
|
|
|
// insert the occupant info into our canonical table
|
|
_occInfo.put(info.getBodyOid(), info);
|
|
|
|
// clone the canonical copy and insert it into the DSet
|
|
_plobj.addToOccupantInfo((OccupantInfo)info.clone());
|
|
|
|
return info;
|
|
|
|
} catch (Exception e) {
|
|
Log.warning("Failure building occupant info " +
|
|
"[where=" + where() + ", body=" + body + "].");
|
|
Log.logStackTrace(e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a string that can be used in log messages to identify the
|
|
* place as sensibly as possible to the developer who has to puzzle
|
|
* over log output trying to figure out what's going on. Derived place
|
|
* managers can override this and augment the default value (which is
|
|
* simply the place object id) with useful identifying information.
|
|
*/
|
|
public String where ()
|
|
{
|
|
return StringUtil.shortClassName(this) + ":" + (
|
|
(_plobj != null) ? String.valueOf(_plobj.getOid()) : "-1");
|
|
}
|
|
|
|
/**
|
|
* Derived classes should override this method if they are making use
|
|
* of a derived occupant info class. They should call the super
|
|
* implementation and then populate the occupant info fields in their
|
|
* extended object.
|
|
*/
|
|
protected void populateOccupantInfo (OccupantInfo info, BodyObject body)
|
|
{
|
|
// the base occupant info is only their username
|
|
info.bodyOid = new Integer(body.getOid());
|
|
info.username = body.username;
|
|
info.status = body.status;
|
|
}
|
|
|
|
/**
|
|
* Called when a body object enters this place.
|
|
*/
|
|
protected void bodyEntered (final int bodyOid)
|
|
{
|
|
if (Log.debug()) {
|
|
Log.debug("Body entered [where=" + where() +
|
|
", oid=" + bodyOid + "].");
|
|
}
|
|
|
|
// let our delegates know what's up
|
|
applyToDelegates(new DelegateOp() {
|
|
public void apply (PlaceManagerDelegate delegate) {
|
|
delegate.bodyEntered(bodyOid);
|
|
}
|
|
});
|
|
|
|
// if we were on the road to shutting down, step off
|
|
cancelShutdowner();
|
|
}
|
|
|
|
/**
|
|
* Called when a body object leaves this place.
|
|
*/
|
|
protected void bodyLeft (final int bodyOid)
|
|
{
|
|
if (Log.debug()) {
|
|
Log.debug("Body left [where=" + where() +
|
|
", oid=" + bodyOid + "].");
|
|
}
|
|
|
|
// if their occupant info hasn't been removed (which may be the
|
|
// case if they logged off rather than left via a MoveTo request),
|
|
// we need to get it on out of here
|
|
Integer key = new Integer(bodyOid);
|
|
if (_plobj.occupantInfo.containsKey(key)) {
|
|
_plobj.removeFromOccupantInfo(key);
|
|
}
|
|
|
|
// clear out their canonical (local) occupant info record
|
|
_occInfo.remove(bodyOid);
|
|
|
|
// let our delegates know what's up
|
|
applyToDelegates(new DelegateOp() {
|
|
public void apply (PlaceManagerDelegate delegate) {
|
|
delegate.bodyLeft(bodyOid);
|
|
}
|
|
});
|
|
|
|
// if that leaves us with zero occupants, maybe do something
|
|
if (_plobj.occupants.size() == 0) {
|
|
placeBecameEmpty();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when a body's occupant info is updated.
|
|
*/
|
|
protected void bodyUpdated (final OccupantInfo info)
|
|
{
|
|
// let our delegates know what's up
|
|
applyToDelegates(new DelegateOp() {
|
|
public void apply (PlaceManagerDelegate delegate) {
|
|
delegate.bodyUpdated(info);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Called when we transition from having bodies in the place to not
|
|
* having any bodies in the place. Some places may take this as a sign
|
|
* to pack it in, others may wish to stick around. In any case, they
|
|
* can override this method to do their thing.
|
|
*/
|
|
protected void placeBecameEmpty ()
|
|
{
|
|
// let our delegates know what's up
|
|
applyToDelegates(new DelegateOp() {
|
|
public void apply (PlaceManagerDelegate delegate) {
|
|
delegate.placeBecameEmpty();
|
|
}
|
|
});
|
|
|
|
// Log.info("Place became empty " + where() + ".");
|
|
|
|
// queue up a shutdown interval
|
|
long idlePeriod = idleUnloadPeriod();
|
|
if (idlePeriod > 0L) {
|
|
SafeInterval si = new SafeInterval((PresentsDObjectMgr)_omgr) {
|
|
public void intervalExpired (int id, Object arg) {
|
|
_iid = id;
|
|
super.intervalExpired(id, arg);
|
|
}
|
|
public void run () {
|
|
if (_iid == _shutdownId) {
|
|
_shutdownId = -1;
|
|
Log.debug("Unloading idle place '" + where () + "'.");
|
|
shutdown();
|
|
}
|
|
}
|
|
protected int _iid;
|
|
};
|
|
_shutdownId = IntervalManager.register(si, idlePeriod, null, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cancels any registered shutdown interval.
|
|
*/
|
|
protected void cancelShutdowner ()
|
|
{
|
|
if (_shutdownId != -1) {
|
|
IntervalManager.remove(_shutdownId);
|
|
_shutdownId = -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the period (in milliseconds) of emptiness after which this
|
|
* place manager will unload itself and shutdown. Returning
|
|
* <code>0</code> indicates that the place should never be shutdown.
|
|
*/
|
|
protected long idleUnloadPeriod ()
|
|
{
|
|
return 5 * 60 * 1000L;
|
|
}
|
|
|
|
/**
|
|
* Registers a particular message handler instance to be used when
|
|
* processing message events with the specified name.
|
|
*
|
|
* @param name the message name of the message events that should be
|
|
* handled by this handler.
|
|
* @param handler the handler to be registered.
|
|
*/
|
|
public void registerMessageHandler (String name, MessageHandler handler)
|
|
{
|
|
// create our handler map if necessary
|
|
if (_msghandlers == null) {
|
|
_msghandlers = new HashMap();
|
|
}
|
|
_msghandlers.put(name, handler);
|
|
}
|
|
|
|
/**
|
|
* Dispatches message events to registered message handlers. Derived
|
|
* classes should probably register message handlers rather than
|
|
* override this method directly.
|
|
*/
|
|
public void messageReceived (MessageEvent event)
|
|
{
|
|
MessageHandler handler = null;
|
|
if (_msghandlers != null) {
|
|
handler = (MessageHandler)_msghandlers.get(event.getName());
|
|
}
|
|
if (handler != null) {
|
|
handler.handleEvent(event, this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles occupant arrival into the place. Derived classes may need
|
|
* to override this method to handle other oid lists in their derived
|
|
* place objects. They should be sure to call
|
|
* <code>super.objectAdded</code> if the event is one they don't
|
|
* explicitly handle.
|
|
*/
|
|
public void objectAdded (ObjectAddedEvent event)
|
|
{
|
|
if (event.getName().equals(PlaceObject.OCCUPANTS)) {
|
|
bodyEntered(event.getOid());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles occupant departure from the place. Derived classes may need
|
|
* to override this method to handle other oid lists in their derived
|
|
* place objects. They should be sure to call
|
|
* <code>super.objectRemoved</code> if the event is one they don't
|
|
* explicitly handle.
|
|
*/
|
|
public void objectRemoved (ObjectRemovedEvent event)
|
|
{
|
|
if (event.getName().equals(PlaceObject.OCCUPANTS)) {
|
|
bodyLeft(event.getOid());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles place destruction. We shut ourselves down and ask the place
|
|
* registry to unmap us.
|
|
*/
|
|
public void objectDestroyed (ObjectDestroyedEvent event)
|
|
{
|
|
// unregister ourselves
|
|
_registry.unmapPlaceManager(this);
|
|
|
|
// let our derived classes and delegates shut themselves down
|
|
didShutdown();
|
|
}
|
|
|
|
// documentation inherited from interface
|
|
public void entryAdded (EntryAddedEvent event)
|
|
{
|
|
}
|
|
|
|
// documentation inherited from interface
|
|
public void entryUpdated (EntryUpdatedEvent event)
|
|
{
|
|
if (event.getName().equals(PlaceObject.OCCUPANT_INFO)) {
|
|
bodyUpdated((OccupantInfo)event.getEntry());
|
|
}
|
|
}
|
|
|
|
// documentation inherited from interface
|
|
public void entryRemoved (EntryRemovedEvent event)
|
|
{
|
|
}
|
|
|
|
// documentation inherited from interface
|
|
public boolean isValidSpeaker (DObject speakObj, ClientObject speaker)
|
|
{
|
|
// only allow people in the room to speak
|
|
return _plobj.occupants.contains(speaker.getOid());
|
|
}
|
|
|
|
/**
|
|
* Generates a string representation of this manager. Does so in a way
|
|
* that makes it easier for derived classes to add to the string
|
|
* representation.
|
|
*
|
|
* @see #toString(StringBuffer)
|
|
*/
|
|
public String toString ()
|
|
{
|
|
StringBuffer buf = new StringBuffer();
|
|
buf.append("[");
|
|
toString(buf);
|
|
buf.append("]");
|
|
return buf.toString();
|
|
}
|
|
|
|
/**
|
|
* An extensible way to add to the string representation of this
|
|
* class. Override this (being sure to call super) and append your
|
|
* info to the buffer.
|
|
*/
|
|
protected void toString (StringBuffer buf)
|
|
{
|
|
buf.append("place=").append(_plobj);
|
|
buf.append(", config=").append(_config);
|
|
}
|
|
|
|
/**
|
|
* Adds the supplied delegate to the list for this manager.
|
|
*/
|
|
protected void addDelegate (PlaceManagerDelegate delegate)
|
|
{
|
|
if (_delegates == null) {
|
|
_delegates = new ArrayList();
|
|
}
|
|
_delegates.add(delegate);
|
|
}
|
|
|
|
/**
|
|
* Used to call methods in delegates.
|
|
*/
|
|
protected static interface DelegateOp
|
|
{
|
|
public void apply (PlaceManagerDelegate delegate);
|
|
}
|
|
|
|
/**
|
|
* Applies the supplied operation to the registered delegates.
|
|
*/
|
|
protected void applyToDelegates (DelegateOp op)
|
|
{
|
|
if (_delegates != null) {
|
|
int dcount = _delegates.size();
|
|
for (int i = 0; i < dcount; i++) {
|
|
op.apply((PlaceManagerDelegate)_delegates.get(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
/** A reference to the place registry with which we're registered. */
|
|
protected PlaceRegistry _registry;
|
|
|
|
/** The invocation manager with whom we register our game invocation
|
|
* services. */
|
|
protected InvocationManager _invmgr;
|
|
|
|
/** A distributed object manager for doing dobj stuff. */
|
|
protected DObjectManager _omgr;
|
|
|
|
/** A reference to the place object that we manage. */
|
|
protected PlaceObject _plobj;
|
|
|
|
/** A reference to the configuration for our place. */
|
|
protected PlaceConfig _config;
|
|
|
|
/** Message handlers are used to process message events. */
|
|
protected HashMap _msghandlers;
|
|
|
|
/** A list of the delegates in use by this manager. */
|
|
protected ArrayList _delegates;
|
|
|
|
/** Used to keep a canonical copy of the occupant info records. */
|
|
protected HashIntMap _occInfo = new HashIntMap();
|
|
|
|
/** The id of the interval currently registered to shut this place
|
|
* down after a certain period of idility. Is <code>-1</code> if no
|
|
* interval is currently registered. */
|
|
protected int _shutdownId = -1;
|
|
}
|