Much work on generalizing the notion of message "transport" and

allowing the use of annotations to customize transport for 
DObject fields and service/receiver methods.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5099 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Andrzej Kapolka
2008-05-16 01:47:33 +00:00
parent e279133868
commit ee74481cad
39 changed files with 864 additions and 123 deletions
@@ -0,0 +1,44 @@
//
// $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.presents.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import com.threerings.presents.net.Transport;
/**
* An annotation indicating the type of transport desired for a distributed object
* class, field, or method.
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface TransportHint
{
/** The type of transport to use. */
Transport.Type type () default Transport.Type.RELIABLE_ORDERED;
/** For ordered transport types, the channel to use. */
int channel () default 0;
}
@@ -61,6 +61,7 @@ import com.threerings.presents.net.DownstreamMessage;
import com.threerings.presents.net.LogoffRequest;
import com.threerings.presents.net.PingRequest;
import com.threerings.presents.net.PongResponse;
import com.threerings.presents.net.Transport;
import com.threerings.presents.net.UpstreamMessage;
import com.threerings.presents.util.DatagramSequencer;
@@ -163,7 +164,7 @@ public class BlockingCommunicator extends Communicator
public void postMessage (UpstreamMessage msg)
{
// post as datagram if hinted and possible
if (msg.datagram && _datagramWriter != null) {
if (!msg.getTransport().isReliable() && _datagramWriter != null) {
_dataq.append(msg);
} else {
_msgq.append(msg);
@@ -486,6 +487,7 @@ public class BlockingCommunicator extends Communicator
if (_datagramChannel.read(_buf) <= 0) {
throw new IOException("No datagram available to read.");
}
_buf.flip();
// decode through the sequencer
try {
@@ -493,7 +495,6 @@ public class BlockingCommunicator extends Communicator
if (msg == null) {
return null; // received out of order
}
msg.datagram = true;
if (debugLogMessages()) {
Log.info("DATAGRAM " + msg);
}
@@ -802,7 +803,7 @@ public class BlockingCommunicator extends Communicator
_datagramChannel.connect(new InetSocketAddress(_client.getHostname(), port));
for (int ii = 0; ii < DATAGRAM_ATTEMPTS_PER_PORT; ii++) {
// send a ping datagram
sendDatagram(new PingRequest());
sendDatagram(new PingRequest(Transport.UNRELIABLE_UNORDERED));
// wait for a response
int resp = _selector.select(DATAGRAM_RESPONSE_WAIT);
@@ -45,6 +45,8 @@ import com.threerings.presents.dobj.MessageEvent;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.net.Transport;
/**
* Handles the client side management of the invocation services.
*/
@@ -195,6 +197,16 @@ public class InvocationDirector
* invocation oid.
*/
public void sendRequest (int invOid, int invCode, int methodId, Object[] args)
{
sendRequest(invOid, invCode, methodId, args, Transport.DEFAULT);
}
/**
* Requests that the specified invocation request be packaged up and sent to the supplied
* invocation oid.
*/
public void sendRequest (
int invOid, int invCode, int methodId, Object[] args, Transport transport)
{
if (_clobj == null) {
Log.warning("Dropping invocation request on shutdown director [code=" + invCode +
@@ -218,7 +230,8 @@ public class InvocationDirector
}
// create an invocation request event
InvocationRequestEvent event = new InvocationRequestEvent(invOid, invCode, methodId, args);
InvocationRequestEvent event =
new InvocationRequestEvent(invOid, invCode, methodId, args, transport);
// because invocation directors are used on the server, we set the source oid here so that
// invocation requests are properly attributed to the right client object when created by
@@ -35,6 +35,8 @@ import com.threerings.presents.client.InvocationService;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.dobj.InvocationResponseEvent;
import com.threerings.presents.net.Transport;
/**
* Provides a base from which all invocation service marshallers extend. Handles functionality
* common to all marshallers.
@@ -69,6 +71,10 @@ public class InvocationMarshaller
* valid on the server. */
public transient DObjectManager omgr;
/** The transport mode through which the request was received. This is only valid on the
* server. */
public transient Transport transport;
/**
* Set an identifier for the invocation that this listener is used for, so we can report it
* if we are never responded-to.
@@ -92,7 +98,7 @@ public class InvocationMarshaller
{
_invId = null;
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, REQUEST_FAILED_RSPID, new Object[] { cause }));
callerOid, requestId, REQUEST_FAILED_RSPID, new Object[] { cause }, transport));
}
/**
@@ -130,7 +136,7 @@ public class InvocationMarshaller
}
/** On the server, the id of the invocation method. */
protected transient String _invId;
protected transient String _invId;
}
/**
@@ -146,8 +152,8 @@ public class InvocationMarshaller
public void requestProcessed ()
{
_invId = null;
omgr.postEvent(
new InvocationResponseEvent(callerOid, requestId, REQUEST_PROCESSED, null));
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, REQUEST_PROCESSED, null, transport));
}
// documentation inherited
@@ -178,7 +184,7 @@ public class InvocationMarshaller
{
_invId = null;
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, REQUEST_PROCESSED, new Object[] { result }));
callerOid, requestId, REQUEST_PROCESSED, new Object[] { result }, transport));
}
// documentation inherited
@@ -248,7 +254,16 @@ public class InvocationMarshaller
*/
protected void sendRequest (Client client, int methodId, Object[] args)
{
client.getInvocationDirector().sendRequest(_invOid, _invCode, methodId, args);
sendRequest(client, methodId, args, Transport.DEFAULT);
}
/**
* Called by generated invocation marshaller code; packages up and sends the specified
* invocation service request.
*/
protected void sendRequest (Client client, int methodId, Object[] args, Transport transport)
{
client.getInvocationDirector().sendRequest(_invOid, _invCode, methodId, args, transport);
}
/** The oid of the invocation object, where invocation service requests are sent. */
@@ -27,6 +27,8 @@ import java.util.HashMap;
import com.samskivert.util.StringUtil;
import com.threerings.presents.net.Transport;
/**
* An attribute changed event is dispatched when a single attribute of a distributed object has
* changed. It can also be constructed to request an attribute change on an object and posted to
@@ -162,7 +164,24 @@ public class AttributeChangedEvent extends NamedEvent
*/
protected AttributeChangedEvent (int targetOid, String name, Object value, Object oldValue)
{
super(targetOid, name);
this(targetOid, name, value, oldValue, Transport.DEFAULT);
}
/**
* Constructs a new attribute changed event on the specified target object with the supplied
* attribute name and value. <em>Do not construct these objects by hand.</em> Use {@link
* DObject#changeAttribute} instead.
*
* @param targetOid the object id of the object whose attribute has changed.
* @param name the name of the attribute (data member) that has changed.
* @param value the new value of the attribute (in the case of primitive types, the
* reflection-defined object-alternative is used).
* @param transport a hint as to the type of transport desired for the event.
*/
protected AttributeChangedEvent (
int targetOid, String name, Object value, Object oldValue, Transport transport)
{
super(targetOid, name, transport);
_value = value;
_oldValue = oldValue;
}
@@ -25,6 +25,8 @@ import java.util.List;
import com.threerings.util.StreamableArrayList;
import com.threerings.presents.net.Transport;
/**
* Used to manage and submit groups of events on a collection of
* distributed objects in a single transaction.
@@ -91,13 +93,18 @@ public class CompoundEvent extends DEvent
// then post this event onto the queue (but only if we actually
// accumulated some events)
switch (_events.size()) {
int size = _events.size();
switch (size) {
case 0: // nothing doing
break;
case 1: // no point in being compound
_omgr.postEvent(_events.get(0));
break;
default: // now we're talking
_transport = _events.get(0).getTransport();
for (int ii = 1; ii < size; ii++) {
_transport = _events.get(ii).getTransport().combine(_transport);
}
_omgr.postEvent(this);
break;
}
@@ -139,6 +146,15 @@ public class CompoundEvent extends DEvent
}
}
@Override // from DEvent
public void setTransport (Transport transport)
{
super.setTransport(transport);
for (int ii = 0, nn = _events.size(); ii < nn; ii++) {
_events.get(ii).setTransport(transport);
}
}
@Override // from DEvent
public boolean applyToObject (DObject target)
throws ObjectAccessException
@@ -23,6 +23,8 @@ package com.threerings.presents.dobj;
import com.threerings.io.Streamable;
import com.threerings.presents.net.Transport;
/**
* A distributed object event is dispatched whenever any modification is made to a distributed
* object. It can also be dispatched purely for notification purposes, without making any
@@ -45,8 +47,19 @@ public abstract class DEvent implements Streamable
* Constructs a new distributed object event that pertains to the specified distributed object.
*/
public DEvent (int targetOid)
{
this(targetOid, Transport.DEFAULT);
}
/**
* Constructs a new distributed object event that pertains to the specified distributed object.
*
* @param transport a hint as to the type of transport desired for the event.
*/
public DEvent (int targetOid, Transport transport)
{
_toid = targetOid;
_transport = transport;
}
/**
@@ -121,6 +134,24 @@ public abstract class DEvent implements Streamable
_soid = sourceOid;
}
/**
* Sets the transport parameters. For events received over the network, these indicate the
* mode of transport over which the event was received. When an event is sent over the
* network, these act as a hint as to the type of transport desired.
*/
public void setTransport (Transport transport)
{
_transport = transport;
}
/**
* Returns the transport parameters.
*/
public Transport getTransport ()
{
return _transport;
}
/**
* Events with associated listener interfaces should implement this function and notify the
* supplied listener if it implements their event listening interface. For example, the {@link
@@ -152,6 +183,9 @@ public abstract class DEvent implements Streamable
{
buf.append("targetOid=").append(_toid);
buf.append(", sourceOid=").append(_soid);
if (_transport != Transport.DEFAULT) {
buf.append(", transport=").append(_transport);
}
}
/** The oid of the object that is the target of this event. */
@@ -160,6 +194,9 @@ public abstract class DEvent implements Streamable
/** The oid of the client that generated this event. */
protected transient int _soid = -1;
/** The transport parameters. */
protected transient Transport _transport = Transport.DEFAULT;
/** Used to differentiate between null meaning we haven't initialized our old value and null
* being the actual old value. */
protected static final Object UNSET_OLD_VALUE = new Object();
@@ -37,6 +37,7 @@ import com.threerings.io.Streamable;
import com.threerings.util.TrackedObject;
import com.threerings.presents.Log;
import com.threerings.presents.net.Transport;
/**
* The distributed object forms the foundation of the Presents system. All information shared among
@@ -250,7 +251,7 @@ public class DObject
public void setAccessController (AccessController controller)
{
_controller = controller;
}
}
/**
* Returns a reference to the access controller in use by this object or null if none has been
@@ -524,11 +525,21 @@ public class DObject
}
/**
* Posts a message event on this distrubuted object.
* Posts a message event on this distributed object.
*/
public void postMessage (String name, Object... args)
{
postEvent(new MessageEvent(_oid, name, args));
postMessage(Transport.DEFAULT, name, args);
}
/**
* Posts a message event on this distributed object.
*
* @param transport a hint as to the type of transport desired for the message.
*/
public void postMessage (Transport transport, String name, Object... args)
{
postEvent(new MessageEvent(_oid, name, args, transport));
}
/**
@@ -683,7 +694,7 @@ public class DObject
_tevent.commit();
}
}
}
}
/**
* Returns true if this object is in the middle of a transaction or false if it is not.
@@ -714,7 +725,7 @@ public class DObject
} else {
_tevent.cancel();
}
}
}
/**
* Removes this object from participation in any transaction in which it might be taking part.
@@ -736,19 +747,37 @@ public class DObject
* Called by derived instances when an attribute setter method was called.
*/
protected void requestAttributeChange (String name, Object value, Object oldValue)
{
requestAttributeChange(name, value, oldValue, Transport.DEFAULT);
}
/**
* Called by derived instances when an attribute setter method was called.
*/
protected void requestAttributeChange (
String name, Object value, Object oldValue, Transport transport)
{
// dispatch an attribute changed event
postEvent(new AttributeChangedEvent(_oid, name, value, oldValue));
postEvent(new AttributeChangedEvent(_oid, name, value, oldValue, transport));
}
/**
* Called by derived instances when an element updater method was called.
*/
protected void requestElementUpdate (String name, int index, Object value, Object oldValue)
{
requestElementUpdate(name, index, value, oldValue, Transport.DEFAULT);
}
/**
* Called by derived instances when an element updater method was called.
*/
protected void requestElementUpdate (
String name, int index, Object value, Object oldValue, Transport transport)
{
// dispatch an attribute changed event
postEvent(new ElementUpdatedEvent(
_oid, name, value, oldValue, index));
_oid, name, value, oldValue, index, transport));
}
/**
@@ -810,6 +839,15 @@ public class DObject
* Calls by derived instances when a set updater method was called.
*/
protected <T extends DSet.Entry> void requestEntryUpdate (String name, DSet<T> set, T entry)
{
requestEntryUpdate(name, set, entry, Transport.DEFAULT);
}
/**
* Calls by derived instances when a set updater method was called.
*/
protected <T extends DSet.Entry> void requestEntryUpdate (
String name, DSet<T> set, T entry, Transport transport)
{
// if we're on the authoritative server, we update the set immediately
T oldEntry = null;
@@ -820,7 +858,7 @@ public class DObject
}
}
// dispatch an entry updated event
postEvent(new EntryUpdatedEvent<T>(_oid, name, entry, oldEntry));
postEvent(new EntryUpdatedEvent<T>(_oid, name, entry, oldEntry, transport));
}
/**
@@ -26,6 +26,8 @@ import java.lang.reflect.Field;
import com.samskivert.util.StringUtil;
import com.threerings.presents.net.Transport;
/**
* An element updated event is dispatched when an element of an array field in a distributed object
* is updated. It can also be constructed to request the update of an entry and posted to the
@@ -49,7 +51,26 @@ public class ElementUpdatedEvent extends NamedEvent
*/
public ElementUpdatedEvent (int targetOid, String name, Object value, Object ovalue, int index)
{
super(targetOid, name);
this(targetOid, name, value, ovalue, index, Transport.DEFAULT);
}
/**
* Constructs a new element updated event on the specified target object with the supplied
* attribute name, element and index.
*
* @param targetOid the object id of the object whose attribute has changed.
* @param name the name of the attribute (data member) for which an element has changed.
* @param value the new value of the element (in the case of primitive types, the
* reflection-defined object-alternative is used).
* @param oldValue the previous value of the element (in the case of primitive types, the
* reflection-defined object-alternative is used).
* @param index the index in the array of the updated element.
* @param transport a hint as to the type of transport desired for the event.
*/
public ElementUpdatedEvent (
int targetOid, String name, Object value, Object ovalue, int index, Transport transport)
{
super(targetOid, name, transport);
_value = value;
_oldValue = ovalue;
_index = index;
@@ -95,7 +95,8 @@ public class EntryAddedEvent<T extends DSet.Entry> extends NamedEvent
protected void notifyListener (Object listener)
{
if (listener instanceof SetListener) {
((SetListener)listener).entryAdded(this);
@SuppressWarnings("unchecked") SetListener<T> setlist = (SetListener<T>)listener;
setlist.entryAdded(this);
}
}
@@ -99,7 +99,8 @@ public class EntryRemovedEvent<T extends DSet.Entry> extends NamedEvent
protected void notifyListener (Object listener)
{
if (listener instanceof SetListener) {
((SetListener)listener).entryRemoved(this);
@SuppressWarnings("unchecked") SetListener<T> setlist = (SetListener<T>)listener;
setlist.entryRemoved(this);
}
}
@@ -25,6 +25,8 @@ import com.samskivert.util.StringUtil;
import com.threerings.presents.Log;
import com.threerings.presents.net.Transport;
/**
* An entry updated event is dispatched when an entry of a {@link DSet} is updated. It can also be
* constructed to request the update of an entry and posted to the dobjmgr.
@@ -44,7 +46,22 @@ public class EntryUpdatedEvent<T extends DSet.Entry> extends NamedEvent
*/
public EntryUpdatedEvent (int targetOid, String name, T entry, T oldEntry)
{
super(targetOid, name);
this(targetOid, name, entry, oldEntry, Transport.DEFAULT);
}
/**
* Constructs a new entry updated event on the specified target object for the specified set
* name and with the supplied updated entry.
*
* @param targetOid the object id of the object to whose set we will add an entry.
* @param name the name of the attribute in which to update the specified entry.
* @param entry the entry to update.
* @param oldEntry the previous value of the entry.
* @param transport a hint as to the type of transport desired for the event.
*/
public EntryUpdatedEvent (int targetOid, String name, T entry, T oldEntry, Transport transport)
{
super(targetOid, name, transport);
_entry = entry;
_oldEntry = oldEntry;
}
@@ -101,7 +118,8 @@ public class EntryUpdatedEvent<T extends DSet.Entry> extends NamedEvent
protected void notifyListener (Object listener)
{
if (listener instanceof SetListener) {
((SetListener)listener).entryUpdated(this);
@SuppressWarnings("unchecked") SetListener<T> setlist = (SetListener<T>)listener;
setlist.entryUpdated(this);
}
}
@@ -23,6 +23,8 @@ package com.threerings.presents.dobj;
import com.samskivert.util.StringUtil;
import com.threerings.presents.net.Transport;
/**
* Used to dispatch an invocation notification from the server to a
* client.
@@ -47,7 +49,27 @@ public class InvocationNotificationEvent extends DEvent
public InvocationNotificationEvent (
int targetOid, short receiverId, int methodId, Object[] args)
{
super(targetOid);
this(targetOid, receiverId, methodId, args, Transport.DEFAULT);
}
/**
* Constructs a new invocation notification event on the specified
* target object with the supplied receiver id, method id and
* arguments.
*
* @param targetOid the object id of the object on which the event is
* to be dispatched.
* @param receiverId identifies the receiver to which this notification
* is being dispatched.
* @param methodId the id of the method to be invoked.
* @param args the arguments for the method. This array should contain
* only values of valid distributed object types.
* @param transport a hint as to the type of transport desired for the event.
*/
public InvocationNotificationEvent (
int targetOid, short receiverId, int methodId, Object[] args, Transport transport)
{
super(targetOid, transport);
_receiverId = receiverId;
_methodId = (byte)methodId;
_args = args;
@@ -23,6 +23,8 @@ package com.threerings.presents.dobj;
import com.samskivert.util.StringUtil;
import com.threerings.presents.net.Transport;
/**
* Used to dispatch an invocation request from the client to the server.
*
@@ -44,7 +46,25 @@ public class InvocationRequestEvent extends DEvent
public InvocationRequestEvent (
int targetOid, int invCode, int methodId, Object[] args)
{
super(targetOid);
this(targetOid, invCode, methodId, args, Transport.DEFAULT);
}
/**
* Constructs a new invocation request event on the specified target
* object with the supplied code, method and arguments.
*
* @param targetOid the object id of the object on which the event is
* to be dispatched.
* @param invCode the invocation provider identification code.
* @param methodId the id of the method to be invoked.
* @param args the arguments for the method. This array should contain
* only values of valid distributed object types.
* @param transport a hint as to the type of transport desired for the event.
*/
public InvocationRequestEvent (
int targetOid, int invCode, int methodId, Object[] args, Transport transport)
{
super(targetOid, transport);
_invCode = invCode;
_methodId = (byte)methodId;
_args = args;
@@ -23,6 +23,8 @@ package com.threerings.presents.dobj;
import com.samskivert.util.StringUtil;
import com.threerings.presents.net.Transport;
/**
* Used to dispatch an invocation response from the server to the client.
*
@@ -44,7 +46,25 @@ public class InvocationResponseEvent extends DEvent
public InvocationResponseEvent (
int targetOid, int requestId, int methodId, Object[] args)
{
super(targetOid);
this(targetOid, requestId, methodId, args, Transport.DEFAULT);
}
/**
* Constructs a new invocation response event on the specified target
* object with the supplied code, method and arguments.
*
* @param targetOid the object id of the object on which the event is
* to be dispatched.
* @param requestId the id of the request to which we are responding.
* @param methodId the method to be invoked.
* @param args the arguments for the method. This array should contain
* only values of valid distributed object types.
* @param transport a hint as to the type of transport desired for the event.
*/
public InvocationResponseEvent (
int targetOid, int requestId, int methodId, Object[] args, Transport transport)
{
super(targetOid, transport);
_requestId = (short)requestId;
_methodId = (byte)methodId;
_args = args;
@@ -23,6 +23,8 @@ package com.threerings.presents.dobj;
import com.samskivert.util.StringUtil;
import com.threerings.presents.net.Transport;
/**
* A message event is used to dispatch a message to all subscribers of a
* distributed object without actually changing any of the fields of the
@@ -46,7 +48,23 @@ public class MessageEvent extends NamedEvent
*/
public MessageEvent (int targetOid, String name, Object[] args)
{
super(targetOid, name);
this(targetOid, name, args, Transport.DEFAULT);
}
/**
* Constructs a new message event on the specified target object with
* the supplied name and arguments.
*
* @param targetOid the object id of the object whose attribute has
* changed.
* @param name the name of the message event.
* @param args the arguments for this message. This array should
* contain only values of valid distributed object types.
* @param transport a hint as to the type of transport desired for the event.
*/
public MessageEvent (int targetOid, String name, Object[] args, Transport transport)
{
super(targetOid, name, transport);
_args = args;
}
@@ -21,6 +21,8 @@
package com.threerings.presents.dobj;
import com.threerings.presents.net.Transport;
/**
* A common parent class for all events that are associated with a name
* (in some cases a field name, in other cases just an identifying name).
@@ -36,7 +38,20 @@ public abstract class NamedEvent extends DEvent
*/
public NamedEvent (int targetOid, String name)
{
super(targetOid);
this(targetOid, name, Transport.DEFAULT);
}
/**
* Constructs a new named event for the specified target object with
* the supplied attribute name.
*
* @param targetOid the object id of the object in question.
* @param name the name associated with this event.
* @param transport a hint as to the type of transport desired for the event.
*/
public NamedEvent (int targetOid, String name, Transport transport)
{
super(targetOid, transport);
_name = name;
}
@@ -21,14 +21,12 @@
package com.threerings.presents.net;
import com.threerings.io.SimpleStreamableObject;
/**
* This class encapsulates a message in the distributed object protocol
* that flows from the server to the client. Downstream messages include
* object subscription, event forwarding and session management.
*/
public abstract class DownstreamMessage extends SimpleStreamableObject
public abstract class DownstreamMessage extends Message
{
/**
* The message id of the upstream message with which this downstream
@@ -37,12 +35,6 @@ public abstract class DownstreamMessage extends SimpleStreamableObject
*/
public short messageId = -1;
/**
* When sending, this acts as a hint that the message may be transmitted as a datagram.
* When receiving, it indicates that the message was received as a datagram.
*/
public transient boolean datagram;
/**
* Generates a string representation of this instance.
*/
@@ -46,6 +46,19 @@ public class EventNotification extends DownstreamMessage
return _event;
}
@Override // documentation inherited
public void setTransport (Transport transport)
{
// the event handles the transport
_event.setTransport(transport);
}
@Override // documentation inherited
public Transport getTransport ()
{
return _event.getTransport();
}
public String toString ()
{
return "[type=EVT, evt=" + _event + "]";
@@ -49,6 +49,19 @@ public class ForwardEventRequest extends UpstreamMessage
return _event;
}
@Override // documentation inherited
public void setTransport (Transport transport)
{
// the event handles the transport
_event.setTransport(transport);
}
@Override // documentation inherited
public Transport getTransport ()
{
return _event.getTransport();
}
public String toString ()
{
return "[type=FWD, evt=" + _event + "]";
@@ -0,0 +1,50 @@
//
// $Id: DownstreamMessage.java 5090 2008-05-13 21:12:39Z andrzej $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 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.presents.net;
import com.threerings.io.SimpleStreamableObject;
/**
* The superclass of {@link UpstreamMessage} and {@link DownstreamMessage}.
*/
public abstract class Message extends SimpleStreamableObject
{
/**
* Sets the message transport parameters. For messages received over the network, these
* describe the mode of transport over which the message was received. When sending messages,
* they act as a hint as to the type of transport desired. Calling this method may have no
* effect, depending on whether non-default modes of transport are supported for this message
* type.
*/
public void setTransport (Transport transport)
{
// no-op by default
}
/**
* Returns the message transport parameters.
*/
public Transport getTransport ()
{
return Transport.DEFAULT;
}
}
@@ -41,6 +41,14 @@ public class PingRequest extends UpstreamMessage
super();
}
/**
* Creates a new ping request using the specified transport.
*/
public PingRequest (Transport transport)
{
_transport = transport;
}
/**
* Returns a timestamp that was obtained when this packet was encoded
* by the low-level networking code.
@@ -85,9 +93,21 @@ public class PingRequest extends UpstreamMessage
in.defaultReadObject();
}
@Override // documentation inherited
public void setTransport (Transport transport)
{
_transport = transport;
}
@Override // documentation inherited
public Transport getTransport ()
{
return _transport;
}
public String toString ()
{
return "[type=PING, msgid=" + messageId + "]";
return "[type=PING, msgid=" + messageId + ", transport=" + _transport + "]";
}
/** A time stamp obtained when we serialize this object. */
@@ -97,4 +117,7 @@ public class PingRequest extends UpstreamMessage
* is to get a timestamp as close as possible to when the packet was
* received on the network). */
protected transient long _unpackStamp;
/** The transport parameters. */
protected transient Transport _transport = Transport.DEFAULT;
}
@@ -42,11 +42,12 @@ public class PongResponse extends DownstreamMessage
* Constructs a pong response which will use the supplied ping time to
* establish the end-to-end processing delay introduced by the server.
*/
public PongResponse (long pingStamp)
public PongResponse (long pingStamp, Transport transport)
{
// save this for when we are serialized in preparation for
// delivery over the network
_pingStamp = pingStamp;
_transport = transport;
}
/**
@@ -112,9 +113,21 @@ public class PongResponse extends DownstreamMessage
in.defaultReadObject();
}
@Override // documentation inherited
public void setTransport (Transport transport)
{
_transport = transport;
}
@Override // documentation inherited
public Transport getTransport ()
{
return _transport;
}
public String toString ()
{
return "[type=PONG, msgid=" + messageId + "]";
return "[type=PONG, msgid=" + messageId + ", transport=" + _transport + "]";
}
/** The ping unpack stamp provided at construct time to this pong
@@ -135,4 +148,7 @@ public class PongResponse extends DownstreamMessage
* is to get a timestamp as close as possible to when the packet was
* received on the network). */
protected transient long _unpackStamp;
/** The transport parameters. */
protected transient Transport _transport = Transport.DEFAULT;
}
@@ -0,0 +1,256 @@
//
// $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.presents.net;
import java.util.EnumMap;
import com.samskivert.util.HashIntMap;
/**
* Message transport parameters. These include the type of transport and the channel (used to
* define independent streams for ordered transport), and may eventually include message
* priority, etc.
*/
public class Transport
{
/**
* The available types of transport.
*/
public enum Type
{
/**
* Messages are neither guaranteed to arrive nor, if they do arrive, to arrive in order
* and without duplicates. Functionally identical to UDP.
*/
UNRELIABLE_UNORDERED(false, false) {
public Type combine (Type other) {
return other; // we defer to all
}
},
/**
* Messages are not guaranteed to arrive, but if they do arrive, then they will arrive in
* order and without duplicates. In other words, out-of-order packets will be dropped.
*/
UNRELIABLE_ORDERED(false, true) {
public Type combine (Type other) {
return other.isReliable() ? RELIABLE_ORDERED : this;
}
},
/**
* Messages are guaranteed to arrive eventually, but they are not guaranteed to arrive in
* order.
*/
RELIABLE_UNORDERED(true, false) {
public Type combine (Type other) {
return other.isOrdered() ? RELIABLE_ORDERED : this;
}
},
/**
* Messages are guaranteed to arrive, and will arrive in the order in which they are sent.
* Functionally identical to TCP.
*/
RELIABLE_ORDERED(true, true) {
public Type combine (Type other) {
return this; // we override all
}
};
/**
* Checks whether this transport type guarantees that messages will be delivered.
*/
public boolean isReliable ()
{
return _reliable;
}
/**
* Checks whether this transport type guarantees that messages will be received in the
* order in which they were sent, if they are received at all.
*/
public boolean isOrdered ()
{
return _ordered;
}
/**
* Returns a transport type that combines the requirements of this type with those of the
* specified other type.
*/
public abstract Type combine (Type other);
Type (boolean reliable, boolean ordered)
{
_reliable = reliable;
_ordered = ordered;
}
protected boolean _reliable, _ordered;
}
/** The unreliable/unordered mode of transport. */
public static final Transport UNRELIABLE_UNORDERED = getInstance(Type.UNRELIABLE_UNORDERED);
/** The unreliable/ordered mode on the default channel. */
public static final Transport UNRELIABLE_ORDERED = getInstance(Type.UNRELIABLE_ORDERED, 0);
/** The reliable/unordered mode. */
public static final Transport RELIABLE_UNORDERED = getInstance(Type.RELIABLE_UNORDERED);
/** The reliable/ordered mode on the default channel. */
public static final Transport RELIABLE_ORDERED = getInstance(Type.RELIABLE_ORDERED, 0);
/** The default mode of transport. */
public static final Transport DEFAULT = RELIABLE_ORDERED;
/**
* Returns the shared instance with the specified parameters.
*/
public static Transport getInstance (Type type)
{
return getInstance(type, 0);
}
/**
* Returns the shared instance with the specified parameters.
*/
public static Transport getInstance (Type type, int channel)
{
// were there more parameters in transport objects, it would be better to have a single map
// of instances and use Transport objects as keys (as in examples of the flyweight
// pattern). however, doing it this way avoids the need to create a new object on lookup
if (_unordered == null) {
_unordered = new EnumMap<Type, Transport>(Type.class);
_ordered = new EnumMap<Type, HashIntMap<Transport>>(Type.class);
}
// for unordered transport, we map on the type alone
if (!type.isOrdered()) {
Transport instance = _unordered.get(type);
if (instance == null) {
_unordered.put(type, instance = new Transport(type));
}
return instance;
}
// for ordered transport, we map on the type and channel
HashIntMap<Transport> instances = _ordered.get(type);
if (instances == null) {
_ordered.put(type, instances = new HashIntMap<Transport>());
}
Transport instance = instances.get(channel);
if (instance == null) {
instances.put(channel, instance = new Transport(type, channel));
}
return instance;
}
/**
* Returns the type of transport.
*/
public Type getType ()
{
return _type;
}
/**
* Returns the transport channel.
*/
public int getChannel ()
{
return _channel;
}
/**
* Checks whether this transport guarantees that messages will be delivered.
*/
public boolean isReliable ()
{
return _type.isReliable();
}
/**
* Checks whether this transport guarantees that messages will be received in the order in
* which they were sent, if they are received at all.
*/
public boolean isOrdered ()
{
return _type.isOrdered();
}
/**
* Returns a transport that satisfies the requirements of this and the specified other
* transport.
*/
public Transport combine (Transport other)
{
// if the channels are different, we fall back to the default channel
return getInstance(
_type.combine(other._type),
(_channel == other._channel) ? _channel : 0);
}
@Override // documentation inherited
public int hashCode ()
{
return 31*_type.hashCode() + _channel;
}
@Override // documentation inherited
public boolean equals (Object other)
{
Transport otrans;
return other instanceof Transport && (otrans = (Transport)other)._type == _type &&
otrans._channel == _channel;
}
@Override // documentation inherited
public String toString ()
{
return "[type=" + _type + ", channel=" + _channel + "]";
}
protected Transport (Type type)
{
this(type, 0);
}
protected Transport (Type type, int channel)
{
_type = type;
_channel = channel;
}
/** The type of transport. */
protected Type _type;
/** The transport channel. */
protected int _channel;
/** Unordered instances mapped by type. */
protected static EnumMap<Type, Transport> _unordered;
/** Ordered instances mapped by type and channel. */
protected static EnumMap<Type, HashIntMap<Transport>> _ordered;
}
@@ -21,14 +21,12 @@
package com.threerings.presents.net;
import com.threerings.io.SimpleStreamableObject;
/**
* This class encapsulates a message in the distributed object protocol
* that flows from the client to the server. Upstream messages include
* object subscription, event forwarding and session management.
*/
public abstract class UpstreamMessage extends SimpleStreamableObject
public abstract class UpstreamMessage extends Message
{
/**
* This is a unique (within the context of a reasonable period of
@@ -41,12 +39,6 @@ public abstract class UpstreamMessage extends SimpleStreamableObject
/** A timestamp indicating when this upstream message was received. */
public transient long received;
/**
* When sending, this acts as a hint that the message may be transmitted as a datagram.
* When receiving, it indicates that the message was received as a datagram.
*/
public transient boolean datagram;
/**
* Each upstream message derived class must provide a zero argument
* constructor so that it can be unserialized when read from the
@@ -42,6 +42,8 @@ import com.threerings.presents.dobj.InvocationRequestEvent;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.net.Transport;
/**
* The invocation services provide client to server invocations (service requests) and server to
* client invocations (responses and notifications). Via this mechanism, the client can make
@@ -183,7 +185,7 @@ public class InvocationManager
/**
* Get the class that is being used to dispatch the specified
* invocation code, for informational purposes.
*
*
* @return the Class, or null if no dispatcher is registered with
* the specified code.
*/
@@ -201,7 +203,7 @@ public class InvocationManager
if (event instanceof InvocationRequestEvent) {
InvocationRequestEvent ire = (InvocationRequestEvent)event;
dispatchRequest(ire.getSourceOid(), ire.getInvCode(),
ire.getMethodId(), ire.getArgs());
ire.getMethodId(), ire.getArgs(), ire.getTransport());
}
}
@@ -211,7 +213,7 @@ public class InvocationManager
* registered invocation dispatcher.
*/
protected void dispatchRequest (
int clientOid, int invCode, int methodId, Object[] args)
int clientOid, int invCode, int methodId, Object[] args, Transport transport)
{
// make sure the client is still around
ClientObject source = (ClientObject)_omgr.getObject(clientOid);
@@ -243,6 +245,7 @@ public class InvocationManager
if (arg instanceof ListenerMarshaller) {
ListenerMarshaller list = (ListenerMarshaller)arg;
list.omgr = _omgr;
list.transport = transport;
// keep track of the listener we'll inform if anything
// goes horribly awry
if (rlist == null) {
@@ -28,6 +28,8 @@ import com.threerings.presents.client.InvocationReceiver.Registration;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.InvocationNotificationEvent;
import com.threerings.presents.net.Transport;
/**
* Provides basic functionality used by all invocation sender classes.
*/
@@ -39,6 +41,16 @@ public abstract class InvocationSender
*/
public static void sendNotification (
ClientObject target, String receiverCode, int methodId, Object[] args)
{
sendNotification(target, receiverCode, methodId, args, Transport.DEFAULT);
}
/**
* Requests that the specified invocation notification be packaged up and sent to the supplied
* target client.
*/
public static void sendNotification (
ClientObject target, String receiverCode, int methodId, Object[] args, Transport transport)
{
// convert the receiver hash id into the code used on this
// specific client
@@ -55,7 +67,8 @@ public abstract class InvocationSender
// create and dispatch an invocation notification event
target.postEvent(
new InvocationNotificationEvent(target.getOid(), rreg.receiverId, methodId, args));
new InvocationNotificationEvent(
target.getOid(), rreg.receiverId, methodId, args, transport));
}
}
}
@@ -267,7 +267,7 @@ public class PresentsClient
* does it in the existing client object. Care must be taken to ensure that any client or
* server code either doesn't map things based on username before this call, or that it's
* updated to reflect the change.
*
*
* @return - true if the client was successfully renamed, false otherwise
*/
public boolean updateUsername (Name username)
@@ -978,11 +978,9 @@ public class PresentsClient
{
public void dispatch (PresentsClient client, UpstreamMessage msg)
{
// send a pong response
// send a pong response using the transport with which the message was received
PingRequest req = (PingRequest)msg;
PongResponse resp = new PongResponse(req.getUnpackStamp());
resp.datagram = req.datagram;
client.safePostMessage(resp);
client.safePostMessage(new PongResponse(req.getUnpackStamp(), req.getTransport()));
}
}
@@ -376,7 +376,6 @@ public abstract class Connection implements NetEventHandler
return; // received out of order
}
msg.received = when;
msg.datagram = true;
_handler.handleMessage(msg);
} catch (ClassNotFoundException cnfe) {
@@ -850,7 +850,7 @@ public class ConnectionManager extends LoopingThread
try {
// send it as a datagram if hinted and possible
if (msg.datagram && conn.getDatagramAddress() != null) {
if (!msg.getTransport().isReliable() && conn.getDatagramAddress() != null) {
postDatagram(conn, msg);
return;
}
@@ -46,6 +46,7 @@ import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Task;
@@ -61,6 +62,9 @@ import com.samskivert.util.SortableArrayList;
import com.samskivert.util.StringUtil;
import com.samskivert.velocity.VelocityUtil;
import com.threerings.presents.annotation.TransportHint;
import com.threerings.presents.net.Transport;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DSet;
import com.threerings.presents.dobj.OidList;
@@ -85,6 +89,11 @@ public class GenDObjectTask extends Task
{
_cloader = ClasspathUtils.getClassLoaderForPath(
getProject(), pathref);
// set the parent of the classloader to be the classloader used to load this task,
// rather than the classloader used to load Ant, so that we have access to Narya
// classes like TransportHint
((AntClassLoader)_cloader).setParent(getClass().getClassLoader());
}
/** Performs the actual work of the task. */
@@ -107,6 +116,7 @@ public class GenDObjectTask extends Task
_doclass = _cloader.loadClass(DObject.class.getName());
_dsclass = _cloader.loadClass(DSet.class.getName());
_olclass = _cloader.loadClass(OidList.class.getName());
} catch (Exception e) {
throw new BuildException("Can't resolve InvocationListener", e);
}
@@ -198,6 +208,18 @@ public class GenDObjectTask extends Task
ctx.put("capfield", StringUtil.unStudlyName(fname).toUpperCase());
ctx.put("upfield", StringUtils.capitalize(fname));
// determine the type of transport
TransportHint hint = f.getAnnotation(TransportHint.class);
String transport;
if (hint == null) {
transport = "DEFAULT";
} else {
transport = "getInstance(\n" +
" com.threerings.presents.net.Transport.Type." +
hint.type().name() + ", " + hint.channel() + ")";
}
ctx.put("transport", "com.threerings.presents.net.Transport." + transport);
// if this field is an array, we need its component types
if (ftype.isArray()) {
Class<?> etype = ftype.getComponentType();
@@ -38,6 +38,7 @@ import com.samskivert.util.StringUtil;
import com.threerings.presents.client.InvocationDecoder;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.net.Transport;
import com.threerings.presents.server.InvocationSender;
/**
@@ -85,7 +86,7 @@ public class GenReceiverTask extends InvocationTask
methods.add(new ServiceMethod(m, imports));
}
methods.sort();
// get rid of primitives and java.lang types
imports.removeGlobals();
@@ -106,6 +107,7 @@ public class GenReceiverTask extends InvocationTask
CollectionUtil.addAll(implist, imports);
checkedAdd(implist, ClientObject.class.getName());
checkedAdd(implist, InvocationSender.class.getName());
checkedAdd(implist, Transport.class.getName());
String dname = StringUtil.replace(rname, "Receiver", "Decoder");
checkedAdd(implist, rpackage + "." + dname);
implist.sort();
@@ -39,6 +39,7 @@ import com.threerings.presents.client.InvocationService;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.InvocationResponseEvent;
import com.threerings.presents.net.Transport;
import com.threerings.presents.server.InvocationDispatcher;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationProvider;
@@ -46,11 +47,11 @@ import com.threerings.presents.server.InvocationProvider;
/**
* An Ant task for generating invocation service marshalling and
* unmarshalling classes.
* TODO: when generating the imports for exported action script files, there are just enough
* TODO: when generating the imports for exported action script files, there are just enough
* conversions of primitive types (e.g. float -> Number), array types (e.g. int[] -> TypedArray)
* and three rings utility types (e.g. float -> Float) to make the existing serivces work. It
* should be possible to create a complete list of these conversions so that future services
* can be generated without problems.
* can be generated without problems.
*/
public class GenServiceTask extends InvocationTask
{
@@ -61,7 +62,7 @@ public class GenServiceTask extends InvocationTask
public ComparableArrayList<ServiceMethod> methods =
new ComparableArrayList<ServiceMethod>();
/** Contains all imports required for the parameters of the methods in this listener. */
public ImportSet imports = new ImportSet();
@@ -77,12 +78,12 @@ public class GenServiceTask extends InvocationTask
continue;
}
if (_verbose) {
System.out.println("Adding " + m + ", imports are " +
System.out.println("Adding " + m + ", imports are " +
StringUtil.toString(imports));
}
methods.add(new ServiceMethod(m, imports));
if (_verbose) {
System.out.println("Added " + m + ", imports are " +
System.out.println("Added " + m + ", imports are " +
StringUtil.toString(imports));
}
}
@@ -137,7 +138,7 @@ public class GenServiceTask extends InvocationTask
protected void processService (File source, Class service)
{
System.out.println("Processing " + service.getName() + "...");
// verify that the service class name is as we expect it to be
if (!service.getName().endsWith("Service")) {
System.err.println("Cannot process '" + service.getName() + "':");
@@ -167,7 +168,7 @@ public class GenServiceTask extends InvocationTask
sdesc.spackage, ".client", ".data");
// ----------- Part I - java marshaller
// start with all imports (service methods and listener methods)
ImportSet imports = sdesc.constructAllImports();
@@ -175,30 +176,31 @@ public class GenServiceTask extends InvocationTask
imports.add(sdesc.service);
imports.add(Client.class);
imports.add(InvocationMarshaller.class);
imports.add(Transport.class);
// if any listeners are to be present, they need the response event
if (sdesc.listeners.size() > 0) {
imports.add(InvocationResponseEvent.class);
}
// import classes contained in arrays
imports.translateClassArrays();
// get rid of java.lang stuff and primitives
imports.removeGlobals();
// get rid of all arrays (they are automatic in java)
imports.removeArrays();
// for each listener type, also import the corresponding marshaller
imports.duplicateAndMunge("*Listener",
imports.duplicateAndMunge("*Listener",
"Service", "Marshaller",
"Listener", "Marshaller",
".client.", ".data.");
// import the parent class of Foo$Bar
imports.swapInnerClassesForParents();
// remove imports in our own package
imports.removeSamePackage(mpackage);
@@ -234,15 +236,15 @@ public class GenServiceTask extends InvocationTask
// start with the service method imports
imports = sdesc.imports.clone();
// add some things that marshallers just need
imports.add(sdesc.service);
imports.add(Client.class);
imports.add(InvocationMarshaller.class);
// replace inner classes with action script equivalents
imports.translateInnerClasses();
// replace primitive types with OOO types (required for unboxing)
imports.replace("byte", "com.threerings.util.Byte");
imports.replace("int", "com.threerings.util.Integer");
@@ -268,7 +270,7 @@ public class GenServiceTask extends InvocationTask
// get rid of java.lang stuff and any remaining primitives
imports.removeGlobals();
if (imports.removeAll("[L*") > 0) {
imports.add("com.threerings.io.TypedArray");
}
@@ -304,20 +306,20 @@ public class GenServiceTask extends InvocationTask
// start imports with just those used by listener methods
imports = listener.imports.clone();
// always need the super class and the listener class
imports.add(imlm);
imports.add(listener.listener);
// replace '$' with '_' for action script naming convention
imports.translateInnerClasses();
// convert primitive java types to ooo util types
imports.replace("long", "com.threerings.util.Long");
// get rid of remaining primitives and java.lang types
imports.removeGlobals();
// remove imports in our own package
imports.removeSamePackage(mpackage);
@@ -359,7 +361,7 @@ public class GenServiceTask extends InvocationTask
// get rid of primitives and java.lang classes
imports.removeGlobals();
// get rid of remaining arrays
imports.removeArrays();
@@ -394,20 +396,20 @@ public class GenServiceTask extends InvocationTask
// start with just the imports needed by listener methods
imports = listener.imports.clone();
// add things needed by all listeners
imports.add(isil);
imports.add(listener.listener);
// change Foo$Bar to Foo_Bar
imports.translateInnerClasses();
// convert java primitive types to ooo util types
// convert java primitive types to ooo util types
imports.replace("long", "com.threerings.util.Long");
// get rid of remaining primitives and java.lang types
imports.removeGlobals();
// remove imports in our own package
imports.removeSamePackage(sdesc.spackage);
@@ -450,29 +452,29 @@ public class GenServiceTask extends InvocationTask
// swap Client for ClientObject
imports.add(ClientObject.class);
imports.remove(Client.class);
// add some classes required for all dispatchers
imports.add(InvocationMarshaller.class);
imports.add(InvocationDispatcher.class);
imports.add(InvocationException.class);
// import classes contained in arrays
imports.translateClassArrays();
// get rid of primitives and java.lang types
imports.removeGlobals();
// get rid of arrays
imports.removeArrays();
// import the Marshaller corresponding to the service
imports.addMunged(sdesc.service,
imports.addMunged(sdesc.service,
"Service", "Marshaller",
".client.", ".data.");
// import Foo instead of Foo$Bar
imports.swapInnerClassesForParents();
// remove imports in our own package
imports.removeSamePackage(dpackage);
@@ -504,7 +506,7 @@ public class GenServiceTask extends InvocationTask
if (_verbose) {
System.out.println("Generating provider");
}
String name = StringUtil.replace(sdesc.sname, "Service", "");
String mpackage = StringUtil.replace(
sdesc.spackage, ".client", ".server");
@@ -515,24 +517,24 @@ public class GenServiceTask extends InvocationTask
// swap Client for ClientObject
imports.add(ClientObject.class);
imports.remove(Client.class);
// import superclass
imports.add(InvocationProvider.class);
// any method that takes a listener may throw this
// any method that takes a listener may throw this
if (sdesc.hasAnyListenerArgs()) {
imports.add(InvocationException.class);
}
// import classes contained in arrays
imports.translateClassArrays();
// get rid of primitives and java.lang types
imports.removeGlobals();
// get rid of arrays
imports.removeArrays();
// import Foo instead of Foo$Bar
imports.swapInnerClassesForParents();
@@ -593,19 +595,19 @@ public class GenServiceTask extends InvocationTask
}
}
if (_verbose) {
System.out.println("Adding " + m + ", imports are " +
System.out.println("Adding " + m + ", imports are " +
StringUtil.toString(imports));
}
methods.add(new ServiceMethod(m, imports));
if (_verbose) {
System.out.println("Added " + m + ", imports are " +
System.out.println("Added " + m + ", imports are " +
StringUtil.toString(imports));
}
}
listeners.sort();
methods.sort();
}
/**
* Checks if any of the service method arguments are listener types.
*/
@@ -640,7 +642,7 @@ public class GenServiceTask extends InvocationTask
ComparableArrayList<ServiceListener> listeners =
new ComparableArrayList<ServiceListener>();
}
/** The path to our ActionScript source files. */
protected File _asroot;
@@ -34,6 +34,7 @@ import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Task;
@@ -46,6 +47,9 @@ import org.apache.velocity.app.VelocityEngine;
import com.samskivert.util.StringUtil;
import com.samskivert.velocity.VelocityUtil;
import com.threerings.presents.annotation.TransportHint;
import com.threerings.presents.net.Transport;
import com.threerings.presents.client.InvocationService.InvocationListener;
/**
@@ -90,7 +94,7 @@ public abstract class InvocationTask extends Task
}
}
/** Used to keep track of invocation service methods or listener
/** Used to keep track of invocation service methods or listener
* methods. */
public class ServiceMethod implements Comparable<ServiceMethod>
{
@@ -101,13 +105,13 @@ public abstract class InvocationTask extends Task
/**
* Creates a new service method.
* @param method the method to inspect
* @param method the method to inspect
* @param imports will be filled with the types required by the method
*/
public ServiceMethod (Method method, ImportSet imports)
{
this.method = method;
// we need to look through our arguments and note any needed
// imports in the supplied table
Class<?>[] args = method.getParameterTypes();
@@ -249,6 +253,16 @@ public abstract class InvocationTask extends Task
return buf.toString();
}
public String getTransport ()
{
TransportHint hint = method.getAnnotation(TransportHint.class);
if (hint == null) {
return "Transport.DEFAULT";
}
return "Transport.getInstance(Transport.Type." +
hint.type().name() + ", " + hint.channel() + ")";
}
protected String boxArgument (Class<?> clazz, int index)
{
if (_ilistener.isAssignableFrom(clazz)) {
@@ -306,6 +320,11 @@ public abstract class InvocationTask extends Task
{
_cloader = ClasspathUtils.getClassLoaderForPath(
getProject(), pathref);
// set the parent of the classloader to be the classloader used to load this task,
// rather than the classloader used to load Ant, so that we have access to Narya
// classes like TransportHint
((AntClassLoader)_cloader).setParent(getClass().getClassLoader());
}
/** Performs the actual work of the task. */
@@ -405,7 +424,7 @@ public abstract class InvocationTask extends Task
/** Show extra output if set. */
protected boolean _verbose;
/** Used to do our own classpath business. */
protected ClassLoader _cloader;
@@ -10,7 +10,8 @@
{
$type ovalue = this.$field;
requestAttributeChange(
$capfield, $wrapfield, $wrapofield);
$capfield, $wrapfield, $wrapofield,
$transport);
this.$field = $clonefield;
}
#if ($elemtype)
@@ -28,7 +29,8 @@
{
$elemtype ovalue = this.$field[index];
requestElementUpdate(
$capfield, index, $wrapelem, $wrapoelem);
$capfield, index, $wrapelem, $wrapoelem,
$transport);
this.$field[index] = value;
}
#end
@@ -25,7 +25,9 @@
*/
public void update$upfield ($etype elem)
{
requestEntryUpdate($capfield, $field, elem);
requestEntryUpdate(
$capfield, $field, elem,
$transport);
}
/**
@@ -32,7 +32,7 @@ public class ${name}Marshaller extends InvocationMarshaller
_invId = null;
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, $lm.code,
new Object[] { $lm.getWrappedArgList(false) }));
new Object[] { $lm.getWrappedArgList(false) }, transport));
}
#end
@@ -71,7 +71,7 @@ public class ${name}Marshaller extends InvocationMarshaller
#end
sendRequest(arg1, $m.code, new Object[] {
$m.getWrappedArgList(true)
});
}, $m.transport);
}
#end
}
@@ -20,7 +20,7 @@ public class ${name}Sender extends InvocationSender
{
sendNotification(
target, ${name}Decoder.RECEIVER_CODE, ${name}Decoder.$m.code,
new Object[] { $m.getWrappedArgList(false) });
new Object[] { $m.getWrappedArgList(false) }, $m.transport);
}
#end
@@ -30,6 +30,9 @@ import java.util.Set;
import com.threerings.io.UnreliableObjectInputStream;
import com.threerings.io.UnreliableObjectOutputStream;
import com.threerings.presents.net.Message;
import com.threerings.presents.net.Transport;
/**
* Used on both the client and the server to handle the encoding and decoding of sequenced
* datagrams.
@@ -48,7 +51,7 @@ public class DatagramSequencer
/**
* Writes a datagram to the underlying stream.
*/
public synchronized void writeDatagram (Object datagram)
public synchronized void writeDatagram (Message datagram)
throws IOException
{
// first write the sequence and acknowledge numbers
@@ -79,7 +82,7 @@ public class DatagramSequencer
* @return the contents of the datagram, or <code>null</code> if the datagram was received
* out-of-order.
*/
public synchronized Object readDatagram ()
public synchronized Message readDatagram ()
throws IOException, ClassNotFoundException
{
// read in the sequence number and determine if it's out-of-order
@@ -104,8 +107,10 @@ public class DatagramSequencer
}
_sendrecs.subList(0, remove).clear();
// read and return the contents of the datagram
return _uin.readObject();
// read the contents of the datagram, note the transport, and return
Message datagram = (Message)_uin.readObject();
datagram.setTransport(Transport.UNRELIABLE_ORDERED);
return datagram;
}
/**