Convert Narya (most of the way) over to a Maven Ant task based build. The

ActionScript bits remain belligerent, but the Java stuff is mostly shipshape.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6222 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2010-10-22 21:12:29 +00:00
parent 555b865bbf
commit 9d2ca42eac
434 changed files with 163 additions and 208 deletions
@@ -0,0 +1,33 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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;
import com.samskivert.util.Logger;
/**
* Contains a reference to the log object used by the Crowd services.
*/
public class Log
{
/** We dispatch our log messages through this logger. */
public static Logger log = Logger.getLogger("com.threerings.crowd");
}
@@ -0,0 +1,38 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.client;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
import com.threerings.crowd.chat.data.ChatChannel;
/**
* Provides a way for clients to speak on chat channels.
*/
public interface ChannelSpeakService extends InvocationService
{
/**
* Requests to speak the supplied message on the specified channel.
*/
public void speak (Client client, ChatChannel channel, String message, byte mode);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,47 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.client;
import com.threerings.crowd.chat.data.ChatMessage;
/**
* A chat display provides a means by which chat messages can be
* displayed. The chat display will be notified when chat messages of
* various sorts have been received by the client.
*/
public interface ChatDisplay
{
/**
* Called to clear the chat display.
*/
void clear ();
/**
* Called to display a chat message.
*
* @param alreadyDisplayed true if a previous chat display in the list has
* already displayed this message, false otherwise.
*
* @return true if the message was displayed, false if not.
*/
boolean displayMessage (ChatMessage msg, boolean alreadyDisplayed);
}
@@ -0,0 +1,41 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.client;
import com.threerings.util.Name;
/**
* Filters messages chat messages to or from the server.
*/
public interface ChatFilter
{
/**
* Filter a chat message.
* @param msg the message text to be filtered.
* @param otherUser an optional argument that represents the target or the speaker, depending
* on 'outgoing', and can be considered in filtering if it is provided.
* @param outgoing true if the message is going out to the server.
*
* @return the filtered message, or null to block it completely.
*/
String filter (String msg, Name otherUser, boolean outgoing);
}
@@ -0,0 +1,77 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.client;
import com.threerings.util.Name;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
/**
* The chat services provide a mechanism by which the client can broadcast chat messages to all
* clients that are subscribed to a particular place object or directly to a particular client.
* These services should not be used directly, but instead should be accessed via the
* {@link ChatDirector}.
*/
public interface ChatService extends InvocationService
{
/**
* Used to communicate the response to a {@link ChatService#tell} request.
*/
public static interface TellListener extends InvocationListener
{
/**
* Communicates the response to a {@link ChatService#tell} request.
*
* @param idleTime the number of ms the tellee has been idle or 0L if they are not idle.
* @param awayMessage the away message configured by the told player or null if they have
* no away message.
*/
void tellSucceeded (long idleTime, String awayMessage);
}
/**
* Requests that a tell message be delivered to the user with username equal to
* <code>target</code>.
*
* @param client a connected, operational client instance.
* @param target the username of the user to which the tell message should be delivered.
* @param message the contents of the message.
* @param listener the reference that will receive the tell response.
*/
void tell (Client client, Name target, String message, TellListener listener);
/**
* Requests that a message be broadcast to all users in the system.
*
* @param client a connected, operational client instance.
* @param message the contents of the message.
* @param listener the reference that will receive a failure response.
*/
void broadcast (Client client, String message, InvocationListener listener);
/**
* Sets this client's away message. If the message is null or the empty string, the away
* message will be cleared.
*/
void away (Client client, String message);
}
@@ -0,0 +1,253 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.client;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.samskivert.util.RandomUtil;
import com.threerings.util.Name;
import static com.threerings.crowd.Log.log;
/**
* A chat filter that can filter out curse words from user chat.
*/
public abstract class CurseFilter implements ChatFilter
{
/** Indicates how messages should be handled. */
public enum Mode { DROP, COMIC, VERNACULAR, UNFILTERED; }
/**
* Creates a curse filter. The curse words should be a string in the following format:
*
* <pre>
* *penis*=John_Thomas shit*=barnacle muff=britches
* </pre>
*
* The key/value pairs are separated by spaces, * matches word characters and the value after
* the = is the string into which to convert the text when converting to the vernacular.
* Underscores in the target string will be turned into spaces.
*
* <p> And stopWords should be in the following format:
*
* <pre>
* *faggot* rape rapes raped raping
* </pre>
*
* Words are separated by spaces and * matches any other word characters.
*/
public CurseFilter (String curseWords, String stopWords)
{
configureCurseWords(curseWords);
configureStopWords(stopWords);
}
/**
* The client will need to provide a way to look up our current chat filter mode.
*/
public abstract Mode getFilterMode ();
// from interface ChatFilter
public String filter (String msg, Name otherUser, boolean outgoing)
{
// first, check against the drop-always list
_stopMatcher.reset(msg);
if (_stopMatcher.find()) {
return null;
}
// then see what kind of curse filtering the user has configured
Mode level = getFilterMode();
if (level == Mode.UNFILTERED) {
return msg;
}
StringBuffer inbuf = new StringBuffer(msg);
StringBuffer outbuf = new StringBuffer(msg.length());
for (int ii=0, nn=_matchers.length; ii < nn; ii++) {
Matcher m = _matchers[ii];
m.reset(inbuf);
while (m.find()) {
switch (level) {
case DROP:
return null;
case COMIC:
m.appendReplacement(outbuf,
_replacements[ii].replace(" ", comicChars(_comicLength[ii])));
break;
case VERNACULAR:
String vernacular = _vernacular[ii];
if (Character.isUpperCase(m.group(2).codePointAt(0))) {
int firstCharLen = Character.charCount(vernacular.codePointAt(0));
vernacular = vernacular.substring(0, firstCharLen).toUpperCase() +
vernacular.substring(firstCharLen);
}
m.appendReplacement(outbuf, _replacements[ii].replace(" ", vernacular));
break;
case UNFILTERED:
// We returned the msg unadulterated above in this case, so it should be
// impossible to wind up here, but let's enumerate it so we can let the compiler
// scream about missing enum values in a switch
log.warning("Omg? We're trying to filter chat even though we're unfiltered?");
break;
}
}
if (outbuf.length() == 0) {
// optimization: if we didn't find a match, jump to the next
// pattern without doing any StringBuilder jimmying
continue;
}
m.appendTail(outbuf);
// swap the buffers around and clear the output
StringBuffer temp = inbuf;
inbuf = outbuf;
outbuf = temp;
outbuf.setLength(0);
}
return inbuf.toString();
}
/**
* Configure the curse word portion of our filtering.
*/
protected void configureCurseWords (String curseWords)
{
StringTokenizer st = new StringTokenizer(curseWords);
int numWords = st.countTokens();
_matchers = new Matcher[numWords];
_replacements = new String[numWords];
_vernacular = new String[numWords];
_comicLength = new int[numWords];
for (int ii=0; ii < numWords; ii++) {
String mapping = st.nextToken();
StringTokenizer st2 = new StringTokenizer(mapping, "=");
if (st2.countTokens() != 2) {
log.warning("Something looks wrong in the x.cursewords properties (" +
mapping + "), skipping.");
continue;
}
String curse = st2.nextToken();
String s = "";
String p = "";
if (curse.startsWith("*")) {
curse = curse.substring(1);
p += "([a-zA-Z]*)";
s += "$1";
} else {
p += "()";
}
s += " ";
p += " ";
if (curse.endsWith("*")) {
curse = curse.substring(0, curse.length() - 1);
p += "([a-zA-Z]*)";
s += "$3";
}
String pattern = "\\b" + p.replace(" ", "(" + curse + ")") + "\\b";
Pattern pat = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
_matchers[ii] = pat.matcher("");
_replacements[ii] = s;
_vernacular[ii] = st2.nextToken().replace('_', ' ');
_comicLength[ii] = curse.codePointCount(0, curse.length());
}
}
/**
* Configure the words that will stop.
*/
protected void configureStopWords (String stopWords)
{
StringTokenizer st = new StringTokenizer(stopWords);
String pattern = "";
while (st.hasMoreTokens()) {
if ("".equals(pattern)) {
pattern += "(";
} else {
pattern += "|";
}
pattern += getStopWordRegexp(st.nextToken());
}
pattern += ")";
setStopPattern(pattern);
}
/**
* Sets our stop word matcher to one for the given regular expression.
*/
protected void setStopPattern (String pattern)
{
_stopMatcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher("");
}
/**
* Turns a naughty word into a regular expression to catch it.
*/
protected String getStopWordRegexp (String word)
{
return "\\b" + word.replace("*", "[A-Za-z]*") + "\\b";
}
/**
* Return a comicy replacement of the specified length.
*/
protected String comicChars (int length)
{
StringBuilder buf = new StringBuilder();
for (int ii=0; ii < length; ii++) {
buf.append(RandomUtil.pickRandom(COMIC_CHARS));
}
return buf.toString();
}
/** A matcher that will always cause a message to be dropped if it matches. */
protected Matcher _stopMatcher;
/** Matchers for each curseword. */
protected Matcher[] _matchers;
/** Length of comic-y replacements for each curseword. */
protected int[] _comicLength;
/** Replacements. */
protected String[] _replacements;
/** Replacements for each curseword "in the vernacular". */
protected String[] _vernacular;
/** Comic replacement characters. */
protected static final String[] COMIC_CHARS = { "!", "@", "#", "%", "&", "*" };
}
@@ -0,0 +1,127 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.client;
import java.util.ArrayList;
import com.samskivert.util.ObserverList;
import com.threerings.crowd.chat.data.ChatMessage;
/**
* Stores chat history.
*/
public class HistoryList extends ArrayList<ChatMessage>
implements ChatDisplay
{
/** An interface for chat history observers. */
public interface Observer {
/** Called when messages have been added or removed from the chat history.
* @param adjustment the number of messages that have been added (+) or removed (-). */
void historyUpdated (int adjustment);
}
// documentation inherited from interface
public boolean displayMessage (ChatMessage msg, boolean alreadyDisplayed)
{
// see if we're full, and if so, clear out a bunch of old stuff
int adjusted;
if (size() == MAX_HISTORY) {
removeRange(0, PRUNE_HISTORY);
adjusted = PRUNE_HISTORY;
} else {
adjusted = 0;
}
// add the message to the history
add(msg);
// notify observers that something changed
notify(adjusted);
return true;
}
@Override
public void clear ()
{
// see how many entries we're clearing out..
int adjusted = size();
super.clear();
// and notify the chat displays of that fact
notify(adjusted);
}
/**
* Adds an {@link Observer} that wants to know about changes to the history.
*/
public void addObserver (Observer obs)
{
_obs.add(obs);
}
/**
* Removes a {@link Observer} from hearing about changes to the history.
*/
public void removeObserver (Observer obs)
{
_obs.remove(obs);
}
/**
* Notifies listening {@link Observer}s that there has been a change to this history.
*/
protected void notify (int adjustment)
{
_historyUpdatedOp.setAdjustment(adjustment);
_obs.apply(_historyUpdatedOp);
}
protected static class HistoryUpdatedOp
implements ObserverList.ObserverOp<Observer>
{
public void setAdjustment (int adjustment) {
_adjustment = adjustment;
}
public boolean apply (Observer obs) {
obs.historyUpdated(_adjustment);
return true;
}
protected int _adjustment;
}
/** A list of {@link Observer}s interested in history changes. */
protected ObserverList<Observer> _obs = ObserverList.newFastUnsafe();
/** An operation used to notify observers of history updates. */
protected HistoryList.HistoryUpdatedOp _historyUpdatedOp = new HistoryUpdatedOp();
/** The maximum number of history entries we'll keep. */
protected static final int MAX_HISTORY = 2000;
/** The number of history entries we'll prune when we hit the max. */
protected static final int PRUNE_HISTORY = 200;
}
@@ -0,0 +1,193 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.client;
import java.util.Collections;
import java.util.HashSet;
import com.google.common.collect.Sets;
import com.samskivert.util.ObserverList;
import com.threerings.util.MessageBundle;
import com.threerings.util.Name;
import com.threerings.presents.client.BasicDirector;
import com.threerings.crowd.util.CrowdContext;
/**
* Manages the mutelist.
*
* TODO: This class right now is pretty much just a placeholder.
*/
public class MuteDirector extends BasicDirector
implements ChatFilter
{
/**
* An interface that can be registered with the MuteDirector to
* receive notifications to the mutelist.
*/
public static interface MuteObserver
{
/**
* The specified player was added or removed from the mutelist.
*/
void muteChanged (Name playername, boolean nowMuted);
}
/**
* Should be instantiated after the ChatDirector.
*/
public MuteDirector (CrowdContext ctx)
{
super(ctx);
}
/**
* Set up the mute director with the specified list of initial mutees.
*/
public MuteDirector (CrowdContext ctx, Name[] list)
{
this(ctx);
Collections.addAll(_mutelist, list);
}
/**
* Called to shut down the mute director.
*/
public void shutdown ()
{
if (_chatdir != null) {
_chatdir.removeChatFilter(this);
_chatdir = null;
}
}
/**
* Set the required ChatDirector.
*/
public void setChatDirector (ChatDirector chatdir)
{
if (_chatdir == null) {
_chatdir = chatdir;
_chatdir.addChatFilter(this);
}
}
/**
* Add the specified mutelist observer.
*/
public void addMuteObserver (MuteObserver obs)
{
_observers.add(obs);
}
/**
* Remove the specified mutelist observer.
*/
public void removeMuteObserver (MuteObserver obs)
{
_observers.remove(obs);
}
/**
* Check to see if the specified user is muted.
*/
public boolean isMuted (Name username)
{
return _mutelist.contains(username);
}
/**
* Mute or unmute the specified user.
*/
public void setMuted (Name username, boolean mute)
{
boolean changed = mute ? _mutelist.add(username) : _mutelist.remove(username);
String feedback;
if (mute) {
feedback = "m.muted";
} else {
feedback = changed ? "m.unmuted" : "m.notmuted";
}
// always give some feedback to the user
_chatdir.displayFeedback(null, MessageBundle.tcompose(feedback, username));
// if the mutelist actually changed, notify observers
if (changed) {
notifyObservers(username, mute);
}
}
/**
* @return a list of the currently muted players.
*
* This list may be out of date immediately upon returning from this method.
*/
public Name[] getMuted ()
{
return _mutelist.toArray(new Name[_mutelist.size()]);
}
// documentation inherited from interface ChatFilter
public String filter (String msg, Name otherUser, boolean outgoing)
{
// we are only concerned with filtering things going to or coming
// from muted users
if ((otherUser != null) && isMuted(otherUser)) {
// if it was outgoing, explain the dropped message, otherwise
// silently drop
if (outgoing) {
_chatdir.displayFeedback(null, "m.no_tell_mute");
}
return null;
}
return msg;
}
/**
* Notify our observers of a change in the mutelist.
*/
protected void notifyObservers (final Name username, final boolean muted)
{
_observers.apply(new ObserverList.ObserverOp<MuteObserver>() {
public boolean apply (MuteObserver observer) {
observer.muteChanged(username, muted);
return true;
}
});
}
/** The chat director that we're working hard for. */
protected ChatDirector _chatdir;
/** The mutelist. */
protected HashSet<Name> _mutelist = Sets.newHashSet();
/** List of mutelist observers. */
protected ObserverList<MuteObserver> _observers =
new ObserverList<MuteObserver>(ObserverList.FAST_UNSAFE_NOTIFY);
}
@@ -0,0 +1,45 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.client;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
/**
* Provides a means by which "speaking" can be allowed among subscribers
* of a particular distributed object.
*/
public interface SpeakService extends InvocationService
{
/**
* Issues a request to speak "on" the distributed object via which
* this speak service was provided.
*
* @param message the message to be spoken.
* @param mode the "mode" of the message. This is an opaque value that
* will be passed back down via the {@link ChatDirector} to the {@link
* ChatDisplay} implementations which can interpret it in an
* application specific manner. It's useful for differentiating
* between regular speech, emotes, etc.
*/
void speak (Client client, String message, byte mode);
}
@@ -0,0 +1,52 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.data;
import javax.annotation.Generated;
import com.threerings.crowd.chat.client.ChannelSpeakService;
import com.threerings.presents.client.Client;
import com.threerings.presents.data.InvocationMarshaller;
/**
* Provides the implementation of the {@link ChannelSpeakService} interface
* that marshalls the arguments and delivers the request to the provider
* on the server. Also provides an implementation of the response listener
* interfaces that marshall the response arguments and deliver them back
* to the requesting client.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from ChannelSpeakService.java.")
public class ChannelSpeakMarshaller extends InvocationMarshaller
implements ChannelSpeakService
{
/** The method id used to dispatch {@link #speak} requests. */
public static final int SPEAK = 1;
// from interface ChannelSpeakService
public void speak (Client arg1, ChatChannel arg2, String arg3, byte arg4)
{
sendRequest(arg1, SPEAK, new Object[] {
arg2, arg3, Byte.valueOf(arg4)
});
}
}
@@ -0,0 +1,57 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.data;
import com.threerings.io.SimpleStreamableObject;
import com.threerings.presents.dobj.DSet;
/**
* Represents a chat channel.
*/
public abstract class ChatChannel extends SimpleStreamableObject
implements Comparable<ChatChannel>, DSet.Entry
{
// from interface Comparable<ChatChannel>
public abstract int compareTo (ChatChannel other);
/**
* Converts this channel into a unique name that can be used as the name of the distributed
* lock used when resolving the channel.
*/
public abstract String getLockName ();
// from interface DSet.Entry
public Comparable<?> getKey ()
{
return this;
}
@Override
public boolean equals (Object other)
{
return compareTo((ChatChannel)other) == 0;
}
@Override
public abstract int hashCode ();
}
@@ -0,0 +1,96 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.data;
import com.threerings.presents.data.InvocationCodes;
import com.threerings.presents.data.Permission;
import com.threerings.crowd.chat.client.ChatDirector;
import com.threerings.crowd.chat.client.SpeakService;
/**
* Contains codes used by the chat invocation services.
*/
public interface ChatCodes extends InvocationCodes
{
/** A return value used by the ChatDirector and possibly other entities to indicate successful
* processing of chat. */
public static final String SUCCESS = "success";
/** The message identifier for a chat notification message. */
public static final String CHAT_NOTIFICATION = "crowd.chat";
/** The message identifier for a chat channel notification message. */
public static final String CHAT_CHANNEL_NOTIFICATION = "crowd.chat.channel";
/** The access control identifier for normal chat privileges. */
public static final Permission CHAT_ACCESS = new Permission();
/** The access control identifier for broadcast chat privileges. */
public static final Permission BROADCAST_ACCESS = new Permission();
/** The configuration key for idle time. */
public static final String IDLE_TIME_KEY = "narya.chat.idle_time";
/** The default time after which a player is assumed idle. */
public static final long DEFAULT_IDLE_TIME = 3 * 60 * 1000L;
/** The chat localtype code for chat messages delivered on the place object currently occupied
* by the client. This is the only type of chat message that will be delivered unless the chat
* director is explicitly provided with other chat message sources via {@link
* ChatDirector#addAuxiliarySource}. */
public static final String PLACE_CHAT_TYPE = "placeChat";
/** The chat localtype for messages received on the user object. */
public static final String USER_CHAT_TYPE = "userChat";
/** The default mode used by {@link SpeakService#speak} requests. */
public static final byte DEFAULT_MODE = 0;
/** A {@link SpeakService#speak} mode to indicate that the user is thinking what they're
* saying, or is it that they're saying what they're thinking? */
public static final byte THINK_MODE = 1;
/** A {@link SpeakService#speak} mode to indicate that a speak is actually an emote. */
public static final byte EMOTE_MODE = 2;
/** A {@link SpeakService#speak} mode to indicate that a speak is actually a shout. */
public static final byte SHOUT_MODE = 3;
/** A {@link SpeakService#speak} mode to indicate that a speak is actually a server-wide
* broadcast. */
public static final byte BROADCAST_MODE = 4;
/** The last chat mode defined in the interface. */
public static final byte LAST_MODE = BROADCAST_MODE;
/** String translations for the various chat modes. */
public static final String[] XLATE_MODES = {
"default", "think", "emote", "shout", "broadcast"
};
/** An error code delivered when the user targeted for a tell notification is not online. */
public static final String USER_NOT_ONLINE = "m.user_not_online";
/** An error code delivered when the user targeted for a tell notification is disconnected. */
public static final String USER_DISCONNECTED = "m.user_disconnected";
}
@@ -0,0 +1,116 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.data;
import javax.annotation.Generated;
import com.threerings.crowd.chat.client.ChatService;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.InvocationResponseEvent;
import com.threerings.util.Name;
/**
* Provides the implementation of the {@link ChatService} interface
* that marshalls the arguments and delivers the request to the provider
* on the server. Also provides an implementation of the response listener
* interfaces that marshall the response arguments and deliver them back
* to the requesting client.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from ChatService.java.")
public class ChatMarshaller extends InvocationMarshaller
implements ChatService
{
/**
* Marshalls results to implementations of {@link ChatService.TellListener}.
*/
public static class TellMarshaller extends ListenerMarshaller
implements TellListener
{
/** The method id used to dispatch {@link #tellSucceeded}
* responses. */
public static final int TELL_SUCCEEDED = 1;
// from interface TellMarshaller
public void tellSucceeded (long arg1, String arg2)
{
_invId = null;
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, TELL_SUCCEEDED,
new Object[] { Long.valueOf(arg1), arg2 }, transport));
}
@Override // from InvocationMarshaller
public void dispatchResponse (int methodId, Object[] args)
{
switch (methodId) {
case TELL_SUCCEEDED:
((TellListener)listener).tellSucceeded(
((Long)args[0]).longValue(), (String)args[1]);
return;
default:
super.dispatchResponse(methodId, args);
return;
}
}
}
/** The method id used to dispatch {@link #away} requests. */
public static final int AWAY = 1;
// from interface ChatService
public void away (Client arg1, String arg2)
{
sendRequest(arg1, AWAY, new Object[] {
arg2
});
}
/** The method id used to dispatch {@link #broadcast} requests. */
public static final int BROADCAST = 2;
// from interface ChatService
public void broadcast (Client arg1, String arg2, InvocationService.InvocationListener arg3)
{
ListenerMarshaller listener3 = new ListenerMarshaller();
listener3.listener = arg3;
sendRequest(arg1, BROADCAST, new Object[] {
arg2, listener3
});
}
/** The method id used to dispatch {@link #tell} requests. */
public static final int TELL = 3;
// from interface ChatService
public void tell (Client arg1, Name arg2, String arg3, ChatService.TellListener arg4)
{
ChatMarshaller.TellMarshaller listener4 = new ChatMarshaller.TellMarshaller();
listener4.listener = arg4;
sendRequest(arg1, TELL, new Object[] {
arg2, arg3, listener4
});
}
}
@@ -0,0 +1,91 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.data;
import com.samskivert.util.StringUtil;
import com.threerings.io.Streamable;
import com.threerings.util.ActionScript;
/**
* The abstract base class of all the client-side ChatMessage objects.
*/
public abstract class ChatMessage
implements Streamable
{
/** The actual text of the message. */
public String message;
/** The bundle to use when translating this message. */
public String bundle;
/** The client side 'localtype' of this chat, set to the type registered with an auxiliary
* source in the ChatDirector. */
public transient String localtype;
/** The client time that this message was created. */
@ActionScript(type="int")
public transient long timestamp;
/**
* For all your unserialization needs.
*/
public ChatMessage ()
{
}
/**
* Construct a ChatMessage.
*/
public ChatMessage (String message, String bundle)
{
this.message = message;
this.bundle = bundle;
}
/**
* Once this message reaches the client, the information contained within is changed around a
* bit.
*/
public void setClientInfo (String msg, String ltype)
{
message = msg;
localtype = ltype;
bundle = null;
timestamp = System.currentTimeMillis();
}
/**
* Get the appropriate message format for this message.
*/
public String getFormat ()
{
return null;
}
@Override
public String toString ()
{
return StringUtil.shortClassName(this) + StringUtil.fieldsToString(this);
}
}
@@ -0,0 +1,33 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.data;
import com.threerings.crowd.chat.server.SpeakUtil;
import com.threerings.util.Name;
/**
* Marks a {@link Name} as disinterested in chat history such that {@link SpeakUtil} will keep no
* messages sent to it.
*/
public interface KeepNoHistory
{
}
@@ -0,0 +1,52 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.data;
import javax.annotation.Generated;
import com.threerings.crowd.chat.client.SpeakService;
import com.threerings.presents.client.Client;
import com.threerings.presents.data.InvocationMarshaller;
/**
* Provides the implementation of the {@link SpeakService} interface
* that marshalls the arguments and delivers the request to the provider
* on the server. Also provides an implementation of the response listener
* interfaces that marshall the response arguments and deliver them back
* to the requesting client.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from SpeakService.java.")
public class SpeakMarshaller extends InvocationMarshaller
implements SpeakService
{
/** The method id used to dispatch {@link #speak} requests. */
public static final int SPEAK = 1;
// from interface SpeakService
public void speak (Client arg1, String arg2, byte arg3)
{
sendRequest(arg1, SPEAK, new Object[] {
arg2, Byte.valueOf(arg3)
});
}
}
@@ -0,0 +1,47 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.data;
import com.threerings.util.Name;
/**
* Provides a mechanism by which the speak service can identify chat listeners so as to maintain a
* recent history of all chat traffic on the server.
*/
public interface SpeakObject
{
/** Used in conjunction with {@link SpeakObject#applyToListeners}. */
public static interface ListenerOp
{
/** Call this method if you only have access to body oids. */
void apply (int bodyOid);
/** Call this method if you can provide usernames directly. */
void apply (Name username);
}
/**
* The speak service will call this every time a chat message is delivered on this speak object
* to note the listeners that received the message.
*/
void applyToListeners (ListenerOp op);
}
@@ -0,0 +1,56 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.data;
/**
* A ChatMessage that represents a message that came from the server and did not result from direct
* user action.
*/
public class SystemMessage extends ChatMessage
{
/** Attention level constant to indicate that this message is merely providing the user with
* information. */
public static final byte INFO = 0;
/** Attention level constant to indicate that this message is the result of a user action. */
public static final byte FEEDBACK = 1;
/** Attention level constant to indicate that some action is required. */
public static final byte ATTENTION = 2;
/** The attention level of this message. */
public byte attentionLevel;
// documentation inherited
public SystemMessage ()
{
}
/**
* Construct a SystemMessage.
*/
public SystemMessage (String message, String bundle, byte attentionLevel)
{
super(message, bundle);
this.attentionLevel = attentionLevel;
}
}
@@ -0,0 +1,55 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.data;
import com.threerings.util.Name;
/**
* A feedback message to indicate that a tell succeeded.
*/
public class TellFeedbackMessage extends UserMessage
{
/**
* A tell feedback message is only composed on the client.
*/
public TellFeedbackMessage (Name target, String message, boolean failure)
{
super(target, null, message, ChatCodes.DEFAULT_MODE);
_failure = failure;
}
/**
* Returns true if this is a failure feedback, false if it is successful tell feedback.
*/
public boolean isFailure ()
{
return _failure;
}
@Override
public String getFormat ()
{
return _failure ? null : "m.told_format";
}
protected boolean _failure;
}
@@ -0,0 +1,90 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.data;
import com.threerings.util.Name;
/**
* A ChatMessage representing a message that came from another user.
*/
public class UserMessage extends ChatMessage
{
/** The user that the message came from. */
public Name speaker;
/** The mode of the message. @see ChatCodes.DEFAULT_MODE */
public byte mode;
/**
* For unserialization.
*/
public UserMessage ()
{
}
/**
* Construct a user message.
*/
public UserMessage (Name speaker, String bundle, String message, byte mode)
{
super(message, bundle);
this.speaker = speaker;
this.mode = mode;
}
/**
* Constructs a user message for a player originated tell (which has no bundle and is in the
* default mode).
*/
public UserMessage (Name speaker, String message)
{
super(message, null);
this.speaker = speaker;
this.mode = ChatCodes.DEFAULT_MODE;
}
/**
* Returns the name to display for the speaker. Some types of messages may wish to not use the
* canonical name for the speaker and should thus override this function.
*/
public Name getSpeakerDisplayName ()
{
return speaker;
}
@Override
public String getFormat ()
{
switch (mode) {
case ChatCodes.THINK_MODE: return "m.think_format";
case ChatCodes.EMOTE_MODE: return "m.emote_format";
case ChatCodes.SHOUT_MODE: return "m.shout_format";
case ChatCodes.BROADCAST_MODE: return "m.broadcast_format";
default: // fall through
}
if (ChatCodes.USER_CHAT_TYPE.equals(localtype)) {
return "m.tell_format";
}
return "m.speak_format";
}
}
@@ -0,0 +1,56 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.data;
import com.threerings.util.Name;
/**
* A system message triggered by the activity of another user. If the user is muted we can suppress
* this message, unlike a normal system message.
*/
public class UserSystemMessage extends SystemMessage
{
/** The "speaker" of this message, the user that triggered that this message be sent to us. */
public Name speaker;
/** Suitable for unserialization. */
public UserSystemMessage ()
{
}
/**
* Construct a INFO-level UserSystemMessage.
*/
public UserSystemMessage (Name sender, String message, String bundle)
{
this(sender, message, bundle, INFO);
}
/**
* Construct a UserSystemMessage.
*/
public UserSystemMessage (Name sender, String message, String bundle, byte attentionLevel)
{
super(message, bundle, attentionLevel);
this.speaker = sender;
}
}
@@ -0,0 +1,71 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.server;
import javax.annotation.Generated;
import com.threerings.crowd.chat.data.ChannelSpeakMarshaller;
import com.threerings.crowd.chat.data.ChatChannel;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationDispatcher;
import com.threerings.presents.server.InvocationException;
/**
* Dispatches requests to the {@link ChannelSpeakProvider}.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from ChannelSpeakService.java.")
public class ChannelSpeakDispatcher extends InvocationDispatcher<ChannelSpeakMarshaller>
{
/**
* Creates a dispatcher that may be registered to dispatch invocation
* service requests for the specified provider.
*/
public ChannelSpeakDispatcher (ChannelSpeakProvider provider)
{
this.provider = provider;
}
@Override
public ChannelSpeakMarshaller createMarshaller ()
{
return new ChannelSpeakMarshaller();
}
@Override
public void dispatchRequest (
ClientObject source, int methodId, Object[] args)
throws InvocationException
{
switch (methodId) {
case ChannelSpeakMarshaller.SPEAK:
((ChannelSpeakProvider)provider).speak(
source, (ChatChannel)args[0], (String)args[1], ((Byte)args[2]).byteValue()
);
return;
default:
super.dispatchRequest(source, methodId, args);
return;
}
}
}
@@ -0,0 +1,42 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.server;
import javax.annotation.Generated;
import com.threerings.crowd.chat.client.ChannelSpeakService;
import com.threerings.crowd.chat.data.ChatChannel;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationProvider;
/**
* Defines the server-side of the {@link ChannelSpeakService}.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from ChannelSpeakService.java.")
public interface ChannelSpeakProvider extends InvocationProvider
{
/**
* Handles a {@link ChannelSpeakService#speak} request.
*/
void speak (ClientObject caller, ChatChannel arg1, String arg2, byte arg3);
}
@@ -0,0 +1,503 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.server;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.primitives.Longs;
import com.google.inject.Inject;
import com.samskivert.util.ArrayIntSet;
import com.samskivert.util.ResultListener;
import com.threerings.util.Name;
import com.threerings.presents.annotation.AnyThread;
import com.threerings.presents.client.InvocationService;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.PresentsDObjectMgr;
import com.threerings.presents.peer.data.ClientInfo;
import com.threerings.presents.peer.data.NodeObject;
import com.threerings.presents.peer.server.PeerManager;
import com.threerings.presents.peer.server.PeerManager.NodeRequest;
import com.threerings.presents.peer.server.NodeRequestsListener;
import com.threerings.crowd.chat.data.ChatChannel;
import com.threerings.crowd.chat.data.ChatCodes;
import com.threerings.crowd.chat.data.UserMessage;
import com.threerings.crowd.chat.server.SpeakUtil.ChatHistoryEntry;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.CrowdCodes;
import com.threerings.crowd.peer.data.CrowdClientInfo;
import com.threerings.crowd.peer.data.CrowdNodeObject;
import com.threerings.crowd.peer.server.CrowdPeerManager;
import static com.threerings.crowd.Log.log;
/**
* Handles chat channel services.
*/
public abstract class ChatChannelManager
implements ChannelSpeakProvider
{
/**
* Value asynchronously returned by {@link #collectChatHistory} after polling all peer nodes.
*/
public static class ChatHistoryResult
{
/** The set of nodes that either did not reply within the timeout, or had a failure. */
public Set<String> failedNodes;
/** The things in the user's chat history, aggregated from all nodes and sorted by
* timestamp. */
public List<ChatHistoryEntry> history;
}
/**
* When a body becomes a member of a channel, this method should be called so that any server
* that happens to be hosting that channel can be told that the body in question is now a
* participant.
*/
@AnyThread
public void bodyAddedToChannel (ChatChannel channel, int bodyId)
{
_peerMan.invokeNodeAction(new ParticipantChanged(channel, bodyId, true));
}
/**
* When a body loses channel membership, this method should be called so that any server that
* happens to be hosting that channel can be told that the body in question is now a
* participant.
*/
@AnyThread
public void bodyRemovedFromChannel (ChatChannel channel, int bodyId)
{
_peerMan.invokeNodeAction(new ParticipantChanged(channel, bodyId, false));
}
/**
* Collects all chat messages heard by the given user on all peers.
*/
@AnyThread
public void collectChatHistory (Name user, final ResultListener<ChatHistoryResult> lner)
{
NodeRequestsListener<List<ChatHistoryEntry>> listener =
new NodeRequestsListener<List<ChatHistoryEntry>>() {
public void requestsProcessed (NodeRequestsResult<List<ChatHistoryEntry>> rRes) {
ChatHistoryResult chRes = new ChatHistoryResult();
chRes.failedNodes = rRes.getNodeErrors().keySet();
chRes.history = Lists.newArrayList(
Iterables.concat(rRes.getNodeResults().values()));
Collections.sort(chRes.history, SORT_BY_TIMESTAMP);
lner.requestCompleted(chRes);
}
public void requestFailed (String cause) {
lner.requestFailed(new InvocationException(cause));
}
};
_peerMan.invokeNodeRequest(new ChatCollectionRequest(user), listener);
}
// from interface ChannelSpeakProvider
public void speak (ClientObject caller, final ChatChannel channel, String message, byte mode)
{
final UserMessage umsg = new UserMessage(
((BodyObject)caller).getVisibleName(), null, message, mode);
// if we're hosting this channel, dispatch it directly
if (_channels.containsKey(channel)) {
dispatchSpeak(channel, umsg);
return;
}
// if we're resolving this channel, queue up our message for momentary deliver
List<UserMessage> msgs = _resolving.get(channel);
if (msgs != null) {
msgs.add(umsg);
return;
}
// forward the speak request to the server that hosts the channel in question
_peerMan.invokeNodeAction(new ForwardChannelSpeak(channel, umsg), new Runnable() {
public void run () {
_resolving.put(channel, Lists.newArrayList(umsg));
resolveAndDispatch(channel);
}
});
}
/**
* Creates our singleton manager and registers our invocation service.
*/
@Inject protected ChatChannelManager (PresentsDObjectMgr omgr, InvocationManager invmgr)
{
invmgr.registerDispatcher(new ChannelSpeakDispatcher(this), CrowdCodes.CROWD_GROUP);
// create and start our idle channel closer; this will run as long as omgr is alive
omgr.newInterval(new Runnable() {
public void run () {
closeIdleChannels();
}
}).schedule(IDLE_CHANNEL_CHECK_PERIOD, true);
}
/**
* Resolves the channel specified in the supplied action and then dispatches it.
*/
protected void resolveAndDispatch (final ChatChannel channel)
{
NodeObject.Lock lock = new NodeObject.Lock("ChatChannel", channel.getLockName());
_peerMan.performWithLock(lock, new PeerManager.LockedOperation() {
public void run () {
((CrowdNodeObject)_peerMan.getNodeObject()).addToHostedChannels(channel);
finishResolveAndDispatch(channel);
}
public void fail (String peerName) {
List<UserMessage> msgs = _resolving.remove(channel);
if (peerName == null) {
log.warning("Failed to resolve chat channel due to lock failure",
"channel", channel);
} else {
// some other peer resolved this channel first, so forward any queued messages
// directly to that node
for (UserMessage msg : msgs) {
_peerMan.invokeNodeAction(peerName, new ForwardChannelSpeak(channel, msg));
}
}
}
});
}
/**
* Resolves the participant set for the specified chat channel and dispatches all pending
* messages to the channel. End users of the chat channel system should override this method
* and do what is necessary to resolve the channel's participant set and call {@link
* #resolutionComplete} or {@link #resolutionFailed}.
*/
protected void finishResolveAndDispatch (ChatChannel channel)
{
resolutionComplete(channel, new ArrayIntSet());
}
/**
* This should be called when a channel's participant set has been resolved.
*/
protected void resolutionComplete (ChatChannel channel, Set<Integer> parts)
{
// map the participants of our now resolved channel
ChannelInfo info = new ChannelInfo();
info.channel = channel;
info.participants = parts;
_channels.put(channel, info);
// dispatch any pending messages now that we know where they go
for (UserMessage msg : _resolving.remove(channel)) {
dispatchSpeak(channel, msg);
}
}
/**
* This should be called if channel resolution fails.
*/
protected void resolutionFailed (ChatChannel channel, Exception cause)
{
log.warning("Failed to resolve chat channel", "channel", channel, cause);
// alas, we just drop all pending messages because we're hosed
_resolving.remove(channel);
}
/**
* Requests that we dispatch the supplied message to all participants of the specified chat
* channel. The speaker will be validated prior to dispatching the message as the originating
* server does not have the information it needs to validate the speaker and must leave that to
* us, the channel hosting server.
*/
protected void dispatchSpeak (ChatChannel channel, UserMessage message)
{
final ChannelInfo info = _channels.get(channel);
if (info == null) {
// TODO: maybe we should just reresolve the channel...
log.warning("Requested to dispatch speak on unhosted channel", "channel", channel,
"msg", message);
return;
}
// validate the speaker
if (!info.participants.contains(getBodyId(message.speaker))) {
log.warning("Dropping channel chat message from non-speaker", "channel", channel,
"message", message);
return;
}
// note that we're dispatching a message on this channel
info.lastMessage = System.currentTimeMillis();
// generate a mapping from node name to an array of body ids for the participants that are
// currently on the node in question
final Map<String,int[]> partMap = Maps.newHashMap();
for (NodeObject nodeobj : _peerMan.getNodeObjects()) {
ArrayIntSet nodeBodyIds = new ArrayIntSet();
for (ClientInfo clinfo : nodeobj.clients) {
int bodyId = getBodyId(((CrowdClientInfo)clinfo).visibleName);
if (info.participants.contains(bodyId)) {
nodeBodyIds.add(bodyId);
}
}
partMap.put(nodeobj.nodeName, nodeBodyIds.toIntArray());
}
for (Map.Entry<String,int[]> entry : partMap.entrySet()) {
_peerMan.invokeNodeAction(
entry.getKey(), new DispatchChannelSpeak(channel, message, entry.getValue()));
}
}
/**
* Delivers the supplied chat channel message to the specified bodies.
*/
protected void deliverSpeak (ChatChannel channel, UserMessage message, int[] bodyIds)
{
channel = intern(channel);
for (int bodyId : bodyIds) {
BodyObject bobj = getBodyObject(bodyId);
if (bobj != null && shouldDeliverSpeak(channel, message, bobj)) {
SpeakUtil.recordToChatHistory(channel, message, bobj.getVisibleName());
bobj.postMessage(ChatCodes.CHAT_CHANNEL_NOTIFICATION, channel, message);
}
}
}
/**
* Called periodically to check for and close any channels that have been idle too long.
*/
protected void closeIdleChannels ()
{
long now = System.currentTimeMillis();
Iterator<Map.Entry<ChatChannel, ChannelInfo>> iter = _channels.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<ChatChannel, ChannelInfo> entry = iter.next();
if (now - entry.getValue().lastMessage > IDLE_CHANNEL_CLOSE_TIME) {
((CrowdNodeObject)_peerMan.getNodeObject()).removeFromHostedChannels(
entry.getKey());
iter.remove();
}
}
}
/**
* Ratifies the delivery of the supplied chat channel message to the specified body. Derived
* classes can override this method to implement channel disabling, mute lists or any other
* suppression they might need.
*/
protected boolean shouldDeliverSpeak (ChatChannel channel, UserMessage message, BodyObject body)
{
return true;
}
/**
* Returns a widely referenced instance equivalent to the given channel, if one is available.
* This reduces memory usage since clients send new channel instances with each message.
*/
protected ChatChannel intern (ChatChannel channel)
{
ChannelInfo chinfo = _channels.get(channel);
if (chinfo != null) {
return chinfo.channel;
}
return channel;
}
/**
* Converts a speaker's visible name into a unique integer id. This is not the oid for this
* speaker but rather a persistent integer identifier that can be passed between servers and
* used to look up the speaker on the target server via a call to {@link #getBodyObject}. We
* use this rather than names to avoid having to send (large) {@link Name} objects for every
* channel participant to each individual peer that will be forwarding messages.
*/
protected abstract int getBodyId (Name speaker);
/**
* Locates a body object from the given unique id. May return null.
*/
protected abstract BodyObject getBodyObject (int bodyId);
/** Forwards a channel speak request from the server hosting the message originator to the
* server that is hosting the channel. */
protected abstract static class ChannelAction extends PeerManager.NodeAction
{
public ChannelAction (ChatChannel channel) {
_channel = channel;
}
public ChannelAction () {
}
@Override public boolean isApplicable (NodeObject nodeobj) {
return ((CrowdNodeObject)nodeobj).hostedChannels.contains(_channel);
}
protected ChatChannel _channel;
@Inject protected transient ChatChannelManager _channelMan;
}
/** Informs the server hosting a channel that a body has been added to or removed from the
* channel's participants set. */
protected static class ParticipantChanged extends ChannelAction
{
public ParticipantChanged (ChatChannel channel, int bodyId, boolean added) {
super(channel);
_bodyId = bodyId;
_added = added;
}
public ParticipantChanged () {
}
@Override protected void execute () {
ChannelInfo info = _channelMan._channels.get(_channel);
if (info != null) {
if (_added) {
info.participants.add(_bodyId);
} else {
info.participants.remove(_bodyId);
}
} else if (_channelMan._resolving.containsKey(_channel)) {
log.warning("Oh for fuck's sake, distributed systems are complicated",
"channel", _channel);
}
}
protected int _bodyId;
protected boolean _added;
}
protected static class ChatCollectionRequest extends NodeRequest
{
public ChatCollectionRequest (Name user)
{
_user = user;
}
public ChatCollectionRequest ()
{
}
@Override public boolean isApplicable (NodeObject nodeobj)
{
// poll all nodes
return true;
}
@Override protected void execute (InvocationService.ResultListener listener)
{
// find all the UserMessages for the given user and send them back
listener.requestProcessed(Lists.newArrayList(Iterables.filter(
SpeakUtil.getChatHistory(_user), IS_USER_MESSAGE)));
}
protected Name _user;
}
protected static final Predicate<ChatHistoryEntry> IS_USER_MESSAGE =
new Predicate<ChatHistoryEntry>() {
public boolean apply (ChatHistoryEntry entry) {
return entry.message instanceof UserMessage;
}
};
protected static final Comparator<ChatHistoryEntry> SORT_BY_TIMESTAMP =
new Comparator<ChatHistoryEntry>() {
public int compare (ChatHistoryEntry e1, ChatHistoryEntry e2) {
return Longs.compare(e1.message.timestamp, e2.message.timestamp);
}
};
/** Forwards a channel speak request from the server hosting the message originator to the
* server that is hosting the channel. */
protected static class ForwardChannelSpeak extends ChannelAction
{
public ForwardChannelSpeak (ChatChannel channel, UserMessage message) {
super(channel);
_message = message;
}
public ForwardChannelSpeak () {
}
@Override protected void execute () {
_channelMan.dispatchSpeak(_channel, _message);
}
protected UserMessage _message;
}
/** Forwards a chat channel message to the server to which some subset of the channel
* participants are connected so that it can dispatch the message on their body objects. */
protected static class DispatchChannelSpeak extends ForwardChannelSpeak
{
public DispatchChannelSpeak (ChatChannel channel, UserMessage message, int[] bodyIds) {
super(channel, message);
_bodyIds = bodyIds;
}
public DispatchChannelSpeak () {
}
@Override public boolean isApplicable (NodeObject nodeobj) {
return true; // not used
}
@Override protected void execute () {
_channelMan.deliverSpeak(_channel, _message, _bodyIds);
}
protected int[] _bodyIds;
}
/** Contains metadata for a particular channel. */
protected static class ChannelInfo
{
/** The channel this info is for. */
public ChatChannel channel;
/** The body ids of the participants of this channel. */
public Set<Integer> participants;
/** The time at which a message was last dispatched on this channel. */
public long lastMessage;
}
/** Contains pending messages for all channels currently being resolved. */
protected Map<ChatChannel,List<UserMessage>> _resolving = Maps.newHashMap();
/** A map of resolved channels to metadata records. */
protected Map<ChatChannel,ChannelInfo> _channels = Maps.newHashMap();
/** Provides peer services. */
@Inject protected CrowdPeerManager _peerMan;
/** The period on which we check for idle channels. */
protected static final long IDLE_CHANNEL_CHECK_PERIOD = 5 * 1000L;
/** The amount of idle time (in milliseconds) after which we close a channel. */
protected static final long IDLE_CHANNEL_CLOSE_TIME = 5 * 60 * 1000L;
}
@@ -0,0 +1,85 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.server;
import javax.annotation.Generated;
import com.threerings.crowd.chat.client.ChatService;
import com.threerings.crowd.chat.data.ChatMarshaller;
import com.threerings.presents.client.InvocationService;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationDispatcher;
import com.threerings.presents.server.InvocationException;
import com.threerings.util.Name;
/**
* Dispatches requests to the {@link ChatProvider}.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from ChatService.java.")
public class ChatDispatcher extends InvocationDispatcher<ChatMarshaller>
{
/**
* Creates a dispatcher that may be registered to dispatch invocation
* service requests for the specified provider.
*/
public ChatDispatcher (ChatProvider provider)
{
this.provider = provider;
}
@Override
public ChatMarshaller createMarshaller ()
{
return new ChatMarshaller();
}
@Override
public void dispatchRequest (
ClientObject source, int methodId, Object[] args)
throws InvocationException
{
switch (methodId) {
case ChatMarshaller.AWAY:
((ChatProvider)provider).away(
source, (String)args[0]
);
return;
case ChatMarshaller.BROADCAST:
((ChatProvider)provider).broadcast(
source, (String)args[0], (InvocationService.InvocationListener)args[1]
);
return;
case ChatMarshaller.TELL:
((ChatProvider)provider).tell(
source, (Name)args[0], (String)args[1], (ChatService.TellListener)args[2]
);
return;
default:
super.dispatchRequest(source, methodId, args);
return;
}
}
}
@@ -0,0 +1,302 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.server;
import java.util.Iterator;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.samskivert.util.StringUtil;
import com.threerings.util.MessageBundle;
import com.threerings.util.Name;
import com.threerings.util.TimeUtil;
import com.threerings.presents.client.InvocationService.InvocationListener;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.InvocationProvider;
import com.threerings.crowd.chat.client.ChatService.TellListener;
import com.threerings.crowd.chat.client.ChatService;
import com.threerings.crowd.chat.data.ChatCodes;
import com.threerings.crowd.chat.data.SystemMessage;
import com.threerings.crowd.chat.data.UserMessage;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.CrowdCodes;
import com.threerings.crowd.data.OccupantInfo;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.server.BodyLocal;
import com.threerings.crowd.server.BodyLocator;
import com.threerings.crowd.server.PlaceRegistry;
/**
* The chat provider handles the server side of the chat-related invocation services.
*/
@Singleton
public class ChatProvider
implements InvocationProvider
{
/** Interface to allow an auto response to a tell message. */
public static interface TellAutoResponder
{
/**
* Called following the delivery of <code>message</code> from <code>teller</code> to
* <code>tellee</code>.
*/
void sentTell (BodyObject teller, BodyObject tellee, String message);
}
/** Used to forward certain types of chat messages between servers in a multi-server setup. */
public static interface ChatForwarder
{
/**
* Requests that the supplied tell message be delivered to the appropriate destination.
*
* @return true if the tell was delivered, false otherwise.
*/
boolean forwardTell (UserMessage message, Name target, TellListener listener);
/**
* Requests that the supplied broadcast message be delivered on other servers.
*/
void forwardBroadcast (Name from, byte levelOrMode, String bundle, String msg);
}
/**
* Creates and registers this chat provider.
*/
@Inject public ChatProvider (InvocationManager invmgr)
{
// register a chat provider with the invocation manager
invmgr.registerDispatcher(new ChatDispatcher(this), CrowdCodes.CROWD_GROUP);
}
/**
* Set an object to which all broadcasts should be sent, rather than iterating over the place
* objects and sending to each of them.
*
* @param object an object to send all broadcasts, or null to send to each place object
* instead.
*/
public void setAlternateBroadcastObject (DObject object)
{
_broadcastObject = object;
}
/**
* Set the auto tell responder for the chat provider. Only one auto responder is allowed.
* <em>Note:</em> this only works for same-server tells. If the tell is forwarded to another
* server, no auto-response opportunity is provided (because we never have both body objects in
* the same place).
*/
public void setTellAutoResponder (TellAutoResponder autoRespond)
{
_autoRespond = autoRespond;
}
/**
* Configures the chat forwarder. This is used by the Crowd peer services to forward messages
* between servers in a multi-server cluster.
*/
public void setChatForwarder (ChatForwarder forwarder)
{
_chatForwarder = forwarder;
}
/**
* Processes a {@link ChatService#tell} request.
*/
public void tell (ClientObject caller, Name target, String message, TellListener listener)
throws InvocationException
{
// ensure that the caller has normal chat privileges
InvocationException.requireAccess(caller, ChatCodes.CHAT_ACCESS);
// deliver the tell message to the target
BodyObject source = (BodyObject)caller;
deliverTell(createTellMessage(source, message), target, listener);
// inform the auto-responder if needed
BodyObject targobj;
if (_autoRespond != null && (targobj = _locator.lookupBody(target)) != null) {
_autoRespond.sentTell(source, targobj, message);
}
}
/**
* Processes a {@link ChatService#broadcast} request.
*/
public void broadcast (ClientObject caller, String message, InvocationListener listener)
throws InvocationException
{
// make sure the requesting user has broadcast privileges
InvocationException.requireAccess(caller, ChatCodes.BROADCAST_ACCESS);
BodyObject body = (BodyObject)caller;
broadcast(body.getVisibleName(), null, message, false, true);
}
/**
* Processes a {@link ChatService#away} request.
*/
public void away (ClientObject caller, String message)
{
BodyObject body = (BodyObject)caller;
// we modify this field via an invocation service request because a body object is not
// modifiable by the client
body.setAwayMessage(message);
}
/**
* Broadcasts the specified message to all place objects in the system.
*
* @param from the user the broadcast is from, or null to send the message as a system message.
* @param bundle the bundle, or null if the message needs no translation.
* @param msg the content of the message to broadcast.
* @param attention if true, the message is sent as ATTENTION level, otherwise as INFO. Ignored
* if from is non-null.
* @param forward if true, forward this broadcast on to any registered chat forwarder, if
* false, deliver it only locally on this server.
*/
public void broadcast (Name from, String bundle, String msg, boolean attention, boolean forward)
{
byte levelOrMode = (from != null) ? ChatCodes.BROADCAST_MODE
: (attention ? SystemMessage.ATTENTION : SystemMessage.INFO);
broadcast(from, levelOrMode, bundle, msg, forward);
}
/**
* Broadcast with support for a customizable level or mode.
* @param levelOrMode if from is null, it's an attentionLevel, else it's a mode code.
*/
public void broadcast (Name from, byte levelOrMode, String bundle, String msg, boolean forward)
{
if (_broadcastObject != null) {
broadcastTo(_broadcastObject, from, levelOrMode, bundle, msg);
} else {
for (Iterator<PlaceObject> iter = _plreg.enumeratePlaces(); iter.hasNext(); ) {
PlaceObject plobj = iter.next();
if (plobj.shouldBroadcast()) {
broadcastTo(plobj, from, levelOrMode, bundle, msg);
}
}
}
if (forward && _chatForwarder != null) {
_chatForwarder.forwardBroadcast(from, levelOrMode, bundle, msg);
}
}
/**
* Delivers a tell message to the specified target and notifies the supplied listener of the
* result. It is assumed that the teller has already been permissions checked.
*/
public void deliverTell (UserMessage message, Name target, TellListener listener)
throws InvocationException
{
// make sure the target user is online
BodyObject tobj = _locator.lookupBody(target);
if (tobj == null) {
// if we have a forwarder configured, try forwarding the tell
if (_chatForwarder != null && _chatForwarder.forwardTell(message, target, listener)) {
return;
}
throw new InvocationException(ChatCodes.USER_NOT_ONLINE);
}
if (tobj.status == OccupantInfo.DISCONNECTED) {
String errmsg = MessageBundle.compose(
ChatCodes.USER_DISCONNECTED, TimeUtil.getTimeOrderString(
System.currentTimeMillis() - tobj.getLocal(BodyLocal.class).statusTime,
TimeUtil.SECOND));
throw new InvocationException(errmsg);
}
// deliver a tell notification to the target player
deliverTell(tobj, message);
// let the teller know it went ok
long idle = 0L;
if (tobj.status == OccupantInfo.IDLE) {
idle = System.currentTimeMillis() - tobj.getLocal(BodyLocal.class).statusTime;
}
String awayMessage = null;
if (!StringUtil.isBlank(tobj.awayMessage)) {
awayMessage = tobj.awayMessage;
}
listener.tellSucceeded(idle, awayMessage);
}
/**
* Delivers a tell notification to the specified target player. It is assumed that the message
* is coming from some server entity and need not be permissions checked or notified of the
* result.
*/
public void deliverTell (BodyObject target, UserMessage message)
{
SpeakUtil.sendMessage(target, message);
// note that the teller "heard" what they said
SpeakUtil.noteMessage(message.speaker, message);
}
/**
* Used to create a {@link UserMessage} for the supplied sender.
*/
protected UserMessage createTellMessage (BodyObject source, String message)
{
return new UserMessage(source.getVisibleName(), message);
}
/**
* Direct a broadcast to the specified object.
*/
protected void broadcastTo (
DObject object, Name from, byte levelOrMode, String bundle, String msg)
{
if (from == null) {
SpeakUtil.sendSystem(object, bundle, msg, levelOrMode /* level */);
} else {
SpeakUtil.sendSpeak(object, from, bundle, msg, levelOrMode /* mode */);
}
}
/** Provides access to place managers. */
@Inject protected PlaceRegistry _plreg;
/** Used to look up body objects by name. */
@Inject protected BodyLocator _locator;
/** Generates auto-responses to tells. May be null. */
protected TellAutoResponder _autoRespond;
/** Forwards chat between servers. May be null. */
protected ChatForwarder _chatForwarder;
/** An alternative object to which broadcasts should be sent. */
protected DObject _broadcastObject;
}
@@ -0,0 +1,70 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.server;
import javax.annotation.Generated;
import com.threerings.crowd.chat.data.SpeakMarshaller;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationDispatcher;
import com.threerings.presents.server.InvocationException;
/**
* Dispatches requests to the {@link SpeakProvider}.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from SpeakService.java.")
public class SpeakDispatcher extends InvocationDispatcher<SpeakMarshaller>
{
/**
* Creates a dispatcher that may be registered to dispatch invocation
* service requests for the specified provider.
*/
public SpeakDispatcher (SpeakProvider provider)
{
this.provider = provider;
}
@Override
public SpeakMarshaller createMarshaller ()
{
return new SpeakMarshaller();
}
@Override
public void dispatchRequest (
ClientObject source, int methodId, Object[] args)
throws InvocationException
{
switch (methodId) {
case SpeakMarshaller.SPEAK:
((SpeakProvider)provider).speak(
source, (String)args[0], ((Byte)args[1]).byteValue()
);
return;
default:
super.dispatchRequest(source, methodId, args);
return;
}
}
}
@@ -0,0 +1,104 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.server;
import com.threerings.util.MessageManager;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.server.InvocationManager;
import com.threerings.crowd.chat.client.SpeakService;
import com.threerings.crowd.chat.data.ChatCodes;
import com.threerings.crowd.data.BodyObject;
import static com.threerings.crowd.Log.log;
/**
* Wires up the {@link SpeakService} to a particular distributed object. A server entity can make
* "speech" available among the subscribers of a particular distributed object by constructing a
* speak handler and registering it with the {@link InvocationManager}, then placing the resulting
* marshaller into the distributed object in question so that subscribers to that object can use it
* to generate "speak" requests on that object.
*/
public class SpeakHandler
implements SpeakProvider
{
/**
* Used to prevent abitrary users from issuing speak requests.
*/
public static interface SpeakerValidator
{
/**
* Should return true if the supplied speaker is allowed to speak via the speak provider
* with which this validator was registered.
*/
boolean isValidSpeaker (DObject speakObj, ClientObject speaker, byte mode);
}
/**
* Creates a handler that will provide speech on the supplied distributed object.
*
* @param speakObj the object for which speech requests will be processed.
* @param validator an optional validator that can be used to prevent arbitrary users from
* using the speech services on this object.
*/
public SpeakHandler (DObject speakObj, SpeakerValidator validator)
{
_speakObj = speakObj;
_validator = validator;
}
// from interface SpeakProvider
public void speak (ClientObject caller, String message, byte mode)
{
// ensure that the caller has normal chat privileges
BodyObject source = (BodyObject)caller;
String errmsg = source.checkAccess(ChatCodes.CHAT_ACCESS, null);
if (errmsg != null) {
// we normally don't listen for responses to speak messages so we can't just throw an
// InvocationException we have to specifically communicate the error to the user
SpeakUtil.sendFeedback(source, MessageManager.GLOBAL_BUNDLE, errmsg);
return;
}
// TODO: broadcast should be handled more like a system message rather than as a mode for a
// user message so that we don't have to do this validation here. Or not.
// ensure that the speaker is valid
if ((mode == ChatCodes.BROADCAST_MODE) ||
(_validator != null && !_validator.isValidSpeaker(_speakObj, caller, mode))) {
log.warning("Refusing invalid speak request", "caller", caller.who(),
"speakObj", _speakObj.which(), "message", message, "mode", mode);
} else {
// issue the speak message on our speak object
SpeakUtil.sendSpeak(_speakObj, source.getVisibleName(), null, message, mode);
}
}
/** Our speech object. */
protected DObject _speakObj;
/** The entity that will validate our speakers. */
protected SpeakerValidator _validator;
}
@@ -0,0 +1,41 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.server;
import javax.annotation.Generated;
import com.threerings.crowd.chat.client.SpeakService;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationProvider;
/**
* Defines the server-side of the {@link SpeakService}.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from SpeakService.java.")
public interface SpeakProvider extends InvocationProvider
{
/**
* Handles a {@link SpeakService#speak} request.
*/
void speak (ClientObject caller, String arg1, byte arg2);
}
@@ -0,0 +1,392 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.chat.server;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.samskivert.util.ObserverList;
import com.threerings.util.Name;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.crowd.chat.data.ChatChannel;
import com.threerings.crowd.chat.data.ChatCodes;
import com.threerings.crowd.chat.data.ChatMessage;
import com.threerings.crowd.chat.data.KeepNoHistory;
import com.threerings.crowd.chat.data.SpeakObject;
import com.threerings.crowd.chat.data.SystemMessage;
import com.threerings.crowd.chat.data.UserMessage;
import com.threerings.crowd.data.BodyObject;
import com.threerings.io.Streamable;
import static com.threerings.crowd.Log.log;
/**
* Provides the back-end of the chat speaking facilities.
*/
public class SpeakUtil
{
/**
* An interface used to notify external systems whenever a chat message is spoken by one user
* and heard by another.
*/
public static interface MessageObserver
{
/**
* Called for each player that hears a particular chat message.
*/
void messageDelivered (Name hearer, UserMessage message);
}
/**
* Recorded parcel of chat for historical purposes, maintained by
* {@link #recordToChatHistory(ChatChannel, UserMessage, Name...)},
* {@link #getChatHistory(Name)}, and {@link #clearHistory(Name)}.
*/
public static class ChatHistoryEntry
implements Streamable
{
/** The channel on which the message was sent, of null if the channel manager was not
* used. */
public ChatChannel channel;
/** The message sent. */
public ChatMessage message;
/** For deserialization. */
public ChatHistoryEntry ()
{
}
/**
* Creates a new history entry.
*/
public ChatHistoryEntry (ChatChannel channel, ChatMessage message)
{
this.channel = channel;
this.message = message;
}
}
/**
* Registers a {@link MessageObserver} to be notified whenever a user-originated chat message
* is heard by another user.
*/
public static void registerMessageObserver (MessageObserver obs)
{
_messageObs.add(obs);
}
/**
* Removes a registration made previously with {@link #registerMessageObserver}.
*/
public static void removeMessageObserver (MessageObserver obs)
{
_messageObs.remove(obs);
}
/**
* Sends a speak notification to the specified place object originating with the specified
* speaker (the speaker optionally being a server entity that wishes to fake a "speak" message)
* and with the supplied message content.
*
* @param speakObj the object on which to generate the speak message.
* @param speaker the username of the user that generated the message (or some special speaker
* name for server messages).
* @param bundle null when the message originates from a real human, the bundle identifier that
* will be used by the client to translate the message text when the message originates from a
* server entity "faking" a chat message.
* @param message the text of the speak message.
*/
public static void sendSpeak (DObject speakObj, Name speaker, String bundle, String message)
{
sendSpeak(speakObj, speaker, bundle, message, ChatCodes.DEFAULT_MODE);
}
/**
* Sends a speak notification to the specified place object originating with the specified
* speaker (the speaker optionally being a server entity that wishes to fake a "speak" message)
* and with the supplied message content.
*
* @param speakObj the object on which to generate the speak message.
* @param speaker the username of the user that generated the message (or some special speaker
* name for server messages).
* @param bundle null when the message originates from a real human, the bundle identifier that
* will be used by the client to translate the message text when the message originates from a
* server entity "faking" a chat message.
* @param message the text of the speak message.
* @param mode the mode of the message, see {@link ChatCodes#DEFAULT_MODE}.
*/
public static void sendSpeak (DObject speakObj, Name speaker, String bundle, String message,
byte mode)
{
sendMessage(speakObj, new UserMessage(speaker, bundle, message, mode));
}
/**
* Sends a system INFO message notification to the specified object with the supplied message
* content. A system message is one that will be rendered where the speak messages are
* rendered, but in a way that makes it clear that it is a message from the server.
*
* Info messages are sent when something happens that was neither directly triggered by the
* user, nor requires direct action.
*
* @param speakObj the object on which to deliver the message.
* @param bundle the name of the localization bundle that should be used to translate this
* system message prior to displaying it to the client.
* @param message the text of the message.
*/
public static void sendInfo (DObject speakObj, String bundle, String message)
{
sendSystem(speakObj, bundle, message, SystemMessage.INFO);
}
/**
* Sends a system FEEDBACK message notification to the specified object with the supplied
* message content. A system message is one that will be rendered where the speak messages are
* rendered, but in a way that makes it clear that it is a message from the server.
*
* Feedback messages are sent in direct response to a user action, usually to indicate success
* or failure of the user's action.
*
* @param speakObj the object on which to deliver the message.
* @param bundle the name of the localization bundle that should be used to translate this
* system message prior to displaying it to the client.
* @param message the text of the message.
*/
public static void sendFeedback (DObject speakObj, String bundle, String message)
{
sendSystem(speakObj, bundle, message, SystemMessage.FEEDBACK);
}
/**
* Sends a system ATTENTION message notification to the specified object with the supplied
* message content. A system message is one that will be rendered where the speak messages are
* rendered, but in a way that makes it clear that it is a message from the server.
*
* Attention messages are sent when something requires user action that did not result from
* direct action by the user.
*
* @param speakObj the object on which to deliver the message.
* @param bundle the name of the localization bundle that should be used to translate this
* system message prior to displaying it to the client.
* @param message the text of the message.
*/
public static void sendAttention (DObject speakObj, String bundle, String message)
{
sendSystem(speakObj, bundle, message, SystemMessage.ATTENTION);
}
/**
* Send the specified message on the specified object.
*/
public static void sendMessage (DObject speakObj, ChatMessage msg)
{
if (speakObj == null) {
log.warning("Dropping speak message, no speak obj '" + msg + "'.", new Exception());
return;
}
// post the message to the relevant object
speakObj.postMessage(ChatCodes.CHAT_NOTIFICATION, new Object[] { msg });
// if this is a user message; add it to the heard history of all users that can "hear" it
if (!(msg instanceof UserMessage)) {
return;
} else if (speakObj instanceof SpeakObject) {
_messageMapper.omgr = (RootDObjectManager)speakObj.getManager();
_messageMapper.message = (UserMessage)msg;
((SpeakObject)speakObj).applyToListeners(_messageMapper);
_messageMapper.omgr = null;
_messageMapper.message = null;
} else {
log.info("Unable to note listeners", "dclass", speakObj.getClass(), "msg", msg);
}
}
/**
* Returns a list of {@link ChatMessage} objects to which this user has been privy in the
* recent past. If the given name implements {@link KeepNoHistory}, null is returned.
*/
public static List<ChatHistoryEntry> getChatHistory (Name username)
{
List<ChatHistoryEntry> history = getHistoryList(username);
if (history != null) {
pruneHistory(System.currentTimeMillis(), history);
}
return history;
}
/**
* Called to clear the chat history for the specified user.
*/
public static void clearHistory (Name username)
{
// Log.info("Clearing history for " + username + ".");
_histories.remove(username);
}
/**
* Records the specified channel and message to the specified users' chat histories. If {@link
* ChatMessage#timestamp} is not already filled in, it will be.
*/
public static void recordToChatHistory (
ChatChannel channel, UserMessage msg, Name... usernames)
{
// fill in the message's time stamp if necessary
if (msg.timestamp == 0L) {
msg.timestamp = System.currentTimeMillis();
}
for (Name username : usernames) {
// add the message to this user's chat history
List<ChatHistoryEntry> history = getHistoryList(username);
if (history == null) {
continue;
}
history.add(new ChatHistoryEntry(channel, msg));
// if the history is big enough, potentially prune it (we always prune when asked for
// the history, so this is just to balance memory usage with CPU expense)
if (history.size() > 15) {
pruneHistory(msg.timestamp, history);
}
}
}
/**
* Notes that the specified user was privy to the specified message. If {@link
* ChatMessage#timestamp} is not already filled in, it will be.
*/
protected static void noteMessage (Name username, UserMessage msg)
{
// fill in the message's time stamp if necessary
if (msg.timestamp == 0L) {
msg.timestamp = System.currentTimeMillis();
}
recordToChatHistory(null, msg, username);
// Log.info("Noted that " + username + " heard " + msg + ".");
// notify any message observers
_messageOp.init(username, msg);
_messageObs.apply(_messageOp);
}
/**
* Send the specified system message on the specified dobj.
*/
protected static void sendSystem (DObject speakObj, String bundle, String message, byte level)
{
sendMessage(speakObj, new SystemMessage(message, bundle, level));
}
/**
* Returns this user's chat history, creating one if necessary. If the given name implements
* {@link KeepNoHistory}, null is returned.
*/
protected static List<ChatHistoryEntry> getHistoryList (Name username)
{
if (username instanceof KeepNoHistory) {
return null;
}
List<ChatHistoryEntry> history = _histories.get(username);
if (history == null) {
_histories.put(username, history = Lists.newArrayList());
}
return history;
}
/**
* Prunes all messages from this history which are expired.
*/
protected static void pruneHistory (long now, List<ChatHistoryEntry> history)
{
int prunepos = 0;
for (int ll = history.size(); prunepos < ll; prunepos++) {
ChatHistoryEntry entry = history.get(prunepos);
if (now - entry.message.timestamp < HISTORY_EXPIRATION) {
break; // stop when we get to the first valid message
}
}
history.subList(0, prunepos).clear();
}
/** Used to note the recipients of a chat message. */
protected static class MessageMapper implements SpeakObject.ListenerOp
{
public RootDObjectManager omgr;
public UserMessage message;
public void apply (int bodyOid) {
DObject dobj = omgr.getObject(bodyOid);
if (dobj != null && dobj instanceof BodyObject) {
noteMessage(((BodyObject)dobj).getVisibleName(), message);
}
}
public void apply (Name username) {
noteMessage(username, message);
}
}
/** Used to notify our {@link MessageObserver}s. */
protected static class MessageObserverOp
implements ObserverList.ObserverOp<MessageObserver>
{
public void init (Name hearer, UserMessage message) {
_hearer = hearer;
_message = message;
}
public boolean apply (MessageObserver observer) {
observer.messageDelivered(_hearer, _message);
return true;
}
protected Name _hearer;
protected UserMessage _message;
}
/** Recent chat history for the server. */
protected static Map<Name, List<ChatHistoryEntry>> _histories = Maps.newHashMap();
/** Used to note the recipients of a chat message. */
protected static MessageMapper _messageMapper = new MessageMapper();
/** A list of {@link MessageObserver}s. */
protected static ObserverList<MessageObserver> _messageObs = ObserverList.newFastUnsafe();
/** Used to notify our {@link MessageObserver}s. */
protected static MessageObserverOp _messageOp = new MessageObserverOp();
/** The amount of time before chat history becomes... history. */
protected static final long HISTORY_EXPIRATION = 5L * 60L * 1000L;
}
@@ -0,0 +1,37 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
/**
* The client side of the body-related invocation services.
*/
public interface BodyService extends InvocationService
{
/**
* Requests to set the idle state of the client to the specified
* value.
*/
void setIdle (Client client, boolean idle);
}
@@ -0,0 +1,53 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import com.threerings.crowd.data.PlaceObject;
/**
* The location adapter makes life easier for a class that really only
* cares about one or two of the location observer callbacks and doesn't
* want to provide empty implementations of the others. One can either
* extend location adapter, or create an anonymous instance that overrides
* the desired callback(s). Note that the location adapter defaults to
* ratifying any location change.
*
* @see LocationObserver
*/
public class LocationAdapter implements LocationObserver
{
// documentation inherited
public boolean locationMayChange (int placeId)
{
return true;
}
// documentation inherited
public void locationDidChange (PlaceObject place)
{
}
// documentation inherited
public void locationChangeFailed (int placeId, String reason)
{
}
}
@@ -0,0 +1,68 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import com.threerings.presents.client.InvocationDecoder;
/**
* Dispatches calls to a {@link LocationReceiver} instance.
*/
public class LocationDecoder extends InvocationDecoder
{
/** The generated hash code used to identify this receiver class. */
public static final String RECEIVER_CODE = "58f2830e027f4f3377e100ef12332497";
/** The method id used to dispatch {@link LocationReceiver#forcedMove}
* notifications. */
public static final int FORCED_MOVE = 1;
/**
* Creates a decoder that may be registered to dispatch invocation
* service notifications to the specified receiver.
*/
public LocationDecoder (LocationReceiver receiver)
{
this.receiver = receiver;
}
@Override
public String getReceiverCode ()
{
return RECEIVER_CODE;
}
@Override
public void dispatchNotification (int methodId, Object[] args)
{
switch (methodId) {
case FORCED_MOVE:
((LocationReceiver)receiver).forcedMove(
((Integer)args[0]).intValue()
);
return;
default:
super.dispatchNotification(methodId, args);
return;
}
}
}
@@ -0,0 +1,628 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import java.util.ArrayList;
import com.google.common.collect.Lists;
import com.samskivert.util.ObserverList;
import com.samskivert.util.ResultListener;
import com.samskivert.util.ObserverList.ObserverOp;
import com.threerings.presents.client.BasicDirector;
import com.threerings.presents.client.Client;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.util.SafeSubscriber;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.CrowdCodes;
import com.threerings.crowd.data.LocationCodes;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.util.CrowdContext;
import static com.threerings.crowd.Log.log;
/**
* The location director provides a means by which entities on the client can request to move from
* place to place and can be notified if other entities have caused the client to move to a new
* place. It also provides a mechanism for ratifying a request to move to a new place before
* actually issuing the request.
*/
public class LocationDirector extends BasicDirector
implements LocationCodes, LocationReceiver
{
/**
* Used to recover from a moveTo request that was accepted but resulted in a failed attempt to
* fetch the place object to which we were moving.
*/
public static interface FailureHandler
{
/**
* Should instruct the client to move to the last known working location (as well as clean
* up after the failed moveTo request).
*/
void recoverFailedMove (int placeId);
}
/**
* Constructs a location director which will configure itself for operation using the supplied
* context.
*/
public LocationDirector (CrowdContext ctx)
{
super(ctx);
// keep this around for later
_ctx = ctx;
// register for location notifications
_ctx.getClient().getInvocationDirector().registerReceiver(new LocationDecoder(this));
}
/**
* Adds a location observer to the list. This observer will subsequently be notified of
* potential, effected and failed location changes.
*/
public void addLocationObserver (LocationObserver observer)
{
_observers.add(observer);
}
/**
* Removes a location observer from the list.
*/
public void removeLocationObserver (LocationObserver observer)
{
_observers.remove(observer);
}
/**
* Returns the place object for the location we currently occupy or null if we're not currently
* occupying any location.
*/
public PlaceObject getPlaceObject ()
{
return _plobj;
}
/**
* Returns true if there is a pending move request.
*/
public boolean movePending ()
{
return (_pendingPlaceId > 0);
}
/**
* Requests that this client be moved to the specified place. A request will be made and when
* the response is received, the location observers will be notified of success or failure.
*
* @return true if the move to request was issued, false if it was rejected by a location
* observer or because we have another request outstanding.
*/
public boolean moveTo (int placeId)
{
// make sure the placeId is valid
if (placeId < 0) {
log.warning("Refusing moveTo(): invalid placeId " + placeId + ".");
return false;
}
// first check to see if our observers are happy with this move request
if (!mayMoveTo(placeId, null)) {
return false;
}
// we need to call this both to mark that we're issuing a move request and to check to see
// if the last issued request should be considered stale
boolean refuse = checkRepeatMove();
// complain if we're over-writing a pending request
if (_pendingPlaceId != -1) {
// if the pending request has been outstanding more than a minute, go ahead and let
// this new one through in an attempt to recover from dropped moveTo requests
if (refuse) {
log.warning("Refusing moveTo; We have a request outstanding",
"ppid", _pendingPlaceId, "npid", placeId);
return false;
} else {
log.warning("Overriding stale moveTo request", "ppid", _pendingPlaceId,
"npid", placeId);
}
}
// make a note of our pending place id
_pendingPlaceId = placeId;
// issue a moveTo request
log.info("Issuing moveTo(" + placeId + ").");
_lservice.moveTo(_ctx.getClient(), placeId, new LocationService.MoveListener() {
public void moveSucceeded (PlaceConfig config) {
// handle the successful move
didMoveTo(_pendingPlaceId, config);
// and clear out the tracked pending oid
_pendingPlaceId = -1;
handlePendingForcedMove();
}
public void requestFailed (String reason) {
// clear out our pending request oid
int placeId = _pendingPlaceId;
_pendingPlaceId = -1;
log.info("moveTo failed", "pid", placeId, "reason", reason);
// let our observers know that something has gone horribly awry
handleFailure(placeId, reason);
handlePendingForcedMove();
}
});
return true;
}
/**
* Requests to move to the room that we last occupied, if such a room exists.
*
* @return true if we had a previous room and we requested to move to it, false if we had no
* previous room.
*/
public boolean moveBack ()
{
if (_previousPlaceId == -1) {
return false;
} else {
moveTo(_previousPlaceId);
return true;
}
}
/**
* Issues a request to leave our current location.
*
* @return true if we were able to leave, false if we are in the middle of moving somewhere and
* can't yet leave.
*/
public boolean leavePlace ()
{
if (_pendingPlaceId != -1) {
return false;
}
_lservice.leavePlace(_ctx.getClient());
didLeavePlace();
// let our observers know that we're no longer in a location
_observers.apply(_didChangeOp);
return true;
}
/**
* This can be called by cooperating directors that need to coopt the moving process to extend
* it in some way or other. In such situations, they should call this method before moving to a
* new location to check to be sure that all of the registered location observers are amenable
* to a location change.
*
* @param placeId the place oid of our tentative new location.
*
* @return true if everyone is happy with the move, false if it was vetoed by one of the
* location observers.
*/
public boolean mayMoveTo (final int placeId, ResultListener<PlaceConfig> rl)
{
final boolean[] vetoed = new boolean[1];
_observers.apply(new ObserverOp<LocationObserver>() {
public boolean apply (LocationObserver obs) {
vetoed[0] = (vetoed[0] || !obs.locationMayChange(placeId));
return true;
}
});
// if we're actually going somewhere, let the controller know that we might be leaving
mayLeavePlace();
// if we have a result listener, let it know if we failed or keep it for later if we're
// still going
if (rl != null) {
if (vetoed[0]) {
rl.requestFailed(new MoveVetoedException());
} else {
_moveListener = rl;
}
}
// and return the result
return !vetoed[0];
}
/**
* Called to inform our controller that we may be leaving the current place.
*/
protected void mayLeavePlace ()
{
if (_controller != null) {
try {
_controller.mayLeavePlace(_plobj);
} catch (Exception e) {
log.warning("Place controller choked in mayLeavePlace", "plobj", _plobj, e);
}
}
}
/**
* This can be called by cooperating directors that need to coopt the moving process to extend
* it in some way or other. In such situations, they will be responsible for receiving the
* successful move response and they should let the location director know that the move has
* been effected.
*
* @param placeId the place oid of our new location.
* @param config the configuration information for the new place.
*/
public void didMoveTo (int placeId, PlaceConfig config)
{
if (_moveListener != null) {
_moveListener.requestCompleted(config);
_moveListener = null;
}
// keep track of our previous place id
_previousPlaceId = _placeId;
// clear out our last request time
_lastRequestTime = 0;
// do some cleaning up in case we were previously in a place
didLeavePlace();
// make a note that we're now mostly in the new location
_placeId = placeId;
// start up a new place controller to manage the new place
try {
_controller = createController(config);
if (_controller == null) {
log.warning("Place config returned null controller", "config", config);
return;
}
_controller.init(_ctx, config);
// subscribe to our new place object to complete the move
_subber = new SafeSubscriber<PlaceObject>(_placeId, new Subscriber<PlaceObject>() {
public void objectAvailable (PlaceObject object) {
gotPlaceObject(object);
}
public void requestFailed (int oid, ObjectAccessException cause) {
// aiya! we were unable to fetch our new place object; something is badly wrong
log.warning("Aiya! Unable to fetch place object for new location", "plid", oid,
"reason", cause);
// clear out our half initialized place info
int placeId = _placeId;
_placeId = -1;
// let the kids know shit be fucked
handleFailure(placeId, "m.unable_to_fetch_place_object");
}
});
_subber.subscribe(_ctx.getDObjectManager());
} catch (Exception e) {
log.warning("Failed to create place controller", "config", config, e);
handleFailure(_placeId, LocationCodes.E_INTERNAL_ERROR);
}
}
/**
* Called when we're leaving our current location. Informs the location's controller that we're
* departing, unsubscribes from the location's place object, and clears out our internal place
* information.
*/
public void didLeavePlace ()
{
// unsubscribe from our old place object
if (_subber != null) {
_subber.unsubscribe(_ctx.getDObjectManager());
_subber = null;
}
// let the old controller know that things are going away
if (_plobj != null && _controller != null) {
try {
_controller.didLeavePlace(_plobj);
} catch (Exception e) {
log.warning("Place controller choked in didLeavePlace", "plobj", _plobj, e);
}
}
// and clear out other bits
_plobj = null;
_controller = null;
_placeId = -1;
}
/**
* This can be called by cooperating directors that need to coopt the moving process to extend
* it in some way or other. If the coopted move request fails, this failure can be propagated
* to the location observers if appropriate.
*
* @param placeId the place oid to which we failed to move.
* @param reason the reason code given for failure.
*/
public void failedToMoveTo (int placeId, String reason)
{
if (_moveListener != null) {
_moveListener.requestFailed(new MoveFailedException(reason));
_moveListener = null;
}
// clear out our last request time
_lastRequestTime = 0;
// let our observers know what's up
handleFailure(placeId, reason);
}
/**
* Called to test and set a time stamp that we use to determine if a pending moveTo request is
* stale.
*/
public boolean checkRepeatMove ()
{
long now = System.currentTimeMillis();
if (now - _lastRequestTime < STALE_REQUEST_DURATION) {
return true;
} else {
_lastRequestTime = now;
return false;
}
}
@Override
public void clientDidLogon (Client client)
{
super.clientDidLogon(client);
// subscribe to our body object
Subscriber<BodyObject> sub = new Subscriber<BodyObject>() {
public void objectAvailable (BodyObject object) {
gotBodyObject(object);
}
public void requestFailed (int oid, ObjectAccessException cause) {
log.warning("Location director unable to fetch body object; all has gone " +
"horribly wrong", "cause", cause);
}
};
int cloid = client.getClientOid();
client.getDObjectManager().subscribeToObject(cloid, sub);
}
@Override
public void clientDidLogoff (Client client)
{
super.clientDidLogoff(client);
// clear ourselves out and inform observers of our departure
mayLeavePlace();
didLeavePlace();
// let our observers know that we're no longer in a location
_observers.apply(_didChangeOp);
// clear out everything else (it's possible that we were logged off in the middle of a
// change location request)
_pendingPlaceId = -1;
_pendingForcedMoves.clear();
_previousPlaceId = -1;
_lastRequestTime = 0L;
_lservice = null;
}
@Override
protected void registerServices (Client client)
{
client.addServiceGroup(CrowdCodes.CROWD_GROUP);
}
@Override
protected void fetchServices (Client client)
{
// obtain our service handle
_lservice = client.requireService(LocationService.class);
}
protected void gotPlaceObject (PlaceObject object)
{
// yay, we have our new place object
_plobj = object;
// fill in our manager caller
_plobj.initManagerCaller(_ctx.getClient().getDObjectManager());
// let the place controller know that we're ready to roll
if (_controller != null) {
try {
_controller.willEnterPlace(_plobj);
} catch (Exception e) {
log.warning("Controller choked in willEnterPlace", "place", _plobj, e);
}
}
// let our observers know that all is well on the western front
_observers.apply(_didChangeOp);
}
protected void gotBodyObject (BodyObject clobj)
{
// TODO? check to see if we are already in a location, in which case we'll want to be going
// there straight away
}
// documentation inherited from interface
public void forcedMove (final int placeId)
{
// if we're in the middle of a move, we can't abort it or we will screw everything up, so
// just finish up what we're doing and assume that the repeated move request was the
// spurious one as it would be in the case of lag causing rapid-fire repeat requests
if (movePending()) {
if (_pendingPlaceId == placeId) {
log.info("Dropping forced move because we have a move pending",
"pendId", _pendingPlaceId, "reqId", placeId);
} else {
log.info("Delaying forced move because we have a move pending",
"pendId", _pendingPlaceId, "reqId", placeId);
addPendingForcedMove(new Runnable() {
public void run () {
forcedMove(placeId);
}
});
}
return;
}
log.info("Moving at request of server", "placeId", placeId);
// clear out our old place information
mayLeavePlace();
didLeavePlace();
// move to the new place
moveTo(placeId);
}
/**
* Sets the failure handler which will recover from place object fetching failures. In the
* event that we are unable to fetch our place object after making a successful moveTo request,
* we attempt to rectify the failure by moving back to the last known working location. Because
* entites that cooperate with the location director may need to become involved in this
* failure recovery, we provide this interface whereby they can interject themseves into the
* failure recovery process and do their own failure recovery.
*/
public void setFailureHandler (FailureHandler handler)
{
if (_failureHandler != null) {
log.warning("Requested to set failure handler, but we've already got one. The " +
"conflicting entities will likely need to perform more sophisticated " +
"coordination to deal with failures.",
"old", _failureHandler, "new", handler);
} else {
_failureHandler = handler;
}
}
protected void handleFailure (final int placeId, final String reason)
{
_observers.apply(new ObserverOp<LocationObserver>() {
public boolean apply (LocationObserver obs) {
obs.locationChangeFailed(placeId, reason);
return true;
}
});
// try to return to our previous location
if (_failureHandler != null) {
_failureHandler.recoverFailedMove(placeId);
} else if (_placeId <= 0) {
// if we were previously somewhere (and that somewhere isn't where we just tried to
// go), try going back to that happy place
if (_previousPlaceId != -1 && _previousPlaceId != placeId) {
moveTo(_previousPlaceId);
}
} // else we're currently somewhere, so just stay there
}
/**
* Called to create our place controller using the supplied place configuration. This lives in
* a separate method so that derived instances can do funny class loader business if necessary
* to load the place controller using a sandboxed class loader.
*/
protected PlaceController createController (PlaceConfig config)
{
return config.createController();
}
public void addPendingForcedMove (Runnable move)
{
_pendingForcedMoves.add(move);
}
protected void handlePendingForcedMove ()
{
if (!_pendingForcedMoves.isEmpty()) {
_ctx.getClient().getRunQueue().postRunnable(_pendingForcedMoves.remove(0));
}
}
/** The context through which we access needed services. */
protected CrowdContext _ctx;
/** Provides access to location services. */
protected LocationService _lservice;
/** Our location observer list. */
protected ObserverList<LocationObserver> _observers = new ObserverList<LocationObserver>(
ObserverList.SAFE_IN_ORDER_NOTIFY);
/** Used to subscribe to our place object. */
protected SafeSubscriber<PlaceObject> _subber;
/** The oid of the place we currently occupy. */
protected int _placeId = -1;
/** The place object that we currently occupy. */
protected PlaceObject _plobj;
/** The place controller in effect for our current place. */
protected PlaceController _controller;
/** The oid of the place for which we have an outstanding moveTo request, or -1 if we have no
* outstanding request. */
protected int _pendingPlaceId = -1;
/** The oid of the place we previously occupied. */
protected int _previousPlaceId = -1;
/** The last time we requested a move to. */
protected long _lastRequestTime;
/** The entity that deals when we fail to subscribe to a place object. */
protected FailureHandler _failureHandler;
/** A listener that wants to know if we succeeded or how we failed to move. */
protected ResultListener<PlaceConfig> _moveListener;
/** Forced move actions we should take once we complete the move we're in the middle of. */
protected ArrayList<Runnable> _pendingForcedMoves = Lists.newArrayList();
/** The operation used to inform observers that the location changed. */
protected ObserverOp<LocationObserver> _didChangeOp = new ObserverOp<LocationObserver>() {
public boolean apply (LocationObserver obs) {
obs.locationDidChange(_plobj);
return true;
}
};
/** We require that a moveTo request be outstanding for one minute before it is declared to be
* stale. */
protected static final long STALE_REQUEST_DURATION = 60L * 1000L;
}
@@ -0,0 +1,67 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import com.threerings.crowd.data.PlaceObject;
/**
* The location observer interface makes it possible for entities to be
* notified when the client moves to a new location. It also provides a
* means for an entity to participate in the ratification process of a new
* location. Observers may opt to reject a request to change to a new
* location, probably because something is going on in the previous
* location that should not be abandoned.
*
* <p> Note that these location callbacks occur on the main thread and
* should execute quickly and not block under any circumstance.
*/
public interface LocationObserver
{
/**
* Called when someone has requested that we switch to a new location.
* An observer may choose to veto the location change request for some
* reason or other.
*
* @return true if it's OK for the location to change, false if the
* change request should be aborted.
*/
boolean locationMayChange (int placeId);
/**
* Called when we have switched to a new location.
*
* @param place the place object that represents the new location or
* null if we have switched to no location.
*/
void locationDidChange (PlaceObject place);
/**
* This is called on all location observers when a location change
* request is rejected by the server or fails for some other reason.
*
* @param placeId the place id to which we attempted to relocate, but
* failed.
* @param reason the reason code that explains why the location change
* request was rejected or otherwise failed.
*/
void locationChangeFailed (int placeId, String reason);
}
@@ -0,0 +1,39 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import com.threerings.presents.client.InvocationReceiver;
/**
* Defines, for the location services, a set of notifications delivered
* asynchronously by the server to the client.
*/
public interface LocationReceiver extends InvocationReceiver
{
/**
* Used to communicate a required move notification to the client. The
* server will have removed the client from their existing location
* and the client is then responsible for generating a {@link
* LocationService#moveTo} request to move to the new location.
*/
void forcedMove (int placeId);
}
@@ -0,0 +1,61 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
import com.threerings.crowd.data.PlaceConfig;
/**
* The location services provide a mechanism by which the client can request to move from place to
* place in the server. These services should not be used directly, but instead should be accessed
* via the {@link LocationDirector}.
*/
public interface LocationService extends InvocationService
{
/**
* Used to communicate responses to {@link LocationService#moveTo} requests.
*/
public static interface MoveListener extends InvocationListener
{
/**
* Called in response to a successful {@link LocationService#moveTo} request.
*/
void moveSucceeded (PlaceConfig config);
}
/**
* Requests that this client's body be moved to the specified location.
*
* @param client a reference to the client object that defines the context in which this
* invocation service should be executed.
* @param placeId the object id of the place object to which the body should be moved.
* @param listener the listener that will be informed of success or failure.
*/
void moveTo (Client client, int placeId, MoveListener listener);
/**
* Requests that we leave our current place and move to nowhere land.
*/
void leavePlace (Client client);
}
@@ -0,0 +1,33 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
/**
* An exception that indicates that the server did not allow us to move.
*/
public class MoveFailedException extends Exception
{
public MoveFailedException (String message)
{
super(message);
}
}
@@ -0,0 +1,29 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
/**
* An exception that indicates that a LocationObserver vetoed our move request.
*/
public class MoveVetoedException extends Exception
{
}
@@ -0,0 +1,49 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import com.threerings.crowd.data.OccupantInfo;
/**
* The occupant adapter makes life easier for occupant observer classes
* that only care about one or two of the occupant observer
* callbacks. They can either extend occupant adapter or create an
* anonymous class that extends it and overrides just the callbacks they
* care about.
*/
public class OccupantAdapter implements OccupantObserver
{
// documentation inherited from interface
public void occupantEntered (OccupantInfo info)
{
}
// documentation inherited from interface
public void occupantLeft (OccupantInfo info)
{
}
// documentation inherited from interface
public void occupantUpdated (OccupantInfo oinfo, OccupantInfo info)
{
}
}
@@ -0,0 +1,215 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import com.samskivert.util.ObserverList;
import com.threerings.util.Name;
import com.threerings.presents.client.BasicDirector;
import com.threerings.presents.client.Client;
import com.threerings.presents.dobj.EntryAddedEvent;
import com.threerings.presents.dobj.EntryRemovedEvent;
import com.threerings.presents.dobj.EntryUpdatedEvent;
import com.threerings.presents.dobj.SetListener;
import com.threerings.crowd.data.OccupantInfo;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.util.CrowdContext;
/**
* The occupant director listens for occupants of places to enter and
* exit, and dispatches notices to interested parties about these events.
*
* <p> It will eventually provide a framework for keeping track of
* occupant information in a network efficient manner. The idea being that
* we want to store as little information about occupants as possible in
* the place object (probably just body oid and username), but upon
* entering a place, this will be all we know about the occupants. We then
* dispatch a request to get information about all of the occupants in the
* room (things like avatar information for a graphical display or perhaps
* their ratings in the game that is associated with a place for a gaming
* site) which we then pass on to the occupant observers when it becomes
* available.
*
* <p> This information would be cached and we could return cached
* information for occupants for which we have cached info. We will
* probably want to still make a request for the occupant info so that we
* can update non-static occupant data rather than permanently using
* what's in the cache.
*/
public class OccupantDirector extends BasicDirector
implements LocationObserver, SetListener<OccupantInfo>
{
/**
* Constructs a new occupant director with the supplied context.
*/
public OccupantDirector (CrowdContext ctx)
{
super(ctx);
// register ourselves as a location observer
ctx.getLocationDirector().addLocationObserver(this);
}
/**
* Adds the specified occupant observer to the list.
*/
public void addOccupantObserver (OccupantObserver obs)
{
_observers.add(obs);
}
/**
* Removes the specified occupant observer from the list.
*/
public void removeOccupantObserver (OccupantObserver obs)
{
_observers.remove(obs);
}
/**
* Returns the occupant info for the user in question if it exists in
* the currently occupied place. Returns null if no occupant info
* exists for the specified body.
*/
public OccupantInfo getOccupantInfo (int bodyOid)
{
// make sure we're somewhere
return (_place == null) ? null : _place.occupantInfo.get(Integer.valueOf(bodyOid));
}
/**
* Returns the occupant info for the user in question if it exists in
* the currently occupied place. Returns null if no occupant info
* exists with the specified username.
*/
public OccupantInfo getOccupantInfo (Name username)
{
return (_place == null) ? null : _place.getOccupantInfo(username);
}
@Override
public void clientDidLogoff (Client client)
{
// clear things out
if (_place != null) {
_place.removeListener(this);
_place = null;
}
}
// inherit documentation
public boolean locationMayChange (int placeId)
{
// we've got no opinion
return true;
}
// inherit documentation
public void locationDidChange (PlaceObject place)
{
// unlisten to the old place object if there was one
if (_place != null) {
_place.removeListener(this);
}
// listen to the new one
_place = place;
if (_place != null) {
_place.addListener(this);
}
}
// inherit documentation
public void locationChangeFailed (int placeId, String reason)
{
// nothing to do here either
}
/**
* Deals with all of the processing when an occupant shows up.
*/
public void entryAdded (EntryAddedEvent<OccupantInfo> event)
{
// bail if this isn't for the OCCUPANT_INFO field
if (!event.getName().equals(PlaceObject.OCCUPANT_INFO)) {
return;
}
// now let the occupant observers know what's up
final OccupantInfo info = event.getEntry();
_observers.apply(new ObserverList.ObserverOp<OccupantObserver>() {
public boolean apply (OccupantObserver observer) {
observer.occupantEntered(info);
return true;
}
});
}
/**
* Deals with all of the processing when an occupant is updated.
*/
public void entryUpdated (EntryUpdatedEvent<OccupantInfo> event)
{
// bail if this isn't for the OCCUPANT_INFO field
if (!event.getName().equals(PlaceObject.OCCUPANT_INFO)) {
return;
}
// now let the occupant observers know what's up
final OccupantInfo info = event.getEntry();
final OccupantInfo oinfo = event.getOldEntry();
_observers.apply(new ObserverList.ObserverOp<OccupantObserver>() {
public boolean apply (OccupantObserver observer) {
observer.occupantUpdated(oinfo, info);
return true;
}
});
}
/**
* Deals with all of the processing when an occupant leaves.
*/
public void entryRemoved (EntryRemovedEvent<OccupantInfo> event)
{
// bail if this isn't for the OCCUPANT_INFO field
if (!event.getName().equals(PlaceObject.OCCUPANT_INFO)) {
return;
}
// let the occupant observers know what's up
final OccupantInfo oinfo = event.getOldEntry();
_observers.apply(new ObserverList.ObserverOp<OccupantObserver>() {
public boolean apply (OccupantObserver observer) {
observer.occupantLeft(oinfo);
return true;
}
});
}
/** The occupant observers to keep abreast of occupant antics. */
protected ObserverList<OccupantObserver> _observers = ObserverList.newSafeInOrder();
/** The user's current location. */
protected PlaceObject _place;
}
@@ -0,0 +1,50 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import com.threerings.crowd.data.OccupantInfo;
/**
* An entity that is interested in hearing about bodies that enter and leave a location (as well
* as disconnect and reconnect) can implement this interface and register itself with the
* {@link OccupantDirector}.
*/
public interface OccupantObserver
{
/**
* Called when a body enters the place.
*/
void occupantEntered (OccupantInfo info);
/**
* Called when a body leaves the place.
*/
void occupantLeft (OccupantInfo info);
/**
* Called when an occupant is updated.
*
* @param oldinfo the occupant info prior to the update.
* @param newinfo the newly update info record.
*/
void occupantUpdated (OccupantInfo oldinfo, OccupantInfo newinfo);
}
@@ -0,0 +1,276 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import java.util.ArrayList;
import java.awt.event.ActionEvent;
import com.google.common.collect.Lists;
import com.samskivert.swing.Controller;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.util.CrowdContext;
/**
* Controls the user interface that is used to display a place. When the client moves to a new
* place, the appropriate place controller is constructed and requested to create and display the
* user interface for that place.
*/
public abstract class PlaceController extends Controller
{
/**
* Used to call methods in delegates.
*/
public static abstract class DelegateOp
{
public DelegateOp (Class<? extends PlaceControllerDelegate> delegateClass) {
_delegateClass = delegateClass;
}
/** Applies an operation to the supplied delegate. */
public abstract void apply (PlaceControllerDelegate delegate);
public boolean shouldApply (PlaceControllerDelegate delegate) {
return _delegateClass.isInstance(delegate);
}
protected Class<? extends PlaceControllerDelegate> _delegateClass;
}
/**
* Initializes this place controller with a reference to the context that they can use to
* access client services and to the configuration record for this place. The controller
* should create as much of its user interface that it can without having access to the place
* object because this will be invoked in parallel with the fetching of the place object. When
* the place object is obtained, the controller will be notified and it can then finish the
* user interface configuration and put the user interface into operation.
*
* @param ctx the client context.
* @param config the place configuration for this place.
*/
public void init (CrowdContext ctx, PlaceConfig config)
{
// keep these around
_ctx = ctx;
_config = config;
// create our user interface
_view = createPlaceView(_ctx);
// initialize our delegates
applyToDelegates(new DelegateOp(PlaceControllerDelegate.class) {
@Override
public void apply (PlaceControllerDelegate delegate) {
delegate.init(_ctx, _config);
}
});
// let the derived classes do any initialization stuff
didInit();
}
/**
* Derived classes can override this and perform any post-initialization processing they might
* need. They should of course be sure to call <code>super.didInit()</code>.
*/
protected void didInit ()
{
}
/**
* Returns a reference to the place view associated with this controller. This is only valid
* after a call has been made to {@link #init}.
*/
public PlaceView getPlaceView ()
{
return _view;
}
/**
* Returns the {@link PlaceConfig} associated with this place.
*/
public PlaceConfig getPlaceConfig ()
{
return _config;
}
/**
* Creates the user interface that will be used to display this place. The view instance
* returned will later be configured with the place object, once it becomes available.
*
* @param ctx a reference to the {@link CrowdContext} associated with this controller.
*/
protected PlaceView createPlaceView (CrowdContext ctx)
{
return createPlaceView();
}
/**
* Obsolete but retained for runtime compatibility with the old and busted.
*
* @deprecated Use {@link #createPlaceView(CrowdContext)}.
*/
@Deprecated
protected PlaceView createPlaceView ()
{
return null;
}
/**
* This is called by the location director once the place object has been fetched. The place
* controller will dispatch the place object to the user interface hierarchy via
* {@link PlaceViewUtil#dispatchWillEnterPlace}. Derived classes can override this and perform
* any other starting up that they need to do
*/
public void willEnterPlace (final PlaceObject plobj)
{
// keep a handle on our place object
_plobj = plobj;
if (_view != null) {
// let the UI hierarchy know that we've got our place
PlaceViewUtil.dispatchWillEnterPlace(_view, plobj);
// and display the user interface
_ctx.setPlaceView(_view);
}
// let our delegates know what's up
applyToDelegates(new DelegateOp(PlaceControllerDelegate.class) {
@Override
public void apply (PlaceControllerDelegate delegate) {
delegate.willEnterPlace(plobj);
}
});
}
/**
* Called before a request is submitted to the server to leave the current place. As such,
* this method may be called multiple times before {@link #didLeavePlace} is finally called.
* The request to leave may be rejected, but if a place controller needs to flush any
* information to the place manager before it leaves, it should so do here. This is the only
* place in which the controller is guaranteed to be able to communicate to the place manager,
* as by the time {@link #didLeavePlace} is called, the place manager may have already been
* destroyed.
*/
public void mayLeavePlace (final PlaceObject plobj)
{
// let our delegates know what's up
applyToDelegates(new DelegateOp(PlaceControllerDelegate.class) {
@Override
public void apply (PlaceControllerDelegate delegate) {
delegate.mayLeavePlace(plobj);
}
});
}
/**
* This is called by the location director when we are leaving this place and need to clean up
* after ourselves and shutdown. Derived classes should override this method (being sure to
* call <code>super.didLeavePlace</code>) and perform any necessary cleanup.
*/
public void didLeavePlace (final PlaceObject plobj)
{
// let our delegates know what's up
applyToDelegates(new DelegateOp(PlaceControllerDelegate.class) {
@Override
public void apply (PlaceControllerDelegate delegate) {
delegate.didLeavePlace(plobj);
}
});
// let the UI hierarchy know that we're outta here
if (_view != null) {
PlaceViewUtil.dispatchDidLeavePlace(_view, plobj);
_ctx.clearPlaceView(_view);
_view = null;
}
_plobj = null;
}
/**
* Handles basic place controller action events. Derived classes should be sure to call
* <code>super.handleAction</code> for events they don't specifically handle.
*/
@Override
public boolean handleAction (final ActionEvent action)
{
final boolean[] handled = new boolean[1];
// let our delegates have a crack at the action
applyToDelegates(new DelegateOp(PlaceControllerDelegate.class) {
@Override
public void apply (PlaceControllerDelegate delegate) {
// we take advantage of short-circuiting here
handled[0] = handled[0] || delegate.handleAction(action);
}
});
// if they didn't handle it, pass it off to the super class
return handled[0] || super.handleAction(action);
}
/**
* Adds the supplied delegate to the list for this controller.
*/
protected void addDelegate (PlaceControllerDelegate delegate)
{
if (_delegates == null) {
_delegates = Lists.newArrayList();
}
_delegates.add(delegate);
}
/**
* Applies the supplied operation to the registered delegates.
*/
protected void applyToDelegates (DelegateOp op)
{
if (_delegates != null) {
for (int ii = 0, ll = _delegates.size(); ii < ll; ii++) {
PlaceControllerDelegate delegate = _delegates.get(ii);
if (op.shouldApply(delegate)) {
op.apply(delegate);
}
}
}
}
/** A reference to the active client context. */
protected CrowdContext _ctx;
/** A reference to our place configuration. */
protected PlaceConfig _config;
/** A reference to the place object for which we're controlling a user
* interface. */
protected PlaceObject _plobj;
/** A reference to the root user interface component. */
protected PlaceView _view;
/** A list of the delegates in use by this controller. */
protected ArrayList<PlaceControllerDelegate> _delegates;
}
@@ -0,0 +1,98 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import java.awt.event.ActionEvent;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.util.CrowdContext;
/**
* Provides an extensible mechanism for encapsulating delegated
* functionality that works with the place services.
*
* <p> Thanks to Java's lack of multiple inheritance, it will likely
* become necessary to factor certain services that might be used by a
* variety of {@link PlaceController} derived classes into delegate
* classes because they do not fit into the single inheritance hierarchy
* that makes sense for a particular application. To facilitate this
* process, this delegate class is provided which the standard place
* controller can be made to call out to for all of the standard methods.
*/
public class PlaceControllerDelegate
{
/**
* Constructs the delegate with the controller for which it is
* delegating.
*/
public PlaceControllerDelegate (PlaceController controller)
{
_controller = controller;
}
/**
* Called to initialize the delegate.
*/
public void init (CrowdContext ctx, PlaceConfig config)
{
}
/**
* Called to let the delegate know that we're entering a place.
*/
public void willEnterPlace (PlaceObject plobj)
{
}
/**
* Called before a request is submitted to the server to leave the
* current place. The request to leave may be rejected, but if a place
* controller needs to make a final communication to the place manager
* before it leaves, it should so do here. This is the only place in
* which the controller is guaranteed to be able to communicate to the
* place manager, as by the time {@link #didLeavePlace} is called, the
* place manager may have already been destroyed.
*/
public void mayLeavePlace (final PlaceObject plobj)
{
}
/**
* Called to let the delegate know that we've left the place.
*/
public void didLeavePlace (PlaceObject plobj)
{
}
/**
* Called to give the delegate a chance to handle controller actions
* that weren't handled by the main controller.
*/
public boolean handleAction (ActionEvent action)
{
return false;
}
/** A reference to the controller for which we are delegating. */
protected PlaceController _controller;
}
@@ -0,0 +1,65 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import javax.swing.JPanel;
import com.samskivert.swing.Controller;
import com.samskivert.swing.ControllerProvider;
import com.threerings.crowd.data.PlaceObject;
/**
* A useful base class for client interfaces which wish to make use of a
* {@link JPanel} as their top-level {@link PlaceView}.
*/
public class PlacePanel extends JPanel
implements ControllerProvider, PlaceView
{
/**
* Constructs a place panel with the specified controller which will
* be made availabel via the {@link ControllerProvider} interface.
*/
public PlacePanel (PlaceController controller)
{
_controller = controller;
}
// documentation inherited from interface
public Controller getController ()
{
return _controller;
}
// documentation inherited from interface
public void willEnterPlace (PlaceObject plobj)
{
}
// documentation inherited from interface
public void didLeavePlace (PlaceObject plobj)
{
}
/** A reference to the controller with which we interoperate. */
protected PlaceController _controller;
}
@@ -0,0 +1,76 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import com.threerings.crowd.data.PlaceObject;
/**
* This interface provides a convenient means for decoupling user
* interface components that interact with a place object and that need to
* keep themselves up to date when the client moves from place to place.
*
* <p> In general, such components need to know when the client is about
* to enter a place so that they can subscribe if necessary or at least
* extract information about the place. They also need to know when a
* client has left a place so that they can unsubscribe and clean up after
* themselves. This is the information that the place view interface makes
* available to them in a decoupled way.
*
* <p> The part of the client implementation that is responsible for the
* main user interface can act as a location observer, and it can make use
* of {@link PlaceViewUtil} to dispatch notification of place changes to
* every <code>PlaceView</code> implementing user interface element in the
* user interface hierarchy with calls to {@link
* PlaceViewUtil#dispatchWillEnterPlace} and {@link
* PlaceViewUtil#dispatchDidLeavePlace}. These functions traverse the UI
* hierarchy (starting with the element provided which would generally be
* the top-level UI element, and dispatch calls to {@link #willEnterPlace}
* and {@link #didLeavePlace} respectively on any UI element they find
* that implements <code>PlaceView</code>.
*
* <p> By doing this, the client code can simply create place-sensitive
* user interface elements and stick them in the user interface and
* essentially forget about them, knowing that they will all be notified
* of place entering and exiting by virtue of the single dispatching
* calls. It is useful to note that place-sensitive user interface
* elements will also generally need a reference to the {@link
* com.threerings.crowd.util.CrowdContext} derivative in use by
* the client, but those are best supplied at construct time.
*/
public interface PlaceView
{
/**
* Called when the client has entered a place and is about to display
* the user interface for that place.
*
* @param plobj the place object that was just entered.
*/
void willEnterPlace (PlaceObject plobj);
/**
* Called after the client has left a place and needs to clean up
* after the user interface that was displaying that place.
*
* @param plobj the place object that was just left.
*/
void didLeavePlace (PlaceObject plobj);
}
@@ -0,0 +1,94 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.crowd.client;
import java.awt.Container;
import com.threerings.crowd.data.PlaceObject;
import static com.threerings.crowd.Log.log;
/**
* Provides a mechanism for dispatching notifications to all user interface elements in a
* hierarchy that implement the {@link PlaceView} interface. Look at the documentation for
* {@link PlaceView} for more explanation.
*/
public class PlaceViewUtil
{
/**
* Dispatches a call to {@link PlaceView#willEnterPlace} to all UI elements in the hierarchy
* rooted at the component provided via the <code>root</code> parameter.
*
* @param root the component at which to start traversing the UI hierarchy.
* @param plobj the place object that is about to be entered.
*/
public static void dispatchWillEnterPlace (Object root, PlaceObject plobj)
{
// dispatch the call on this component if it implements PlaceView
if (root instanceof PlaceView) {
try {
((PlaceView)root).willEnterPlace(plobj);
} catch (Exception e) {
log.warning("Component choked on willEnterPlace()", "component", root,
"plobj", plobj, e);
}
}
// now traverse all of this component's children
if (root instanceof Container) {
Container cont = (Container)root;
int ccount = cont.getComponentCount();
for (int ii = 0; ii < ccount; ii++) {
dispatchWillEnterPlace(cont.getComponent(ii), plobj);
}
}
}
/**
* Dispatches a call to {@link PlaceView#didLeavePlace} to all UI elements in the hierarchy
* rooted at the component provided via the <code>root</code> parameter.
*
* @param root the component at which to start traversing the UI hierarchy.
* @param plobj the place object that is about to be entered.
*/
public static void dispatchDidLeavePlace (Object root, PlaceObject plobj)
{
// dispatch the call on this component if it implements PlaceView
if (root instanceof PlaceView) {
try {
((PlaceView)root).didLeavePlace(plobj);
} catch (Exception e) {
log.warning("Component choked on didLeavePlace()", "component", root,
"plobj", plobj, e);
}
}
// now traverse all of this component's children
if (root instanceof Container) {
Container cont = (Container)root;
int ccount = cont.getComponentCount();
for (int ii = 0; ii < ccount; ii++) {
dispatchDidLeavePlace(cont.getComponent(ii), plobj);
}
}
}
}
@@ -0,0 +1,52 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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 javax.annotation.Generated;
import com.threerings.crowd.client.BodyService;
import com.threerings.presents.client.Client;
import com.threerings.presents.data.InvocationMarshaller;
/**
* Provides the implementation of the {@link BodyService} interface
* that marshalls the arguments and delivers the request to the provider
* on the server. Also provides an implementation of the response listener
* interfaces that marshall the response arguments and deliver them back
* to the requesting client.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from BodyService.java.")
public class BodyMarshaller extends InvocationMarshaller
implements BodyService
{
/** The method id used to dispatch {@link #setIdle} requests. */
public static final int SET_IDLE = 1;
// from interface BodyService
public void setIdle (Client arg1, boolean arg2)
{
sendRequest(arg1, SET_IDLE, new Object[] {
Boolean.valueOf(arg2)
});
}
}
@@ -0,0 +1,215 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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 javax.annotation.Generated;
import com.threerings.util.Name;
import com.threerings.presents.data.ClientObject;
import com.threerings.crowd.chat.data.SpeakObject;
/**
* The basic user object class for Crowd users. Bodies have a username, a location and a status.
*/
public class BodyObject extends ClientObject
implements SpeakObject
{
// AUTO-GENERATED: FIELDS START
/** The field name of the <code>location</code> field. */
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public static final String LOCATION = "location";
/** The field name of the <code>status</code> field. */
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public static final String STATUS = "status";
/** The field name of the <code>awayMessage</code> field. */
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public static final String AWAY_MESSAGE = "awayMessage";
// AUTO-GENERATED: FIELDS END
/**
* Identifies the place currently occupied by this body. null if they currently occupy no
* place.
*/
public Place location;
/**
* The user's current status ({@link OccupantInfo#ACTIVE}, etc.).
*/
public byte status;
/**
* If non-null, this contains a message to be auto-replied whenever another user delivers a
* tell message to this user.
*/
public String awayMessage;
/**
* Returns the oid of the place occupied by this body or -1 if we occupy no place.
*/
public int getPlaceOid ()
{
return (location == null) ? -1 : location.placeOid;
}
/**
* Returns this user's access control tokens.
*/
public TokenRing getTokens ()
{
return EMPTY_TOKENS;
}
/**
* Returns the name that should be displayed to other users. The default is to use {@link
* #username}.
*/
public Name getVisibleName ()
{
return username;
}
/**
* Creates a blank occupant info instance that will used to publish information about the
* various bodies occupying a place.
*/
public OccupantInfo createOccupantInfo (PlaceObject plobj)
{
return new OccupantInfo(this);
}
/**
* Called when this body is about to enter the specified place. Configures our {@link
* #location} field.
*
* @param place the identifying information for the place we are entering.
* @param plobj the distributed object for the place we are entering.
*/
public void willEnterPlace (Place place, PlaceObject plobj)
{
setLocation(place);
}
/**
* Called when this body has left its occupied place. Clears our {@link #location} field.
*
* @param plobj the distributed object for the place we just departed. This might be null if
* the place object has been destroyed.
*/
public void didLeavePlace (PlaceObject plobj)
{
setLocation(null);
}
// from interface SpeakObject
public void applyToListeners (ListenerOp op)
{
op.apply(getVisibleName());
}
@Override
public String who ()
{
StringBuilder buf = new StringBuilder(username.toString());
buf.append(" (");
addWhoData(buf);
return buf.append(")").toString();
}
// AUTO-GENERATED: METHODS START
/**
* Requests that the <code>location</code> field be set to the
* specified value. The local value will be updated immediately and an
* event will be propagated through the system to notify all listeners
* that the attribute did change. Proxied copies of this object (on
* clients) will apply the value change when they received the
* attribute changed notification.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void setLocation (Place value)
{
Place ovalue = this.location;
requestAttributeChange(
LOCATION, value, ovalue);
this.location = value;
}
/**
* Requests that the <code>status</code> field be set to the
* specified value. The local value will be updated immediately and an
* event will be propagated through the system to notify all listeners
* that the attribute did change. Proxied copies of this object (on
* clients) will apply the value change when they received the
* attribute changed notification.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void setStatus (byte value)
{
byte ovalue = this.status;
requestAttributeChange(
STATUS, Byte.valueOf(value), Byte.valueOf(ovalue));
this.status = value;
}
/**
* Requests that the <code>awayMessage</code> field be set to the
* specified value. The local value will be updated immediately and an
* event will be propagated through the system to notify all listeners
* that the attribute did change. Proxied copies of this object (on
* clients) will apply the value change when they received the
* attribute changed notification.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void setAwayMessage (String value)
{
String ovalue = this.awayMessage;
requestAttributeChange(
AWAY_MESSAGE, value, ovalue);
this.awayMessage = value;
}
// AUTO-GENERATED: METHODS END
/**
* Allows derived classes to add data to the who details.
*/
protected void addWhoData (StringBuilder buf)
{
buf.append(getOid());
if (status != OccupantInfo.ACTIVE) {
buf.append(" ").append(getStatusTranslation());
}
}
/**
* Get a translation suffix for this occupant's status.
* Can be overridden to translate nonstandard statuses.
*/
protected String getStatusTranslation ()
{
return OccupantInfo.X_STATUS[status];
}
/** The default (no tokens) access control. */
protected static final TokenRing EMPTY_TOKENS = new TokenRing();
}
@@ -0,0 +1,33 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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";
}
@@ -0,0 +1,51 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.ClientObject;
import com.threerings.presents.data.Permission;
import com.threerings.presents.data.PermissionPolicy;
import com.threerings.crowd.chat.data.ChatCodes;
/**
* Implements some Crowd permissions.
*/
public class CrowdPermissionPolicy extends PermissionPolicy
{
@Override // from PermissionPolicy
public String checkAccess (ClientObject clobj, Permission perm, Object context)
{
if (!(clobj instanceof BodyObject)) {
return super.checkAccess(clobj, perm, context);
}
BodyObject body = (BodyObject)clobj;
if (perm == ChatCodes.BROADCAST_ACCESS) {
return body.getTokens().isAdmin() ? null : ACCESS_DENIED;
} else if (perm == ChatCodes.CHAT_ACCESS) {
return null;
} else {
return super.checkAccess(clobj, perm, context);
}
}
}
@@ -0,0 +1,43 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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;
/**
* Contains codes used by the location invocation services.
*/
public interface LocationCodes extends InvocationCodes
{
/** An error code indicating that a place identified by a particular
* place id does not exist. Usually generated by a failed moveTo
* request. */
public static final String NO_SUCH_PLACE = "m.no_such_place";
/** An error code sent when a user requests to move to a new place but
* they are in the middle of moving somewhere already. */
public static final String MOVE_IN_PROGRESS = "m.move_in_progress";
/** An error code sent when a user requests to move to a place, but
* they are already in the requested place. */
public static final String ALREADY_THERE = "m.already_there";
}
@@ -0,0 +1,100 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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 javax.annotation.Generated;
import com.threerings.crowd.client.LocationService;
import com.threerings.presents.client.Client;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.InvocationResponseEvent;
/**
* Provides the implementation of the {@link LocationService} interface
* that marshalls the arguments and delivers the request to the provider
* on the server. Also provides an implementation of the response listener
* interfaces that marshall the response arguments and deliver them back
* to the requesting client.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from LocationService.java.")
public class LocationMarshaller extends InvocationMarshaller
implements LocationService
{
/**
* Marshalls results to implementations of {@link LocationService.MoveListener}.
*/
public static class MoveMarshaller extends ListenerMarshaller
implements MoveListener
{
/** The method id used to dispatch {@link #moveSucceeded}
* responses. */
public static final int MOVE_SUCCEEDED = 1;
// from interface MoveMarshaller
public void moveSucceeded (PlaceConfig arg1)
{
_invId = null;
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, MOVE_SUCCEEDED,
new Object[] { arg1 }, transport));
}
@Override // from InvocationMarshaller
public void dispatchResponse (int methodId, Object[] args)
{
switch (methodId) {
case MOVE_SUCCEEDED:
((MoveListener)listener).moveSucceeded(
(PlaceConfig)args[0]);
return;
default:
super.dispatchResponse(methodId, args);
return;
}
}
}
/** The method id used to dispatch {@link #leavePlace} requests. */
public static final int LEAVE_PLACE = 1;
// from interface LocationService
public void leavePlace (Client arg1)
{
sendRequest(arg1, LEAVE_PLACE, new Object[] {
});
}
/** The method id used to dispatch {@link #moveTo} requests. */
public static final int MOVE_TO = 2;
// from interface LocationService
public void moveTo (Client arg1, int arg2, LocationService.MoveListener arg3)
{
LocationMarshaller.MoveMarshaller listener3 = new LocationMarshaller.MoveMarshaller();
listener3.listener = arg3;
sendRequest(arg1, MOVE_TO, new Object[] {
Integer.valueOf(arg2), listener3
});
}
}
@@ -0,0 +1,136 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.io.SimpleStreamableObject;
import com.threerings.util.Name;
import com.threerings.presents.dobj.DSet;
/**
* The occupant info object contains all of the information about an occupant of a place that
* should be shared with other occupants of the place. These objects are stored in the place object
* itself and are updated when bodies enter and exit a place.
*
* <p> A system that builds upon the Crowd framework can extend this class to include extra
* information about their occupants. They will need to provide a derived {@link BodyObject} that
* creates and configures their occupant info in {@link BodyObject#createOccupantInfo}.
*
* <p> Note also that this class implements {@link Cloneable} which means that if derived classes
* add non-primitive attributes, they are responsible for adding the code to clone those attributes
* when a clone is requested.
*/
public class OccupantInfo extends SimpleStreamableObject
implements DSet.Entry, Cloneable
{
/** Constant value for {@link #status}. */
public static final byte ACTIVE = 0;
/** Constant value for {@link #status}. */
public static final byte IDLE = 1;
/** Constant value for {@link #status}. */
public static final byte DISCONNECTED = 2;
/** Maps status codes to human readable strings. */
public static final String[] X_STATUS = { "active", "idle", "discon" };
/** Used by PlaceManager.updateOccupantInfo. */
public static interface Updater<T extends OccupantInfo>
{
/**
* Make whatever changes are desired to your {@link OccupantInfo} here.
*
* @return true if the record was modified and should be published, false if no
* modifications were made (it will not be published).
*/
public boolean update (T info);
}
/** An update to dispatch when an occupant's name changes. */
public static class NameUpdater implements Updater<OccupantInfo>
{
public NameUpdater (Name name) {
_name = name;
}
public boolean update (OccupantInfo info) {
// The behaviour here used to be to compare the names themselves against one another
// using equals(), but was changed to accommodate the idea of display name changing
// while fundamental identity stays the same -- case in point, Whirled's MemberName
// bases equal()ity on an integer identifier. TODO: investigate whether this is a
// reasonable assumption and whether the behaviour change might break something.
if (info.username.getNormal().equals(_name.getNormal())) {
return false;
}
info.username = _name;
return true;
}
protected Name _name;
}
/** The body object id of this occupant (and our entry key). */
public Integer bodyOid;
/** The username of this occupant. */
public Name username;
/** The status of this occupant. */
public byte status = ACTIVE;
/**
* Creates an occupant info with information from the specified occupant's body object.
*/
public OccupantInfo (BodyObject body)
{
bodyOid = Integer.valueOf(body.getOid());
username = body.getVisibleName();
status = body.status;
}
/** A blank constructor used for unserialization. */
public OccupantInfo ()
{
}
/** Access to the body object id as an int. */
public int getBodyOid ()
{
return bodyOid.intValue();
}
// documentation inherited
public Comparable<?> getKey ()
{
return bodyOid;
}
@Override
public OccupantInfo clone ()
{
try {
return (OccupantInfo) super.clone();
} catch (CloneNotSupportedException cnse) {
throw new AssertionError(cnse);
}
}
}
@@ -0,0 +1,61 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.io.SimpleStreamableObject;
/**
* Contains information on the current place occupied by a body.
*/
public class Place extends SimpleStreamableObject
{
/** The oid of this place's {@link PlaceObject}. */
public int placeOid;
/** Used when unserializing. */
public Place ()
{
}
/**
* Creates a place with the supplied oid.
*/
public Place (int placeOid)
{
this.placeOid = placeOid;
}
@Override // from Object
public boolean equals (Object other)
{
if (other == null) {
return false;
}
return getClass().equals(other.getClass()) ? placeOid == ((Place)other).placeOid : false;
}
@Override // from Object
public int hashCode ()
{
return placeOid;
}
}
@@ -0,0 +1,101 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.samskivert.util.StringUtil;
import com.threerings.io.SimpleStreamableObject;
import com.threerings.util.ActionScript;
import com.threerings.crowd.client.PlaceController;
import static com.threerings.crowd.Log.log;
/**
* The place config class encapsulates the configuration information for a
* particular type of place. The hierarchy of place config objects mimics
* the hierarchy of place managers and controllers. Both the place manager
* and place controller are provided with the place config object when the
* place is created.
*
* <p> The place config object is also the mechanism used to instantiate the
* appropriate place manager and controller. Every place must have an
* associated place config derived class that overrides {@link
* #createController} and {@link #getManagerClassName}, returning the
* appropriate place controller and manager class for that place.
*/
public abstract class PlaceConfig extends SimpleStreamableObject
{
/**
* Returns the class that should be used to create a controller for this
* place. The controller class must derive from {@link PlaceController}.
*
* @deprecated Override {@link #createController} directly.
*/
@Deprecated
public Class<?> getControllerClass ()
{
return null;
}
/**
* Create the controller that should be used for this place.
*/
public PlaceController createController ()
{
Class<?> cclass = getControllerClass();
if (cclass == null) {
throw new RuntimeException(
"PlaceConfig.createController() must be overridden.");
}
log.warning("Providing backwards compatibility. PlaceConfig." +
"createController() should be overridden directly.");
try {
return (PlaceController)cclass.newInstance();
} catch (Exception e) {
log.warning("Failed to instantiate controller class '" + cclass + "'.", e);
return null;
}
}
/**
* Returns the name of the class that should be used to create a manager
* for this place. The manager class must derive from {@link
* com.threerings.crowd.server.PlaceManager}. <em>Note:</em> this method
* differs from {@link #createController} because we want to avoid compile
* time linkage of the place config object (which is used on the client) to
* server code. This allows a code optimizer (DashO Pro, for example) to
* remove the server code from the client, knowing that it is never used.
*/
public abstract String getManagerClassName ();
@Override
@ActionScript(name="toStringBuilder")
protected void toString (StringBuilder buf)
{
buf.append("type=").append(StringUtil.shortClassName(this));
buf.append(", ");
super.toString(buf);
}
}
@@ -0,0 +1,239 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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 javax.annotation.Generated;
import com.threerings.util.Name;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.dobj.DSet;
import com.threerings.presents.dobj.OidList;
import com.threerings.presents.dobj.ServerMessageEvent;
import com.threerings.crowd.chat.data.SpeakMarshaller;
import com.threerings.crowd.chat.data.SpeakObject;
import static com.threerings.crowd.Log.log;
/**
* A distributed object that contains information on a place that is occupied by bodies. This place
* might be a chat room, a game room, an island in a massively multiplayer piratical universe,
* anything that has occupants that might want to chat with one another.
*/
public class PlaceObject extends DObject
implements SpeakObject
{
// AUTO-GENERATED: FIELDS START
/** The field name of the <code>occupants</code> field. */
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public static final String OCCUPANTS = "occupants";
/** The field name of the <code>occupantInfo</code> field. */
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public static final String OCCUPANT_INFO = "occupantInfo";
/** The field name of the <code>speakService</code> field. */
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public static final String SPEAK_SERVICE = "speakService";
// AUTO-GENERATED: FIELDS END
/**
* Exists to make calls into the manager look sensible:
*
* <pre>_plobj.manager.invoke("someMethod", args);</pre>
*
* and to route events through the right distributed object manager if we are running in
* standalone/single-player mode where both client and server are running in the same VM.
*/
public class ManagerCaller
{
public void invoke (String method, Object ... args) {
_omgr.postEvent(new ServerMessageEvent(_oid, method, args));
}
protected ManagerCaller (DObjectManager omgr) {
_omgr = omgr;
}
protected DObjectManager _omgr;
}
/**
* Allows the client to call methods on the manager.
*/
public transient ManagerCaller manager;
/**
* Tracks the oid of the body objects of all of the occupants of this place.
*/
public OidList occupants = new OidList();
/**
* Contains an info record (of type {@link OccupantInfo}) for each occupant that contains
* information about that occupant that needs to be known by everyone in the place.
* <em>Note:</em> Don't obtain occupant info records directly from this set when on the server,
* use <code>PlaceManager.getOccupantInfo()</code> instead (along with
* <code>PlaceManager.updateOccupantInfo()</code>) because it does some special processing to
* ensure that readers and updaters don't step on one another even if they make rapid fire
* changes to a user's occupant info.
*/
public DSet<OccupantInfo> occupantInfo = new DSet<OccupantInfo>();
/** Used to generate speak requests on this place object. */
public SpeakMarshaller speakService;
/**
* Called on the client when the location director receives this place object to configure our
* manager caller using the client's distributed object manager.
*/
public void initManagerCaller (DObjectManager omgr)
{
manager = new ManagerCaller(omgr);
}
/**
* Used to indicate whether broadcast chat messages should be dispatched on this place object.
*/
public boolean shouldBroadcast ()
{
return true;
}
/**
* Looks up a user's occupant info by name.
*
* @return the occupant info record for the named user or null if no user in the room has that
* username.
*/
public OccupantInfo getOccupantInfo (Name username)
{
try {
for (OccupantInfo info : occupantInfo) {
if (info.username.equals(username)) {
return info;
}
}
} catch (Throwable t) {
log.warning("PlaceObject.getOccupantInfo choked.", t);
}
return null;
}
// documentation inherited
public void applyToListeners (ListenerOp op)
{
for (int ii = 0, ll = occupants.size(); ii < ll; ii++) {
op.apply(occupants.get(ii));
}
}
// AUTO-GENERATED: METHODS START
/**
* Requests that <code>oid</code> be added to the <code>occupants</code>
* oid list. The list will not change until the event is actually
* propagated through the system.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void addToOccupants (int oid)
{
requestOidAdd(OCCUPANTS, oid);
}
/**
* Requests that <code>oid</code> be removed from the
* <code>occupants</code> oid list. The list will not change until the
* event is actually propagated through the system.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void removeFromOccupants (int oid)
{
requestOidRemove(OCCUPANTS, oid);
}
/**
* Requests that the specified entry be added to the
* <code>occupantInfo</code> set. The set will not change until the event is
* actually propagated through the system.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void addToOccupantInfo (OccupantInfo elem)
{
requestEntryAdd(OCCUPANT_INFO, occupantInfo, elem);
}
/**
* Requests that the entry matching the supplied key be removed from
* the <code>occupantInfo</code> set. The set will not change until the
* event is actually propagated through the system.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void removeFromOccupantInfo (Comparable<?> key)
{
requestEntryRemove(OCCUPANT_INFO, occupantInfo, key);
}
/**
* Requests that the specified entry be updated in the
* <code>occupantInfo</code> set. The set will not change until the event is
* actually propagated through the system.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void updateOccupantInfo (OccupantInfo elem)
{
requestEntryUpdate(OCCUPANT_INFO, occupantInfo, elem);
}
/**
* Requests that the <code>occupantInfo</code> field be set to the
* specified value. Generally one only adds, updates and removes
* entries of a distributed set, but certain situations call for a
* complete replacement of the set value. The local value will be
* updated immediately and an event will be propagated through the
* system to notify all listeners that the attribute did
* change. Proxied copies of this object (on clients) will apply the
* value change when they received the attribute changed notification.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void setOccupantInfo (DSet<OccupantInfo> value)
{
requestAttributeChange(OCCUPANT_INFO, value, this.occupantInfo);
DSet<OccupantInfo> clone = (value == null) ? null : value.clone();
this.occupantInfo = clone;
}
/**
* Requests that the <code>speakService</code> field be set to the
* specified value. The local value will be updated immediately and an
* event will be propagated through the system to notify all listeners
* that the attribute did change. Proxied copies of this object (on
* clients) will apply the value change when they received the
* attribute changed notification.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void setSpeakService (SpeakMarshaller value)
{
SpeakMarshaller ovalue = this.speakService;
requestAttributeChange(
SPEAK_SERVICE, value, ovalue);
this.speakService = value;
}
// AUTO-GENERATED: METHODS END
}
@@ -0,0 +1,115 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.io.SimpleStreamableObject;
/**
* Defines access control tokens that convey certain privileges to users.
*
* @see CrowdPermissionPolicy
*/
public class TokenRing extends SimpleStreamableObject
{
/** Indicates that this user is an administrator. */
public static final int ADMIN = (1 << 0);
/**
* A default constructor, used when unserializing token rings.
*/
public TokenRing ()
{
}
/**
* Constructs a token ring with the supplied set of tokens.
*/
public TokenRing (int tokens)
{
_tokens = tokens;
}
/**
* Returns true if this token ring contains the specified token or tokens, exactly. For
* example, if you pass in the OR of two or more tokens, then the ring must contain all of
* those tokens.
*/
public boolean holdsToken (int token)
{
return (_tokens & token) == token;
}
/**
* Returns true if this token ring contains any one of the specified tokens.
*/
public boolean holdsAnyToken (int tokens)
{
return (_tokens & tokens) != 0;
}
/**
* Convenience function for checking whether this ring holds the {@link #ADMIN} token.
*/
public boolean isAdmin ()
{
return holdsToken(ADMIN);
}
/**
* Returns the bitmask that stores the various tokens.
*/
public int getTokens ()
{
return _tokens;
}
/**
* Set the specified token to be on or off.
*/
public void setToken (int token, boolean on)
{
if (on) {
setToken(token);
} else {
clearToken(token);
}
}
/**
* Adds the specified token to this ring.
*/
public void setToken (int token)
{
_tokens |= token;
}
/**
* Clears the specified token from this ring.
*/
public void clearToken (int token)
{
_tokens &= ~token;
}
/** The tokens contained in this ring (composed together bitwise). */
protected int _tokens;
}
@@ -0,0 +1,50 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<!-- $Id: package.html 617 2001-11-13 00:12:20Z mdb $ -->
</head>
<body bgcolor="white">
Builds on the Presents framework to provide services uesful for chatting
and moving from place to place in a distributed space.
<p> The main Crowd concepts are that of Body and Place and the notion of a
PlaceManager on the server and a pairing of PlaceController and PlaceView
on the client. </p>
<ul>
<li><a href="client/package-summary.html">client</a> - the client side of
the Crowd services.
<li><a href="data/package-summary.html">data</a> - classes that are shared
between client and server.
<li><a href="server/package-summary.html">server</a> - the server side of
the Crowd services.
</ul>
<p> Crowd also provides a mechanism for Chat in Places and between
individuals that are logged on. </p>
<ul>
<li><a href="chat/client/package-summary.html">client</a> - the client side of
the Crowd Chat services.
<li><a href="chat/data/package-summary.html">data</a> - classes that are
shared between client and server.
<li><a href="chat/server/package-summary.html">server</a> - the server side of
the Crowd Chat services.
</ul>
<p> Crowd also integrates with the Presents peer system to make its Chat
services work in a peer environment. </p>
<ul>
<li><a href="chat/client/package-summary.html">client</a> - the client side of
the Crowd Peer Chat services.
<li><a href="chat/data/package-summary.html">data</a> - classes that are
shared between servers.
<li><a href="chat/server/package-summary.html">server</a> - the server side of
the Crowd Peer Chat services.
</ul>
</body>
</html>
@@ -0,0 +1,48 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.peer.client;
import com.threerings.util.Name;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
import com.threerings.crowd.chat.client.ChatService;
import com.threerings.crowd.chat.data.UserMessage;
/**
* Bridges certain Crowd services between peers in a cluster configuration.
*/
public interface CrowdPeerService extends InvocationService
{
/**
* Used to forward a tell request to the server on which the destination user actually
* occupies.
*/
void deliverTell (Client client, UserMessage message, Name target,
ChatService.TellListener listener);
/**
* Dispatches a broadcast message on this peer.
*/
void deliverBroadcast (Client client, Name from, byte levelOrMode, String bundle, String msg);
}
@@ -0,0 +1,35 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.peer.data;
import com.threerings.util.Name;
import com.threerings.presents.peer.data.ClientInfo;
/**
* Extends the standard {@link ClientInfo} with Crowd bits.
*/
public class CrowdClientInfo extends ClientInfo
{
/** The client's visible name, which is used for chatting. */
public Name visibleName;
}
@@ -0,0 +1,120 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.peer.data;
import javax.annotation.Generated;
import com.threerings.presents.dobj.DSet;
import com.threerings.presents.peer.data.NodeObject;
import com.threerings.crowd.chat.data.ChatChannel;
/**
* Extends the basic {@link NodeObject} with Crowd bits.
*/
public class CrowdNodeObject extends NodeObject
{
// AUTO-GENERATED: FIELDS START
/** The field name of the <code>crowdPeerService</code> field. */
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public static final String CROWD_PEER_SERVICE = "crowdPeerService";
/** The field name of the <code>hostedChannels</code> field. */
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public static final String HOSTED_CHANNELS = "hostedChannels";
// AUTO-GENERATED: FIELDS END
/** Used to coordinate tells between servers. */
public CrowdPeerMarshaller crowdPeerService;
/** The chat channels hosted on this server. */
public DSet<ChatChannel> hostedChannels = new DSet<ChatChannel>();
// AUTO-GENERATED: METHODS START
/**
* Requests that the <code>crowdPeerService</code> field be set to the
* specified value. The local value will be updated immediately and an
* event will be propagated through the system to notify all listeners
* that the attribute did change. Proxied copies of this object (on
* clients) will apply the value change when they received the
* attribute changed notification.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void setCrowdPeerService (CrowdPeerMarshaller value)
{
CrowdPeerMarshaller ovalue = this.crowdPeerService;
requestAttributeChange(
CROWD_PEER_SERVICE, value, ovalue);
this.crowdPeerService = value;
}
/**
* Requests that the specified entry be added to the
* <code>hostedChannels</code> set. The set will not change until the event is
* actually propagated through the system.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void addToHostedChannels (ChatChannel elem)
{
requestEntryAdd(HOSTED_CHANNELS, hostedChannels, elem);
}
/**
* Requests that the entry matching the supplied key be removed from
* the <code>hostedChannels</code> set. The set will not change until the
* event is actually propagated through the system.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void removeFromHostedChannels (Comparable<?> key)
{
requestEntryRemove(HOSTED_CHANNELS, hostedChannels, key);
}
/**
* Requests that the specified entry be updated in the
* <code>hostedChannels</code> set. The set will not change until the event is
* actually propagated through the system.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void updateHostedChannels (ChatChannel elem)
{
requestEntryUpdate(HOSTED_CHANNELS, hostedChannels, elem);
}
/**
* Requests that the <code>hostedChannels</code> field be set to the
* specified value. Generally one only adds, updates and removes
* entries of a distributed set, but certain situations call for a
* complete replacement of the set value. The local value will be
* updated immediately and an event will be propagated through the
* system to notify all listeners that the attribute did
* change. Proxied copies of this object (on clients) will apply the
* value change when they received the attribute changed notification.
*/
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
public void setHostedChannels (DSet<ChatChannel> value)
{
requestAttributeChange(HOSTED_CHANNELS, value, this.hostedChannels);
DSet<ChatChannel> clone = (value == null) ? null : value.clone();
this.hostedChannels = clone;
}
// AUTO-GENERATED: METHODS END
}
@@ -0,0 +1,69 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.peer.data;
import javax.annotation.Generated;
import com.threerings.crowd.chat.client.ChatService;
import com.threerings.crowd.chat.data.ChatMarshaller;
import com.threerings.crowd.chat.data.UserMessage;
import com.threerings.crowd.peer.client.CrowdPeerService;
import com.threerings.presents.client.Client;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.util.Name;
/**
* Provides the implementation of the {@link CrowdPeerService} interface
* that marshalls the arguments and delivers the request to the provider
* on the server. Also provides an implementation of the response listener
* interfaces that marshall the response arguments and deliver them back
* to the requesting client.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from CrowdPeerService.java.")
public class CrowdPeerMarshaller extends InvocationMarshaller
implements CrowdPeerService
{
/** The method id used to dispatch {@link #deliverBroadcast} requests. */
public static final int DELIVER_BROADCAST = 1;
// from interface CrowdPeerService
public void deliverBroadcast (Client arg1, Name arg2, byte arg3, String arg4, String arg5)
{
sendRequest(arg1, DELIVER_BROADCAST, new Object[] {
arg2, Byte.valueOf(arg3), arg4, arg5
});
}
/** The method id used to dispatch {@link #deliverTell} requests. */
public static final int DELIVER_TELL = 2;
// from interface CrowdPeerService
public void deliverTell (Client arg1, UserMessage arg2, Name arg3, ChatService.TellListener arg4)
{
ChatMarshaller.TellMarshaller listener4 = new ChatMarshaller.TellMarshaller();
listener4.listener = arg4;
sendRequest(arg1, DELIVER_TELL, new Object[] {
arg2, arg3, listener4
});
}
}
@@ -0,0 +1,79 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.peer.server;
import javax.annotation.Generated;
import com.threerings.crowd.chat.client.ChatService;
import com.threerings.crowd.chat.data.UserMessage;
import com.threerings.crowd.peer.data.CrowdPeerMarshaller;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationDispatcher;
import com.threerings.presents.server.InvocationException;
import com.threerings.util.Name;
/**
* Dispatches requests to the {@link CrowdPeerProvider}.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from CrowdPeerService.java.")
public class CrowdPeerDispatcher extends InvocationDispatcher<CrowdPeerMarshaller>
{
/**
* Creates a dispatcher that may be registered to dispatch invocation
* service requests for the specified provider.
*/
public CrowdPeerDispatcher (CrowdPeerProvider provider)
{
this.provider = provider;
}
@Override
public CrowdPeerMarshaller createMarshaller ()
{
return new CrowdPeerMarshaller();
}
@Override
public void dispatchRequest (
ClientObject source, int methodId, Object[] args)
throws InvocationException
{
switch (methodId) {
case CrowdPeerMarshaller.DELIVER_BROADCAST:
((CrowdPeerProvider)provider).deliverBroadcast(
source, (Name)args[0], ((Byte)args[1]).byteValue(), (String)args[2], (String)args[3]
);
return;
case CrowdPeerMarshaller.DELIVER_TELL:
((CrowdPeerProvider)provider).deliverTell(
source, (UserMessage)args[0], (Name)args[1], (ChatService.TellListener)args[2]
);
return;
default:
super.dispatchRequest(source, methodId, args);
return;
}
}
}
@@ -0,0 +1,167 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.peer.server;
import com.google.inject.Inject;
import com.samskivert.util.Lifecycle;
import com.threerings.util.Name;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.peer.data.ClientInfo;
import com.threerings.presents.peer.data.NodeObject;
import com.threerings.presents.peer.server.PeerManager;
import com.threerings.presents.peer.server.PeerNode;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.PresentsSession;
import com.threerings.crowd.chat.client.ChatService;
import com.threerings.crowd.chat.data.UserMessage;
import com.threerings.crowd.chat.server.ChatProvider;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.peer.data.CrowdClientInfo;
import com.threerings.crowd.peer.data.CrowdNodeObject;
/**
* Extends the standard peer manager and bridges certain Crowd services.
*/
public abstract class CrowdPeerManager extends PeerManager
implements CrowdPeerProvider, ChatProvider.ChatForwarder
{
/**
* Creates an uninitialized peer manager.
*/
@Inject public CrowdPeerManager (Lifecycle cycle)
{
super(cycle);
}
// from interface CrowdPeerProvider
public void deliverTell (ClientObject caller, UserMessage message,
Name target, ChatService.TellListener listener)
throws InvocationException
{
// we just forward the message as if it originated on this server
_chatprov.deliverTell(message, target, listener);
}
// from interface CrowdPeerProvider
public void deliverBroadcast (
ClientObject caller, Name from, byte levelOrMode, String bundle, String msg)
{
// deliver the broadcast locally on this server
_chatprov.broadcast(from, levelOrMode, bundle, msg, false);
}
// from interface ChatProvider.ChatForwarder
public boolean forwardTell (UserMessage message, Name target,
ChatService.TellListener listener)
{
// look up their auth username from their visible name
Name username = authFromViz(target);
if (username == null) {
return false; // sorry kid, don't know ya
}
// look through our peers to see if the target user is online on one of them
for (PeerNode peer : _peers.values()) {
CrowdNodeObject cnobj = (CrowdNodeObject)peer.nodeobj;
if (cnobj == null) {
continue;
}
// we have to use auth username to look up their ClientInfo
CrowdClientInfo cinfo = (CrowdClientInfo)cnobj.clients.get(username);
if (cinfo != null) {
cnobj.crowdPeerService.deliverTell(peer.getClient(), message, target, listener);
return true;
}
}
return false;
}
// from interface ChatProvider.ChatForwarder
public void forwardBroadcast (Name from, byte levelOrMode, String bundle, String msg)
{
for (PeerNode peer : _peers.values()) {
if (peer.nodeobj != null) {
((CrowdNodeObject)peer.nodeobj).crowdPeerService.deliverBroadcast(
peer.getClient(), from, levelOrMode, bundle, msg);
}
}
}
@Override // from PeerManager
public void shutdown ()
{
super.shutdown();
// unregister our invocation service
if (_nodeobj != null) {
_invmgr.clearDispatcher(((CrowdNodeObject)_nodeobj).crowdPeerService);
}
// clear our chat forwarder registration
_chatprov.setChatForwarder(null);
}
@Override // from PeerManager
protected NodeObject createNodeObject ()
{
return new CrowdNodeObject();
}
@Override // from PeerManager
protected ClientInfo createClientInfo ()
{
return new CrowdClientInfo();
}
@Override // from PeerManager
protected void initClientInfo (PresentsSession client, ClientInfo info)
{
super.initClientInfo(client, info);
((CrowdClientInfo)info).visibleName =
((BodyObject)client.getClientObject()).getVisibleName();
}
@Override // from PeerManager
protected void didInit ()
{
super.didInit();
// register and initialize our invocation service
CrowdNodeObject cnobj = (CrowdNodeObject)_nodeobj;
cnobj.setCrowdPeerService(_invmgr.registerDispatcher(new CrowdPeerDispatcher(this)));
// register ourselves as a chat forwarder
_chatprov.setChatForwarder(this);
}
/**
* Converts a visible name to an authentication name. If this method returns null, the chat
* system will act as if the vizname in question is not online.
*/
protected abstract Name authFromViz (Name vizname);
@Inject protected ChatProvider _chatprov;
}
@@ -0,0 +1,51 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.peer.server;
import javax.annotation.Generated;
import com.threerings.crowd.chat.client.ChatService;
import com.threerings.crowd.chat.data.UserMessage;
import com.threerings.crowd.peer.client.CrowdPeerService;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationProvider;
import com.threerings.util.Name;
/**
* Defines the server-side of the {@link CrowdPeerService}.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from CrowdPeerService.java.")
public interface CrowdPeerProvider extends InvocationProvider
{
/**
* Handles a {@link CrowdPeerService#deliverBroadcast} request.
*/
void deliverBroadcast (ClientObject caller, Name arg1, byte arg2, String arg3, String arg4);
/**
* Handles a {@link CrowdPeerService#deliverTell} request.
*/
void deliverTell (ClientObject caller, UserMessage arg1, Name arg2, ChatService.TellListener arg3)
throws InvocationException;
}
@@ -0,0 +1,70 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import javax.annotation.Generated;
import com.threerings.crowd.data.BodyMarshaller;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationDispatcher;
import com.threerings.presents.server.InvocationException;
/**
* Dispatches requests to the {@link BodyProvider}.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from BodyService.java.")
public class BodyDispatcher extends InvocationDispatcher<BodyMarshaller>
{
/**
* Creates a dispatcher that may be registered to dispatch invocation
* service requests for the specified provider.
*/
public BodyDispatcher (BodyProvider provider)
{
this.provider = provider;
}
@Override
public BodyMarshaller createMarshaller ()
{
return new BodyMarshaller();
}
@Override
public void dispatchRequest (
ClientObject source, int methodId, Object[] args)
throws InvocationException
{
switch (methodId) {
case BodyMarshaller.SET_IDLE:
((BodyProvider)provider).setIdle(
source, ((Boolean)args[0]).booleanValue()
);
return;
default:
super.dispatchRequest(source, methodId, args);
return;
}
}
}
@@ -0,0 +1,37 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import com.threerings.presents.server.ClientLocal;
import com.threerings.crowd.data.BodyObject;
/**
* Contains information tracked for each body by the server.
*/
public class BodyLocal extends ClientLocal
{
/**
* The time at which the {@link BodyObject#status} field was last updated.
*/
public long statusTime;
}
@@ -0,0 +1,52 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.threerings.util.Name;
import com.threerings.presents.annotation.EventThread;
import com.threerings.presents.server.ClientManager;
import com.threerings.crowd.data.BodyObject;
/**
* Used to lookup {@link BodyObject} instances by name.
*/
@Singleton
public class BodyLocator
{
/**
* Returns the body object for the user with the specified visible name, or null if they are
* not online.
*/
@EventThread
public BodyObject lookupBody (Name visibleName)
{
// by default visibleName is username
return (BodyObject)_clmgr.getClientObject(visibleName);
}
@Inject protected ClientManager _clmgr;
}
@@ -0,0 +1,105 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationManager;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.CrowdCodes;
import com.threerings.crowd.data.OccupantInfo;
import static com.threerings.crowd.Log.log;
/**
* Handles body related services.
*/
@Singleton
public class BodyManager
implements BodyProvider
{
/**
* Constructs and initializes the body manager.
*/
@Inject public BodyManager (InvocationManager invmgr)
{
invmgr.registerDispatcher(new BodyDispatcher(this), CrowdCodes.CROWD_GROUP);
}
/**
* Locates the specified body's occupant info in the specified location, applies the supplied
* occupant info operation to it and then broadcasts the updated info (assuming the occop
* returned true indicating that an update was made).
*/
public <T extends OccupantInfo> boolean updateOccupantInfo (
BodyObject body, OccupantInfo.Updater<T> updater)
{
PlaceManager pmgr = _plreg.getPlaceManager(body.getPlaceOid());
return (pmgr == null) ? false : pmgr.updateOccupantInfo(body.getOid(), updater);
}
/**
* Updates the connection status for the given body object's occupant info in the specified
* location.
*/
public void updateOccupantStatus (BodyObject body, final byte status)
{
// no need to NOOP
if (body.status != status) {
// update the status in their body object
body.setStatus(status);
body.getLocal(BodyLocal.class).statusTime = System.currentTimeMillis();
}
updateOccupantInfo(body, new OccupantInfo.Updater<OccupantInfo>() {
public boolean update (OccupantInfo info) {
if (info.status == status) {
return false;
}
info.status = status;
return true;
}
});
}
// from interface BodyProvider
public void setIdle (ClientObject caller, boolean idle)
{
BodyObject bobj = (BodyObject)caller;
// determine the body's proposed new status
byte nstatus = (idle) ? OccupantInfo.IDLE : OccupantInfo.ACTIVE;
if (bobj.status == nstatus) {
return; // ignore NOOP attempts
}
// update their status!
log.debug("Setting user idle state", "user", bobj.username, "status", nstatus);
updateOccupantStatus(bobj, nstatus);
}
/** Provides access to place managers. */
@Inject protected PlaceRegistry _plreg;
}
@@ -0,0 +1,41 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import javax.annotation.Generated;
import com.threerings.crowd.client.BodyService;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationProvider;
/**
* Defines the server-side of the {@link BodyService}.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from BodyService.java.")
public interface BodyProvider extends InvocationProvider
{
/**
* Handles a {@link BodyService#setIdle} request.
*/
void setIdle (ClientObject caller, boolean arg1);
}
@@ -0,0 +1,54 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.PermissionPolicy;
import com.threerings.presents.server.ClientLocal;
import com.threerings.presents.server.ClientResolver;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.CrowdPermissionPolicy;
/**
* Used to configure crowd-specific client object data.
*/
public class CrowdClientResolver extends ClientResolver
{
@Override // from ClientResolver
public ClientObject createClientObject ()
{
return new BodyObject();
}
@Override
public ClientLocal createLocalAttribute ()
{
return new BodyLocal();
}
@Override // from ClientResolver
public PermissionPolicy createPermissionPolicy ()
{
return new CrowdPermissionPolicy();
}
}
@@ -0,0 +1,82 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.AccessController;
import com.threerings.presents.dobj.DEvent;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.ProxySubscriber;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.server.PresentsObjectAccess;
import com.threerings.bureau.data.BureauClientObject;
import com.threerings.crowd.data.PlaceObject;
/**
* Defines the various object access controllers used by the Crowd server.
*/
public class CrowdObjectAccess
{
/**
* Provides access control for place objects. The default behavior is to allow place occupants
* to subscribe to the place object and to use the {@link PresentsObjectAccess#DEFAULT}
* modification policy.
*/
public static AccessController PLACE = new AccessController()
{
// documentation inherited from interface
public boolean allowSubscribe (DObject object, Subscriber<?> sub)
{
if (sub instanceof ProxySubscriber) {
ClientObject co = ((ProxySubscriber)sub).getClientObject();
return ((PlaceObject)object).occupants.contains(co.getOid());
}
return true;
}
// documentation inherited from interface
public boolean allowDispatch (DObject object, DEvent event)
{
return PresentsObjectAccess.DEFAULT.allowDispatch(object, event);
}
};
/**
* Extends the access control in {@link #PLACE} to allow Bureau clients to subscribe.
*/
public static AccessController BUREAU_ACCESS_PLACE = new AccessController() {
public boolean allowSubscribe (DObject object, Subscriber<?> sub) {
if (sub instanceof ProxySubscriber) {
ClientObject co = ((ProxySubscriber)sub).getClientObject();
if (co instanceof BureauClientObject) {
return true;
}
}
return PLACE.allowSubscribe(object, sub);
}
public boolean allowDispatch (DObject object, DEvent event) {
return PLACE.allowDispatch(object, event);
}
};
}
@@ -0,0 +1,104 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.threerings.util.Name;
import com.threerings.presents.net.AuthRequest;
import com.threerings.presents.server.SessionFactory;
import com.threerings.presents.server.ClientResolver;
import com.threerings.presents.server.PresentsSession;
import com.threerings.presents.server.PresentsServer;
import com.threerings.crowd.chat.server.ChatProvider;
import static com.threerings.crowd.Log.log;
/**
* Extends the Presents server configuring extensions for Crowd services.
*/
@Singleton
public class CrowdServer extends PresentsServer
{
/** Configures dependencies needed by the Crowd services. */
public static class Module extends PresentsServer.Module
{
@Override protected void configure () {
super.configure();
// nada (yet)
}
}
/**
* Initializes all of the server services and prepares for operation.
*/
@Override
public void init (Injector injector)
throws Exception
{
super.init(injector);
// configure the client manager to use our bits
_clmgr.setDefaultSessionFactory(new SessionFactory() {
@Override
public Class<? extends PresentsSession> getSessionClass (AuthRequest areq) {
return CrowdSession.class;
}
@Override
public Class<? extends ClientResolver> getClientResolverClass (Name username) {
return CrowdClientResolver.class;
}
});
}
public static void main (String[] args)
{
Injector injector = Guice.createInjector(new Module());
CrowdServer server = injector.getInstance(CrowdServer.class);
try {
server.init(injector);
server.run();
} catch (Exception e) {
log.warning("Unable to initialize server.", e);
}
}
/** Handles the creation and tracking of place managers. */
@Inject protected PlaceRegistry _plreg;
/** Handles body-related invocation services. */
@Inject protected BodyManager _bodyman;
/** Handles location-related invocation services. */
@Inject protected LocationManager _locman;
/** Provides chat-related invocation services. */
@Inject protected ChatProvider _chatprov;
/** The config key for our list of invocation provider mappings. */
protected final static String PROVIDERS_KEY = "providers";
}
@@ -0,0 +1,92 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import com.google.inject.Inject;
import com.threerings.presents.server.PresentsSession;
import com.threerings.crowd.chat.server.SpeakUtil;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.OccupantInfo;
/**
* Extends the presents session with crowd-specific session handling.
*/
public class CrowdSession extends PresentsSession
{
@Override
protected void sessionConnectionClosed ()
{
super.sessionConnectionClosed();
if (_clobj != null) {
// note that the user is disconnected
BodyObject bobj = (BodyObject)_clobj;
_bodyman.updateOccupantStatus(bobj, OccupantInfo.DISCONNECTED);
}
}
@Override
protected void sessionWillResume ()
{
super.sessionWillResume();
// note that the user's active once more
BodyObject bobj = (BodyObject)_clobj;
_bodyman.updateOccupantStatus(bobj, OccupantInfo.ACTIVE);
}
@Override
protected void sessionDidEnd ()
{
super.sessionDidEnd();
BodyObject body = (BodyObject)_clobj;
// clear out our location so that anyone listening will know that we've left
clearLocation(body);
// reset our status in case this object remains around until they start their next session
// (which could happen very soon)
_bodyman.updateOccupantStatus(body, OccupantInfo.ACTIVE);
// clear our chat history
if (body != null) {
SpeakUtil.clearHistory(body.getVisibleName());
}
}
/**
* When the user ends their session, this method is called to clear out any location they might
* occupy. The default implementation takes care of standard crowd location occupancy, but
* users of other services may which to override this method and clear the user out of a scene,
* zone or other location-derived occupancy.
*/
protected void clearLocation (BodyObject bobj)
{
_locman.leaveOccupiedPlace(bobj);
}
@Inject protected BodyManager _bodyman;
@Inject protected LocationManager _locman;
}
@@ -0,0 +1,77 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import javax.annotation.Generated;
import com.threerings.crowd.client.LocationService;
import com.threerings.crowd.data.LocationMarshaller;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationDispatcher;
import com.threerings.presents.server.InvocationException;
/**
* Dispatches requests to the {@link LocationProvider}.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from LocationService.java.")
public class LocationDispatcher extends InvocationDispatcher<LocationMarshaller>
{
/**
* Creates a dispatcher that may be registered to dispatch invocation
* service requests for the specified provider.
*/
public LocationDispatcher (LocationProvider provider)
{
this.provider = provider;
}
@Override
public LocationMarshaller createMarshaller ()
{
return new LocationMarshaller();
}
@Override
public void dispatchRequest (
ClientObject source, int methodId, Object[] args)
throws InvocationException
{
switch (methodId) {
case LocationMarshaller.LEAVE_PLACE:
((LocationProvider)provider).leavePlace(
source
);
return;
case LocationMarshaller.MOVE_TO:
((LocationProvider)provider).moveTo(
source, ((Integer)args[0]).intValue(), (LocationService.MoveListener)args[1]
);
return;
default:
super.dispatchRequest(source, methodId, args);
return;
}
}
}
@@ -0,0 +1,185 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.server.ClientManager;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.PresentsSession;
import com.threerings.crowd.client.LocationService;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.CrowdCodes;
import com.threerings.crowd.data.LocationCodes;
import com.threerings.crowd.data.Place;
import com.threerings.crowd.data.PlaceConfig;
import static com.threerings.crowd.Log.log;
/**
* Handles location-related services.
*/
@Singleton
public class LocationManager
implements LocationProvider, LocationCodes
{
@Inject public LocationManager (InvocationManager invmgr)
{
invmgr.registerDispatcher(new LocationDispatcher(this), CrowdCodes.CROWD_GROUP);
}
// from interface LocationProvider
public void moveTo (ClientObject caller, int placeOid, LocationService.MoveListener listener)
throws InvocationException
{
// do the move and send the response
listener.moveSucceeded(moveTo((BodyObject)caller, placeOid));
}
// from interface LocationProvider
public void leavePlace (ClientObject caller)
{
leaveOccupiedPlace((BodyObject)caller);
}
/**
* Moves the specified body from whatever location they currently occupy to the location
* identified by the supplied place oid.
*
* @return the config object for the new location.
*
* @exception InvocationException thrown if the move was not successful for some reason
* (which will be communicated as an error code in the exception's message data).
*/
public PlaceConfig moveTo (BodyObject source, int placeOid)
throws InvocationException
{
// make sure the place in question actually exists
PlaceManager pmgr = _plreg.getPlaceManager(placeOid);
if (pmgr == null) {
log.info("Requested to move to non-existent place", "who", source.who(),
"placeOid", placeOid);
throw new InvocationException(NO_SUCH_PLACE);
}
// if they're already in the location they're asking to move to, just give them the config
// because we don't need to update anything in distributed object world
Place place = pmgr.getLocation();
if (place.equals(source.location)) {
log.debug("Going along with client request to move to where they already are",
"source", source.who(), "place", place);
return pmgr.getConfig();
}
// make sure they have access to the specified place
String errmsg;
if ((errmsg = pmgr.ratifyBodyEntry(source)) != null) {
throw new InvocationException(errmsg);
}
// acquire a lock on the body object to avoid breakage by rapid fire moveTo requests
if (!source.acquireLock("moveToLock")) {
// if we're still locked, a previous moveTo request hasn't been fully processed
throw new InvocationException(MOVE_IN_PROGRESS);
}
// configure the client accordingly if the place uses a custom class loader
PresentsSession client = _clmgr.getClient(source.username);
if (client != null) {
client.setClassLoader(pmgr.getClass().getClassLoader());
}
try {
source.startTransaction();
try {
// remove them from any previous location
leaveOccupiedPlace(source);
// let the place manager know that we're coming in
pmgr.bodyWillEnter(source);
// let the body object know that it's going in
source.willEnterPlace(place, pmgr.getPlaceObject());
} finally {
source.commitTransaction();
}
} finally {
// and finally queue up an event to release the lock once these events are processed
source.releaseLock("moveToLock");
}
return pmgr.getConfig();
}
/**
* Removes the specified body from the place object they currently occupy. Does nothing if the
* body is not currently in a place.
*/
public void leaveOccupiedPlace (BodyObject source)
{
Place oldloc = source.location;
if (oldloc == null) {
return; // nothing to do if they weren't previously in some location
}
PlaceManager pmgr = _plreg.getPlaceManager(oldloc.placeOid);
if (pmgr == null) {
log.warning("Body requested to leave no longer existent place?",
"boid", source.getOid(), "place", oldloc);
return;
}
// tell the place manager that they're on the way out
pmgr.bodyWillLeave(source);
// clear out their location
source.didLeavePlace(pmgr.getPlaceObject());
}
/**
* Forcibly moves the specified body object to the new place. This is accomplished by first
* removing the client from their old location and then sending the client a notification,
* instructing it to move to the new location (which it does using the normal moveTo service).
* This has the benefit that the client is removed from their old place regardless of whether
* or not they are cooperating. If they choose to ignore the forced move request, they will
* remain in limbo, unable to do much of anything.
*/
public void moveBody (BodyObject source, Place place)
{
// first remove them from their old place
leaveOccupiedPlace(source);
// then send a forced move notification
LocationSender.forcedMove(source, place.placeOid);
}
@Inject protected RootDObjectManager _omgr;
@Inject protected PlaceRegistry _plreg;
@Inject protected ClientManager _clmgr;
}
@@ -0,0 +1,48 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import javax.annotation.Generated;
import com.threerings.crowd.client.LocationService;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationProvider;
/**
* Defines the server-side of the {@link LocationService}.
*/
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
comments="Derived from LocationService.java.")
public interface LocationProvider extends InvocationProvider
{
/**
* Handles a {@link LocationService#leavePlace} request.
*/
void leavePlace (ClientObject caller);
/**
* Handles a {@link LocationService#moveTo} request.
*/
void moveTo (ClientObject caller, int arg1, LocationService.MoveListener arg2)
throws InvocationException;
}
@@ -0,0 +1,48 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationSender;
import com.threerings.crowd.client.LocationDecoder;
import com.threerings.crowd.client.LocationReceiver;
/**
* Used to issue notifications to a {@link LocationReceiver} instance on a
* client.
*/
public class LocationSender extends InvocationSender
{
/**
* Issues a notification that will result in a call to {@link
* LocationReceiver#forcedMove} on a client.
*/
public static void forcedMove (
ClientObject target, int arg1)
{
sendNotification(
target, LocationDecoder.RECEIVER_CODE, LocationDecoder.FORCED_MOVE,
new Object[] { Integer.valueOf(arg1) });
}
}
@@ -0,0 +1,36 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import com.threerings.crowd.data.OccupantInfo;
/**
* An operation to be applied to all occupants in a location that may
* contain occupants, e.g., a {@link PlaceManager}.
*/
public interface OccupantOp
{
/**
* Called with the occupant info for each occupant in the location.
*/
void apply (OccupantInfo info);
}
@@ -0,0 +1,815 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.Interval;
import com.samskivert.util.MethodFinder;
import com.samskivert.util.StringUtil;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.AccessController;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DSet;
import com.threerings.presents.dobj.DynamicListener;
import com.threerings.presents.dobj.EntryUpdatedEvent;
import com.threerings.presents.dobj.MessageEvent;
import com.threerings.presents.dobj.MessageListener;
import com.threerings.presents.dobj.NamedSetAdapter;
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.RootDObjectManager;
import com.threerings.presents.dobj.SetAdapter;
import com.threerings.presents.server.InvocationDispatcher;
import com.threerings.presents.server.InvocationManager;
import com.threerings.crowd.chat.data.ChatCodes;
import com.threerings.crowd.chat.server.SpeakDispatcher;
import com.threerings.crowd.chat.server.SpeakHandler;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.OccupantInfo;
import com.threerings.crowd.data.Place;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject;
import static com.threerings.crowd.Log.log;
/**
* 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, SpeakHandler.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.
*
* @deprecated Use dynamically bound methods instead. See {@link DynamicListener}.
*/
@Deprecated
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.
*/
void handleEvent (MessageEvent event, PlaceManager pmgr);
}
/**
* Used to call methods on this place manager's delegates.
*/
public static abstract class DelegateOp
{
public DelegateOp (Class<? extends PlaceManagerDelegate> delegateClass) {
_delegateClass = delegateClass;
}
/** Applies an operation to the supplied delegate. */
public abstract void apply (PlaceManagerDelegate delegate);
public boolean shouldApply (PlaceManagerDelegate delegate) {
return _delegateClass.isInstance(delegate);
}
protected Class<? extends PlaceManagerDelegate> _delegateClass;
}
/**
* Returns a reference to our place configuration object.
*/
public PlaceConfig getConfig ()
{
return _config;
}
/**
* Returns a {@link Place} instance that identifies this place.
*/
public Place getLocation ()
{
return new Place(_plobj.getOid());
}
/**
* Returns the place object managed by this place manager.
*/
public PlaceObject getPlaceObject ()
{
return _plobj;
}
/**
* Applies the supplied occupant operation to each occupant currently present in this place.
*/
public void applyToOccupants (OccupantOp op)
{
if (_plobj != null) {
for (OccupantInfo info : _plobj.occupantInfo) {
op.apply(info);
}
}
}
/**
* Calls the supplied updater on the canonical occupant info record for the specified body
* (which must be an occupant of this place) and broadcasts the update to all other occupants.
*
* @return true if the updater was called and the update sent, false if the body could not be
* located (was not an occupant of this place) or the updater made no modifications.
*
* @exception ClassCastException thrown if the type of the supplied updater does not match the
* type of {@link OccupantInfo} record used for the occupant. Caveat utilitor.
*/
public <T extends OccupantInfo> boolean updateOccupantInfo (
int bodyOid, OccupantInfo.Updater<T> updater)
{
@SuppressWarnings("unchecked") T info = (T)_occInfo.get(bodyOid);
if (info == null || !updater.update(info)) {
return false;
}
// update the canonical copy
_occInfo.put(info.getBodyOid(), info);
// clone the canonical copy and send an event updating the distributed set with that clone
_plobj.updateOccupantInfo(info.clone());
return true;
}
/**
* Called by the place registry after creating this place manager.
*/
public void init (PlaceRegistry registry, InvocationManager invmgr, RootDObjectManager omgr,
BodyLocator locator, PlaceConfig config)
{
_registry = registry;
_invmgr = invmgr;
_omgr = omgr;
_locator = locator;
_config = config;
// initialize our delegates
applyToDelegates(new DelegateOp(PlaceManagerDelegate.class) {
@Override
public void apply (PlaceManagerDelegate delegate) {
delegate.init(PlaceManager.this, _omgr, _invmgr);
}
});
// let derived classes do initialization stuff
try {
didInit();
} catch (Throwable t) {
log.warning("Manager choked in didInit()", "where", where(), t);
}
}
/**
* Adds the supplied delegate to the list for this manager.
*/
public void addDelegate (PlaceManagerDelegate delegate)
{
if (_delegates == null) {
_delegates = Lists.newArrayList();
}
if (_omgr != null) {
delegate.init(this, _omgr, _invmgr);
delegate.didInit(_config);
}
_delegates.add(delegate);
}
/**
* Applies the supplied operation to this manager's registered delegates.
*/
public void applyToDelegates (DelegateOp op)
{
if (_delegates != null) {
for (int ii = 0, ll = _delegates.size(); ii < ll; ii++) {
PlaceManagerDelegate delegate = _delegates.get(ii);
if (op.shouldApply(delegate)) {
op.apply(delegate);
}
}
}
}
/**
* 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 by the place manager after the place object has been successfully created.
*/
public void startup (PlaceObject plobj)
{
// keep track of this
_plobj = plobj;
// we usually want to create and register a speaker service instance that clients can use
// to speak in this place
if (shouldCreateSpeakService()) {
plobj.setSpeakService(addDispatcher(new SpeakDispatcher(createSpeakHandler(plobj))));
}
// we'll need to hear about place object events
plobj.addListener(this);
plobj.addListener(_bodyUpdater);
plobj.addListener(_occListener);
plobj.addListener(_deathListener);
// configure this place's access controller
plobj.setAccessController(getAccessController());
// let our derived classes do their thang
try {
didStartup();
} catch (Throwable t) {
log.warning("Manager choked in didStartup()", "where", where(), t);
}
// since we start empty, we need to immediately assume shutdown
checkShutdownInterval();
}
/**
* 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
_omgr.destroyObject(_plobj.getOid());
// make sure we don't have any shutdowner in the queue
cancelShutdowner();
}
/**
* Provides an opportunity for the place manager to prevent bodies from entering.
*
* @return <code>null</code> if the body can enter, otherwise a translatable message explaining
* the reason the body is blocked from entering
*/
public String ratifyBodyEntry (BodyObject body)
{
return null;
}
/**
* This is called to inform the manager that a body is on the way in. This is called at the
* very beginning of the entry process before the client is informed that it is allowed to
* enter. This will be followed by a call to {@link #bodyEntered} once all events relating to
* body entry have been processed.
*/
public void bodyWillEnter (BodyObject body)
{
// create a new occupant info instance and insert it into our canonical table
OccupantInfo info = body.createOccupantInfo(_plobj);
_occInfo.put(info.getBodyOid(), info);
_plobj.startTransaction();
try {
addOccupantInfo(body, info.clone());
} finally {
_plobj.commitTransaction();
}
}
/**
* Called to inform a manager that a body is about to leave this place. This will be followed
* by a call to {@link #bodyLeft} once all events relating to body entry have been processed.
*/
public void bodyWillLeave (BodyObject body)
{
_plobj.startTransaction();
try {
// remove their occupant info (which is keyed on oid)
_plobj.removeFromOccupantInfo(body.getOid());
// and remove them from the occupant list
_plobj.removeFromOccupants(body.getOid());
} finally {
_plobj.commitTransaction();
}
}
/**
* 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.
*
* @deprecated Use dynamically bound methods instead. See {@link DynamicListener}.
*/
@Deprecated
public void registerMessageHandler (String name, MessageHandler handler)
{
// create our handler map if necessary
if (_msghandlers == null) {
_msghandlers = Maps.newHashMap();
}
_msghandlers.put(name, handler);
}
// from interface MessageListener
public void messageReceived (MessageEvent event)
{
if (_msghandlers != null) {
MessageHandler handler = _msghandlers.get(event.getName());
if (handler != null) {
handler.handleEvent(event, this);
}
}
// If the message is directed at us, see if it's a request for a method invocation
if (event.isPrivate()) { // aka if (event instanceof ServerMessageEvent)
// the first argument should be the client object of the caller or null if it is
// a server-originated event
int srcoid = event.getSourceOid();
DObject source = (srcoid <= 0) ? null : _omgr.getObject(srcoid);
Object[] args = event.getArgs(), nargs;
if (args == null) {
nargs = new Object[] { source };
} else {
nargs = new Object[args.length+1];
nargs[0] = source;
System.arraycopy(args, 0, nargs, 1, args.length);
}
// Lazily create our dispatcher now that it's actually getting a message
if (_dispatcher == null) {
Class<?> clazz = getClass();
MethodFinder finder = _dispatcherFinders.get(clazz);
if (finder == null) {
finder = new MethodFinder(clazz);
_dispatcherFinders.put(clazz, finder);
}
_dispatcher = new DynamicListener<DSet.Entry>(this, finder);
}
_dispatcher.dispatchMethod(event.getName(), nargs);
}
}
// documentation inherited from interface
public boolean isValidSpeaker (DObject speakObj, ClientObject speaker, byte mode)
{
// have a whitelist for valid modes (no broadcasting, that's done elsewhere)
switch (mode) {
default:
return false;
case ChatCodes.DEFAULT_MODE:
case ChatCodes.THINK_MODE:
case ChatCodes.EMOTE_MODE:
case ChatCodes.SHOUT_MODE:
break;
}
// only allow people in the room to speak.
return _plobj.occupants.contains(speaker.getOid());
}
/**
* 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 (_plobj == null) ? StringUtil.shortClassName(this) + ":-1" : _plobj.which();
}
/**
* 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(StringBuilder)
*/
@Override
public String toString ()
{
StringBuilder buf = new StringBuilder();
buf.append("[");
toString(buf);
buf.append("]");
return buf.toString();
}
/**
* Derived classes will generally override this method to create a custom {@link PlaceObject}
* derivation that contains extra information.
*/
protected PlaceObject createPlaceObject ()
{
try {
return getPlaceObjectClass().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @deprecated Use {@link #createPlaceObject}.
*/
@Deprecated
protected Class<? extends PlaceObject> getPlaceObjectClass ()
{
return PlaceObject.class;
}
/**
* 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(PlaceManagerDelegate.class) {
@Override
public void apply (PlaceManagerDelegate delegate) {
delegate.didInit(_config);
}
});
}
/**
* 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 ()
{
}
/**
* @return true if we should create a speaker service for our place object so that clients can
* use it to speak in this place.
*/
protected boolean shouldCreateSpeakService ()
{
return true;
}
/**
* Creates an access controller for this place's distributed object, which by default is {@link
* CrowdObjectAccess#PLACE}.
*/
protected AccessController getAccessController ()
{
return CrowdObjectAccess.PLACE;
}
/**
* 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(PlaceManagerDelegate.class) {
@Override
public void apply (PlaceManagerDelegate delegate) {
delegate.didStartup(_plobj);
}
});
}
/**
* 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 ()
{
// clear out our listenership
_plobj.removeListener(this);
_plobj.removeListener(_bodyUpdater);
_plobj.removeListener(_occListener);
_plobj.removeListener(_deathListener);
// clear out our invocation service registrations
for (InvocationMarshaller marsh : _marshallers) {
_invmgr.clearDispatcher(marsh);
}
// let our delegates know that we've shut down
applyToDelegates(new DelegateOp(PlaceManagerDelegate.class) {
@Override
public void apply (PlaceManagerDelegate delegate) {
delegate.didShutdown();
}
});
// if shutting down emptied the place and scheduled the shutdowner, clear that out
cancelShutdowner();
}
/**
* Registers an invocation dispatcher and notes the registration such that it will be
* automatically cleared when this manager shuts down.
*/
protected <T extends InvocationMarshaller> T addDispatcher (InvocationDispatcher<T> disp)
{
T marsh = _invmgr.registerDispatcher(disp);
_marshallers.add(marsh);
return marsh;
}
/**
* Called when a body object enters this place.
*/
protected void bodyEntered (final int bodyOid)
{
log.debug("Body entered", "where", where(), "oid", bodyOid);
// let our delegates know what's up
applyToDelegates(new DelegateOp(PlaceManagerDelegate.class) {
@Override
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)
{
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 = Integer.valueOf(bodyOid);
if (_plobj.occupantInfo.containsKey(key)) {
_plobj.removeFromOccupantInfo(key);
}
// clear out their canonical (local) occupant info record
OccupantInfo leaver = _occInfo.remove(bodyOid);
// let our delegates know what's up
applyToDelegates(new DelegateOp(PlaceManagerDelegate.class) {
@Override public void apply (PlaceManagerDelegate delegate) {
delegate.bodyLeft(bodyOid);
}
});
// if that leaves us with zero occupants, maybe do something
if (shouldDeclareEmpty(leaver)) {
placeBecameEmpty();
}
}
/**
* Adds this occupant's info to the {@link PlaceObject}. This is called in a transaction on the
* place object so if a derived class needs to add additional information for an occupant it
* should override this method. It may opt to add the information before calling super if it
* wishes to rely on its information being configured when {@link #bodyAdded} is called.
*/
protected void addOccupantInfo (BodyObject body, OccupantInfo info)
{
// clone the canonical copy and insert it into the DSet
_plobj.addToOccupantInfo(info);
// add the body oid to our place object's occupant list
_plobj.addToOccupants(body.getOid());
}
/**
* Returns whether the location should be marked as empty and potentially shutdown.
*/
protected boolean shouldDeclareEmpty (OccupantInfo leaver)
{
return (_plobj.occupants.size() == 0);
}
/**
* 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(PlaceManagerDelegate.class) {
@Override
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(PlaceManagerDelegate.class) {
@Override
public void apply (PlaceManagerDelegate delegate) {
delegate.placeBecameEmpty();
}
});
// Log.info("Place became empty " + where() + ".");
checkShutdownInterval();
}
/**
* Called on startup and when the place is empty.
*/
protected void checkShutdownInterval ()
{
// queue up a shutdown interval, unless we've already got one.
long idlePeriod = idleUnloadPeriod();
if (idlePeriod > 0L && _shutdownInterval == null) {
(_shutdownInterval = _omgr.newInterval(new Runnable() {
public void run () {
log.debug("Unloading idle place '" + where() + "'.");
shutdown();
}
})).schedule(idlePeriod);
}
}
/**
* Cancels any registered shutdown interval.
*/
protected void cancelShutdowner ()
{
if (_shutdownInterval != null) {
_shutdownInterval.cancel();
_shutdownInterval = null;
}
}
/**
* 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;
}
/**
* 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 (StringBuilder buf)
{
buf.append("place=").append(_plobj);
buf.append(", config=").append(_config);
}
/**
* Creates the speak handler for this place. Derived classes can customize the speak handler if
* they so desire.
*/
protected SpeakHandler createSpeakHandler (PlaceObject plobj)
{
return new SpeakHandler(plobj, this);
}
/** Listens for occupant updates. */
protected SetAdapter<OccupantInfo> _bodyUpdater =
new NamedSetAdapter<OccupantInfo>(PlaceObject.OCCUPANT_INFO) {
@Override
public void namedEntryUpdated (EntryUpdatedEvent<OccupantInfo> event) {
bodyUpdated(event.getEntry());
}
};
/** Listens for body entry and departure. */
protected OidListListener _occListener = new OidListListener() {
public void objectAdded (ObjectAddedEvent event) {
if (event.getName().equals(PlaceObject.OCCUPANTS)) {
bodyEntered(event.getOid());
}
}
public void objectRemoved (ObjectRemovedEvent event) {
if (event.getName().equals(PlaceObject.OCCUPANTS)) {
bodyLeft(event.getOid());
}
}
};
/** Listens for death of our place object. */
protected ObjectDeathListener _deathListener = new ObjectDeathListener() {
public void objectDestroyed (ObjectDestroyedEvent event) {
// unregister ourselves
_registry.unmapPlaceManager(PlaceManager.this);
// let our derived classes and delegates shut themselves down
try {
didShutdown();
} catch (Throwable t) {
log.warning("Manager choked in didShutdown()", "where", where(), t);
}
}
};
/** 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 RootDObjectManager _omgr;
/** Used to look up body objects by name. */
protected BodyLocator _locator;
/** 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 Map<String, MessageHandler> _msghandlers;
/** A list of the delegates in use by this manager. */
protected List<PlaceManagerDelegate> _delegates;
/** A list of services registered with {@link #addDispatcher} which will be automatically
* cleared when this manager shuts down. */
protected List<InvocationMarshaller> _marshallers = Lists.newArrayList();
/** Used to keep a canonical copy of the occupant info records. */
protected HashIntMap<OccupantInfo> _occInfo = new HashIntMap<OccupantInfo>();
/** The interval currently registered to shut this place down after a certain period of
* idility, or null if no interval is currently registered. */
protected Interval _shutdownInterval;
/** Used to do method lookup magic when we receive message events. */
protected DynamicListener<?> _dispatcher;
/** Maps from a PlaceManager subclass to a MethodFinder for it. When there are many many
* instances of a PlaceManager in existence, having a MethodFinder instance for each gets quite
* expensive. */
protected static Map<Class<?>, MethodFinder> _dispatcherFinders = Maps.newHashMap();
}
@@ -0,0 +1,130 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.server.InvocationDispatcher;
import com.threerings.presents.server.InvocationManager;
import com.threerings.crowd.data.OccupantInfo;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject;
/**
* Provides an extensible mechanism for encapsulating delegated functionality that works with the
* place services.
*
* <p> Thanks to Java's lack of multiple inheritance, it will likely become necessary to factor
* certain services that might be used by a variety of {@link PlaceManager} derived classes into
* delegate classes because they do not fit into the single inheritance hierarchy that makes sense
* for a particular application. To facilitate this process, this delegate class is provided which
* the standard place manager can be made to call out to for all of the standard methods.
*/
public class PlaceManagerDelegate
{
/**
* Called by the place manager when this delegate is registered with it. This will happen
* before any calls to {@link #didInit}, etc.
*/
public void init (PlaceManager plmgr, RootDObjectManager omgr, InvocationManager invmgr)
{
_plmgr = plmgr;
_omgr = omgr;
_invmgr = invmgr;
}
/**
* Called when the place manager is initialized.
*/
public void didInit (PlaceConfig config)
{
}
/**
* Called when the place manager is started up.
*/
public void didStartup (PlaceObject plobj)
{
}
/**
* Called when the place manager is shut down.
*/
public void didShutdown ()
{
}
/**
* Called when a body enters the place.
*/
public void bodyEntered (int bodyOid)
{
}
/**
* Called when a body leaves the place.
*/
public void bodyLeft (int bodyOid)
{
}
/**
* Called when a body occupant info is updated.
*/
public void bodyUpdated (OccupantInfo info)
{
}
/**
* Called when the last body leaves the place.
*/
public void placeBecameEmpty ()
{
}
/**
* Invokes {@link PlaceManager#where}.
*/
public String where ()
{
return _plmgr.where();
}
/**
* Registers an invocation dispatcher and notes the registration such that it will be
* automatically cleared when our parent manager shuts down.
*/
protected <T extends InvocationMarshaller> T addDispatcher (InvocationDispatcher<T> disp)
{
return _plmgr.addDispatcher(disp);
}
/** A reference to the manager for which we are delegating. */
protected PlaceManager _plmgr;
/** A reference to our distributed object manager. */
protected RootDObjectManager _omgr;
/** A reference to our invocation manager. */
protected InvocationManager _invmgr;
}
@@ -0,0 +1,283 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.server;
import java.util.Iterator;
import java.util.List;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.samskivert.util.IntMap;
import com.samskivert.util.IntMaps;
import com.samskivert.util.Lifecycle;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationManager;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject;
import static com.threerings.crowd.Log.log;
/**
* The place registry keeps track of all of the active places in the server. It should be used to
* create new places and it will take care of instantiating and initializing a place manager to
* manage newly created places.
*/
@Singleton
public class PlaceRegistry
implements Lifecycle.ShutdownComponent
{
/** Used in conjunction with {@link PlaceRegistry#createPlace(PlaceConfig,PreStartupHook)}. */
public static interface PreStartupHook
{
void invoke (PlaceManager plmgr);
}
/**
* Creates and initializes the place registry. This is called by the server during its
* initialization phase.
*/
@Inject public PlaceRegistry (Lifecycle cycle)
{
cycle.addComponent(this);
}
/**
* Returns the place manager associated with the specified place object id or null if no such
* place exists.
*/
public PlaceManager getPlaceManager (int placeOid)
{
return _pmgrs.get(placeOid);
}
/**
* Creates and registers a new place manager with no delegates.
*
* @see #createPlace(PlaceConfig,List)
*/
public PlaceManager createPlace (PlaceConfig config)
throws InstantiationException, InvocationException
{
return createPlace(config, null, null);
}
/**
* Creates and registers a new place manager along with the place object to be managed. The
* registry takes care of tracking the creation of the object and informing the manager when it
* is created.
*
* @param config the configuration object for the place to be created. The {@link PlaceManager}
* derived class that should be instantiated to manage the place will be determined from the
* config object.
* @param delegates a list of {@link PlaceManagerDelegate} instances to be registered with the
* manager prior to it being initialized and started up. <em>Note:</em> these delegates will
* have dependencies injected into them prior to registering them with the manager.
*
* @return a reference to the place manager, which will have been configured with its place
* object and started up (via a call to {@link PlaceManager#startup}.
*
* @exception InstantiationException thrown if an error occurs trying to instantiate and
* initialize the place manager.
* @exception InvocationException thrown if the place manager returns failure from the call to
* {@link PlaceManager#checkPermissions}. The error string returned by that call will be
* provided as in the exception.
*/
public PlaceManager createPlace (PlaceConfig config, List<PlaceManagerDelegate> delegates)
throws InstantiationException, InvocationException
{
return createPlace(config, delegates, null);
}
/**
* Don't use this method, see {@link #createPlace(PlaceConfig)}.
*
* @param hook an optional pre-startup hook that allows a place manager to be configured prior
* to having {@link PlaceManager#startup} called. This mainly exists because it used to be
* possible to do such things. Try not to use this in new code.
*/
public PlaceManager createPlace (PlaceConfig config, PreStartupHook hook)
throws InstantiationException, InvocationException
{
return createPlace(config, null, hook);
}
/**
* Returns an enumeration of all of the registered place objects. This should only be accessed
* on the dobjmgr thread and shouldn't be kept around across event dispatches.
*/
public Iterator<PlaceObject> enumeratePlaces ()
{
final Iterator<PlaceManager> itr = _pmgrs.values().iterator();
return new Iterator<PlaceObject>() {
public boolean hasNext () {
return itr.hasNext();
}
public PlaceObject next () {
PlaceManager plmgr = itr.next();
return (plmgr == null) ? null : plmgr.getPlaceObject();
}
public void remove () {
throw new UnsupportedOperationException();
}
};
}
/**
* Returns an enumeration of all of the registered place managers. This should only be
* accessed on the dobjmgr thread and shouldn't be kept around across event dispatches.
*/
public Iterator<PlaceManager> enumeratePlaceManagers ()
{
return _pmgrs.values().iterator();
}
// from interface Lifecycle.ShutdownComponent
public void shutdown ()
{
// shut down all active places
for (Iterator<PlaceManager> iter = enumeratePlaceManagers(); iter.hasNext(); ) {
PlaceManager pmgr = iter.next();
try {
pmgr.shutdown();
} catch (Exception e) {
log.warning("Place manager failed shutting down", "where", pmgr.where(), e);
}
}
}
/**
* Creates a place manager using the supplied config, injects dependencies into and registers
* the supplied list of delegates, runs the supplied pre-startup hook and finally returns it.
*/
protected PlaceManager createPlace (PlaceConfig config, List<PlaceManagerDelegate> delegates,
PreStartupHook hook)
throws InstantiationException, InvocationException
{
PlaceManager pmgr = null;
try {
// create a place manager using the class supplied in the place config
pmgr = createPlaceManager(config);
// if we have delegates, inject their dependencies and add them
if (delegates != null) {
for (PlaceManagerDelegate delegate : delegates) {
_injector.injectMembers(delegate);
pmgr.addDelegate(delegate);
}
}
// let the pmgr know about us and its configuration
pmgr.init(this, _invmgr, _omgr, selectLocator(config), config);
} catch (Exception e) {
log.warning(e);
throw new InstantiationException("Error creating PlaceManager for " + config);
}
// let the manager abort the whole process if it fails any permissions checks
String errmsg = pmgr.checkPermissions();
if (errmsg != null) {
// give the place manager a chance to clean up after its early initialization process
pmgr.permissionsFailed();
throw new InvocationException(errmsg);
}
// and create and register the place object
PlaceObject plobj = pmgr.createPlaceObject();
_omgr.registerObject(plobj);
// stick the manager into our table
_pmgrs.put(plobj.getOid(), pmgr);
// start the place manager up with the newly created place object
try {
if (hook != null) {
hook.invoke(pmgr);
}
pmgr.startup(plobj);
} catch (Exception e) {
log.warning("Error starting place manager", "obj", plobj, "pmgr", pmgr, e);
}
return pmgr;
}
/**
* Creates an instance of a {@link PlaceManager} using the information in the supplied place
* config. Derived classes may wish to specialize this process for certain places for example
* loading user supplied place management code from a special class loader that sandboxes their
* code.
*/
protected PlaceManager createPlaceManager (PlaceConfig config)
throws Exception
{
@SuppressWarnings("unchecked") Class<? extends PlaceManager> clazz =
(Class<? extends PlaceManager>)Class.forName(config.getManagerClassName());
return _injector.getInstance(clazz);
}
/**
* Selects the body locator to be used by the PlaceManager created for the supplied config.
*/
protected BodyLocator selectLocator (PlaceConfig config)
{
return _locator;
}
/**
* Called by the place manager when it has been shut down.
*/
protected void unmapPlaceManager (PlaceManager pmgr)
{
int ploid = pmgr.getPlaceObject().getOid();
// remove it from the table
if (_pmgrs.remove(ploid) == null) {
log.warning("Requested to unmap unmapped place manager", "pmgr", pmgr);
// } else {
// Log.info("Unmapped place manager [class=" + pmgr.getClass().getName() +
// ", ploid=" + ploid + "].");
}
}
/** We use this to inject dependencies into place managers that we create. */
@Inject protected Injector _injector;
/** The invocation manager with which we operate. */
@Inject protected InvocationManager _invmgr;
/** The distributed object manager with which we operate. */
@Inject protected RootDObjectManager _omgr;
/** Used to look body objects up by name. */
@Inject protected BodyLocator _locator;
/** A mapping from place object id to place manager. */
protected IntMap<PlaceManager> _pmgrs = IntMaps.newHashIntMap();
}
@@ -0,0 +1,83 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.util;
import com.threerings.util.MessageManager;
import com.threerings.presents.util.PresentsContext;
import com.threerings.crowd.chat.client.ChatDirector;
import com.threerings.crowd.client.LocationDirector;
import com.threerings.crowd.client.OccupantDirector;
import com.threerings.crowd.client.PlaceView;
/**
* The crowd context provides access to the various managers, etc. that
* are needed by the crowd client code.
*/
public interface CrowdContext extends PresentsContext
{
/**
* Returns a reference to the location director.
*/
LocationDirector getLocationDirector ();
/**
* Returns a reference to the occupant director.
*/
OccupantDirector getOccupantDirector ();
/**
* Provides access to the chat director.
*/
ChatDirector getChatDirector ();
/**
* Returns a reference to the message manager used by the client to generate localized
* messages.
*/
MessageManager getMessageManager ();
/**
* When the client enters a new place, the location director creates a
* place controller which then creates a place view to visualize the
* place for the user. The place view created by the place controller
* will be passed to this function to actually display it in whatever
* user interface is provided for the user. We don't require any
* particular user interface toolkit, so it is expected that the place
* view implementation will coordinate with the client implementation
* so that the client can display the view provided by the place
* controller.
*
* <p> Though the place view is created before we enter the place, it
* won't be displayed (via a call to this function) until we have
* fully entered the place and are ready for user interaction.
*/
void setPlaceView (PlaceView view);
/**
* When the client leaves a place, the place controller will remove
* any place view it set previously via {@link #setPlaceView} with a
* call to this method.
*/
void clearPlaceView (PlaceView view);
}