diff --git a/src/as/com/threerings/crowd/chat/client/ChannelSpeakService.as b/src/as/com/threerings/crowd/chat/client/ChannelSpeakService.as new file mode 100644 index 000000000..9b4bba952 --- /dev/null +++ b/src/as/com/threerings/crowd/chat/client/ChannelSpeakService.as @@ -0,0 +1,36 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.crowd.chat.client { + +import com.threerings.crowd.chat.data.ChatChannel; +import com.threerings.presents.client.Client; +import com.threerings.presents.client.InvocationService; + +/** + * An ActionScript version of the Java ChannelSpeakService interface. + */ +public interface ChannelSpeakService extends InvocationService +{ + // from Java interface ChannelSpeakService + function speak (arg1 :Client, arg2 :ChatChannel, arg3 :String, arg4 :int) :void; +} +} diff --git a/src/as/com/threerings/crowd/chat/data/ChannelSpeakMarshaller.as b/src/as/com/threerings/crowd/chat/data/ChannelSpeakMarshaller.as new file mode 100644 index 000000000..42ee12dc4 --- /dev/null +++ b/src/as/com/threerings/crowd/chat/data/ChannelSpeakMarshaller.as @@ -0,0 +1,50 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.crowd.chat.data { + +import com.threerings.crowd.chat.client.ChannelSpeakService; +import com.threerings.presents.client.Client; +import com.threerings.presents.data.InvocationMarshaller; +import com.threerings.util.Byte; + +/** + * Provides the implementation of the 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. + */ +public class ChannelSpeakMarshaller extends InvocationMarshaller + implements ChannelSpeakService +{ + /** The method id used to dispatch speak requests. */ + public static const SPEAK :int = 1; + + // from interface ChannelSpeakService + public function speak (arg1 :Client, arg2 :ChatChannel, arg3 :String, arg4 :int) :void + { + sendRequest(arg1, SPEAK, [ + arg2, arg3, Byte.valueOf(arg4) + ]); + } +} +} diff --git a/src/as/com/threerings/crowd/chat/data/ChatChannel.as b/src/as/com/threerings/crowd/chat/data/ChatChannel.as new file mode 100644 index 000000000..1e82ecfaa --- /dev/null +++ b/src/as/com/threerings/crowd/chat/data/ChatChannel.as @@ -0,0 +1,54 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.crowd.chat.data { + +import com.threerings.io.SimpleStreamableObject; +import com.threerings.util.Comparable; +import com.threerings.util.Equalable; + +import com.threerings.presents.dobj.DSet_Entry; + +/** + * Represents a chat channel. + */ +public /*abstract*/ class ChatChannel extends SimpleStreamableObject + implements Comparable, Equalable, DSet_Entry +{ + // from interface Comparable + public function compareTo (other :Object) :int + { + throw new Error("abstract"); + } + + // from interface Equalable + public function equals (other :Object) :Boolean + { + throw new Error("abstract"); + } + + // from interface DSet_Entry + public function getKey () :Object + { + return this; + } +} +} diff --git a/src/as/com/threerings/crowd/chat/data/ChatCodes.as b/src/as/com/threerings/crowd/chat/data/ChatCodes.as index 6c40b0d8b..c0b70ae05 100644 --- a/src/as/com/threerings/crowd/chat/data/ChatCodes.as +++ b/src/as/com/threerings/crowd/chat/data/ChatCodes.as @@ -36,7 +36,10 @@ public class ChatCodes extends InvocationCodes public static const SUCCESS :String = "success"; /** The message identifier for a chat notification message. */ - public static const CHAT_NOTIFICATION :String = "chat"; + public static const CHAT_NOTIFICATION :String = "crowd.chat"; + + /** The message identifier for a chat channel notification message. */ + public static const CHAT_CHANENL_NOTIFICATION :String = "crowd.chat.channel"; /** The access control identifier for normal chat privileges. */ public static const CHAT_ACCESS :Permission = new Permission(); diff --git a/src/java/com/threerings/crowd/chat/client/ChannelSpeakService.java b/src/java/com/threerings/crowd/chat/client/ChannelSpeakService.java new file mode 100644 index 000000000..6da50ba0f --- /dev/null +++ b/src/java/com/threerings/crowd/chat/client/ChannelSpeakService.java @@ -0,0 +1,38 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.crowd.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); +} diff --git a/src/java/com/threerings/crowd/chat/data/ChannelSpeakMarshaller.java b/src/java/com/threerings/crowd/chat/data/ChannelSpeakMarshaller.java new file mode 100644 index 000000000..4e596aa6a --- /dev/null +++ b/src/java/com/threerings/crowd/chat/data/ChannelSpeakMarshaller.java @@ -0,0 +1,48 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.crowd.chat.data; + +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. + */ +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) + }); + } +} diff --git a/src/java/com/threerings/crowd/chat/data/ChatChannel.java b/src/java/com/threerings/crowd/chat/data/ChatChannel.java new file mode 100644 index 000000000..ce072f5ca --- /dev/null +++ b/src/java/com/threerings/crowd/chat/data/ChatChannel.java @@ -0,0 +1,57 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.crowd.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, DSet.Entry +{ + // from interface Comparable + 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 (); +} diff --git a/src/java/com/threerings/crowd/chat/data/ChatCodes.java b/src/java/com/threerings/crowd/chat/data/ChatCodes.java index 48a170681..2f021285b 100644 --- a/src/java/com/threerings/crowd/chat/data/ChatCodes.java +++ b/src/java/com/threerings/crowd/chat/data/ChatCodes.java @@ -37,7 +37,10 @@ public interface ChatCodes extends InvocationCodes public static final String SUCCESS = "success"; /** The message identifier for a chat notification message. */ - public static final String CHAT_NOTIFICATION = "chat"; + 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(); diff --git a/src/java/com/threerings/crowd/chat/data/ChatMarshaller.java b/src/java/com/threerings/crowd/chat/data/ChatMarshaller.java index de4c7036a..949283a1d 100644 --- a/src/java/com/threerings/crowd/chat/data/ChatMarshaller.java +++ b/src/java/com/threerings/crowd/chat/data/ChatMarshaller.java @@ -39,7 +39,7 @@ public class ChatMarshaller extends InvocationMarshaller implements ChatService { /** - * Marshalls results to implementations of {@link TellListener}. + * Marshalls results to implementations of {@link ChatService.TellListener}. */ public static class TellMarshaller extends ListenerMarshaller implements TellListener diff --git a/src/java/com/threerings/crowd/chat/server/ChannelSpeakDispatcher.java b/src/java/com/threerings/crowd/chat/server/ChannelSpeakDispatcher.java new file mode 100644 index 000000000..c3487deb9 --- /dev/null +++ b/src/java/com/threerings/crowd/chat/server/ChannelSpeakDispatcher.java @@ -0,0 +1,67 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.crowd.chat.server; + +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}. + */ +public class ChannelSpeakDispatcher extends InvocationDispatcher +{ + /** + * Creates a dispatcher that may be registered to dispatch invocation + * service requests for the specified provider. + */ + public ChannelSpeakDispatcher (ChannelSpeakProvider provider) + { + this.provider = provider; + } + + @Override // documentation inherited + public ChannelSpeakMarshaller createMarshaller () + { + return new ChannelSpeakMarshaller(); + } + + @Override // documentation inherited + 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; + } + } +} diff --git a/src/java/com/threerings/crowd/chat/server/ChannelSpeakProvider.java b/src/java/com/threerings/crowd/chat/server/ChannelSpeakProvider.java new file mode 100644 index 000000000..59bea767d --- /dev/null +++ b/src/java/com/threerings/crowd/chat/server/ChannelSpeakProvider.java @@ -0,0 +1,38 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.crowd.chat.server; + +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}. + */ +public interface ChannelSpeakProvider extends InvocationProvider +{ + /** + * Handles a {@link ChannelSpeakService#speak} request. + */ + void speak (ClientObject caller, ChatChannel arg1, String arg2, byte arg3); +} diff --git a/src/java/com/threerings/crowd/chat/server/ChatChannelManager.java b/src/java/com/threerings/crowd/chat/server/ChatChannelManager.java new file mode 100644 index 000000000..06841a81c --- /dev/null +++ b/src/java/com/threerings/crowd/chat/server/ChatChannelManager.java @@ -0,0 +1,357 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.crowd.chat.server; + +import java.util.List; +import java.util.Map; + +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import com.samskivert.io.PersistenceException; +import com.samskivert.jdbc.RepositoryUnit; +import com.samskivert.util.ArrayIntSet; +import com.samskivert.util.IntSet; +import com.threerings.util.Name; + +import com.threerings.presents.annotation.AnyThread; +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.server.InvocationManager; +import com.threerings.presents.server.PresentsInvoker; + +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 com.threerings.crowd.chat.data.ChatChannel; +import com.threerings.crowd.chat.data.ChatCodes; +import com.threerings.crowd.chat.data.UserMessage; + +import static com.threerings.crowd.Log.log; + +/** + * Handles chat channel services. + */ +@Singleton +public abstract class ChatChannelManager + implements ChannelSpeakProvider +{ + /** + * 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)); + } + + // 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 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 (InvocationManager invmgr) + { + invmgr.registerDispatcher(new ChannelSpeakDispatcher(this), CrowdCodes.CROWD_GROUP); + } + + /** + * 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 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, IntSet parts) + { + // map the participants of our now resolved channel + _channels.put(channel, parts); + + // 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 IntSet parts = _channels.get(channel); + if (parts == 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 (!parts.contains(getBodyId(message.speaker))) { + log.warning("Dropping channel chat message from non-speaker", "channel", channel, + "message", message); + return; + } + + // 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 partMap = Maps.newHashMap(); + _peerMan.applyToNodes(new Function() { + public Void apply (NodeObject nodeobj) { + ArrayIntSet nodeBodyIds = new ArrayIntSet(); + for (ClientInfo info : nodeobj.clients) { + int bodyId = getBodyId(((CrowdClientInfo)info).visibleName); + if (parts.contains(bodyId)) { + nodeBodyIds.add(bodyId); + } + } + partMap.put(nodeobj.nodeName, nodeBodyIds.toIntArray()); + return null; + } + }); + + for (Map.Entry 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) + { + for (int bodyId : bodyIds) { + BodyObject bobj = getBodyObject(bodyId); + if (bobj != null && shouldDeliverSpeak(channel, message, bobj)) { + bobj.postMessage(ChatCodes.CHAT_CHANNEL_NOTIFICATION, channel, message); + } + } + } + + /** + * 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; + } + + /** + * 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 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 () { + IntSet partSet = _channelMan._channels.get(_channel); + if (partSet != null) { + if (_added) { + partSet.add(_bodyId); + } else { + partSet.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; + } + + /** 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 pending messages for all channels currently being resolved. */ + protected Map> _resolving = Maps.newHashMap(); + + /** A map of resolved channels to the body ids of their participants. */ + protected Map _channels = Maps.newHashMap(); + + @Inject protected CrowdPeerManager _peerMan; + @Inject protected PresentsInvoker _invoker; +} diff --git a/src/java/com/threerings/crowd/chat/server/ChatProvider.java b/src/java/com/threerings/crowd/chat/server/ChatProvider.java index 842fbe24b..776207f41 100644 --- a/src/java/com/threerings/crowd/chat/server/ChatProvider.java +++ b/src/java/com/threerings/crowd/chat/server/ChatProvider.java @@ -126,7 +126,7 @@ public class ChatProvider } /** - * Processes a request from a client to deliver a tell message to another client. + * Processes a {@link ChatService#tell} request. */ public void tell (ClientObject caller, Name target, String message, TellListener listener) throws InvocationException diff --git a/src/java/com/threerings/crowd/data/LocationMarshaller.java b/src/java/com/threerings/crowd/data/LocationMarshaller.java index 114bdb1b5..7f4f5d274 100644 --- a/src/java/com/threerings/crowd/data/LocationMarshaller.java +++ b/src/java/com/threerings/crowd/data/LocationMarshaller.java @@ -37,7 +37,7 @@ public class LocationMarshaller extends InvocationMarshaller implements LocationService { /** - * Marshalls results to implementations of {@link MoveListener}. + * Marshalls results to implementations of {@link LocationService.MoveListener}. */ public static class MoveMarshaller extends ListenerMarshaller implements MoveListener @@ -77,9 +77,7 @@ public class LocationMarshaller extends InvocationMarshaller // from interface LocationService public void leavePlace (Client arg1) { - sendRequest(arg1, LEAVE_PLACE, new Object[] { - - }); + sendRequest(arg1, LEAVE_PLACE, new Object[] {}); } /** The method id used to dispatch {@link #moveTo} requests. */ diff --git a/src/java/com/threerings/crowd/peer/data/CrowdNodeObject.java b/src/java/com/threerings/crowd/peer/data/CrowdNodeObject.java index b18229cad..2b43ab6a5 100644 --- a/src/java/com/threerings/crowd/peer/data/CrowdNodeObject.java +++ b/src/java/com/threerings/crowd/peer/data/CrowdNodeObject.java @@ -21,8 +21,11 @@ package com.threerings.crowd.peer.data; +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. */ @@ -31,11 +34,17 @@ public class CrowdNodeObject extends NodeObject // AUTO-GENERATED: FIELDS START /** The field name of the crowdPeerService field. */ public static final String CROWD_PEER_SERVICE = "crowdPeerService"; + + /** The field name of the hostedChannels field. */ + 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 hostedChannels = new DSet(); + // AUTO-GENERATED: METHODS START /** * Requests that the crowdPeerService field be set to the @@ -52,5 +61,52 @@ public class CrowdNodeObject extends NodeObject CROWD_PEER_SERVICE, value, ovalue); this.crowdPeerService = value; } + + /** + * Requests that the specified entry be added to the + * hostedChannels set. The set will not change until the event is + * actually propagated through the system. + */ + public void addToHostedChannels (ChatChannel elem) + { + requestEntryAdd(HOSTED_CHANNELS, hostedChannels, elem); + } + + /** + * Requests that the entry matching the supplied key be removed from + * the hostedChannels set. The set will not change until the + * event is actually propagated through the system. + */ + public void removeFromHostedChannels (Comparable key) + { + requestEntryRemove(HOSTED_CHANNELS, hostedChannels, key); + } + + /** + * Requests that the specified entry be updated in the + * hostedChannels set. The set will not change until the event is + * actually propagated through the system. + */ + public void updateHostedChannels (ChatChannel elem) + { + requestEntryUpdate(HOSTED_CHANNELS, hostedChannels, elem); + } + + /** + * Requests that the hostedChannels 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. + */ + public void setHostedChannels (DSet value) + { + requestAttributeChange(HOSTED_CHANNELS, value, this.hostedChannels); + DSet clone = (value == null) ? null : value.typedClone(); + this.hostedChannels = clone; + } // AUTO-GENERATED: METHODS END }