diff --git a/src/java/com/threerings/presents/annotation/TransportHint.java b/src/java/com/threerings/presents/annotation/TransportHint.java new file mode 100644 index 000000000..96cd3142c --- /dev/null +++ b/src/java/com/threerings/presents/annotation/TransportHint.java @@ -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; +} diff --git a/src/java/com/threerings/presents/client/BlockingCommunicator.java b/src/java/com/threerings/presents/client/BlockingCommunicator.java index bc86f15b5..7e7e38895 100644 --- a/src/java/com/threerings/presents/client/BlockingCommunicator.java +++ b/src/java/com/threerings/presents/client/BlockingCommunicator.java @@ -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); diff --git a/src/java/com/threerings/presents/client/InvocationDirector.java b/src/java/com/threerings/presents/client/InvocationDirector.java index b5a8f6702..c182b4728 100644 --- a/src/java/com/threerings/presents/client/InvocationDirector.java +++ b/src/java/com/threerings/presents/client/InvocationDirector.java @@ -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 diff --git a/src/java/com/threerings/presents/data/InvocationMarshaller.java b/src/java/com/threerings/presents/data/InvocationMarshaller.java index 24eee66ff..033b361a4 100644 --- a/src/java/com/threerings/presents/data/InvocationMarshaller.java +++ b/src/java/com/threerings/presents/data/InvocationMarshaller.java @@ -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. */ diff --git a/src/java/com/threerings/presents/dobj/AttributeChangedEvent.java b/src/java/com/threerings/presents/dobj/AttributeChangedEvent.java index 2cd3524d7..c9c5e7a2d 100644 --- a/src/java/com/threerings/presents/dobj/AttributeChangedEvent.java +++ b/src/java/com/threerings/presents/dobj/AttributeChangedEvent.java @@ -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. Do not construct these objects by hand. 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; } diff --git a/src/java/com/threerings/presents/dobj/CompoundEvent.java b/src/java/com/threerings/presents/dobj/CompoundEvent.java index bae605109..b6125279c 100644 --- a/src/java/com/threerings/presents/dobj/CompoundEvent.java +++ b/src/java/com/threerings/presents/dobj/CompoundEvent.java @@ -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 diff --git a/src/java/com/threerings/presents/dobj/DEvent.java b/src/java/com/threerings/presents/dobj/DEvent.java index f75b169e4..55e661d67 100644 --- a/src/java/com/threerings/presents/dobj/DEvent.java +++ b/src/java/com/threerings/presents/dobj/DEvent.java @@ -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(); diff --git a/src/java/com/threerings/presents/dobj/DObject.java b/src/java/com/threerings/presents/dobj/DObject.java index af40acc38..073b1cd48 100644 --- a/src/java/com/threerings/presents/dobj/DObject.java +++ b/src/java/com/threerings/presents/dobj/DObject.java @@ -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 void requestEntryUpdate (String name, DSet set, T entry) + { + requestEntryUpdate(name, set, entry, Transport.DEFAULT); + } + + /** + * Calls by derived instances when a set updater method was called. + */ + protected void requestEntryUpdate ( + String name, DSet 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(_oid, name, entry, oldEntry)); + postEvent(new EntryUpdatedEvent(_oid, name, entry, oldEntry, transport)); } /** diff --git a/src/java/com/threerings/presents/dobj/ElementUpdatedEvent.java b/src/java/com/threerings/presents/dobj/ElementUpdatedEvent.java index 61b3820af..71a29d74e 100644 --- a/src/java/com/threerings/presents/dobj/ElementUpdatedEvent.java +++ b/src/java/com/threerings/presents/dobj/ElementUpdatedEvent.java @@ -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; diff --git a/src/java/com/threerings/presents/dobj/EntryAddedEvent.java b/src/java/com/threerings/presents/dobj/EntryAddedEvent.java index 8c069c97e..38675f8ae 100644 --- a/src/java/com/threerings/presents/dobj/EntryAddedEvent.java +++ b/src/java/com/threerings/presents/dobj/EntryAddedEvent.java @@ -95,7 +95,8 @@ public class EntryAddedEvent extends NamedEvent protected void notifyListener (Object listener) { if (listener instanceof SetListener) { - ((SetListener)listener).entryAdded(this); + @SuppressWarnings("unchecked") SetListener setlist = (SetListener)listener; + setlist.entryAdded(this); } } diff --git a/src/java/com/threerings/presents/dobj/EntryRemovedEvent.java b/src/java/com/threerings/presents/dobj/EntryRemovedEvent.java index 0e38ce0a3..9efe38f39 100644 --- a/src/java/com/threerings/presents/dobj/EntryRemovedEvent.java +++ b/src/java/com/threerings/presents/dobj/EntryRemovedEvent.java @@ -99,7 +99,8 @@ public class EntryRemovedEvent extends NamedEvent protected void notifyListener (Object listener) { if (listener instanceof SetListener) { - ((SetListener)listener).entryRemoved(this); + @SuppressWarnings("unchecked") SetListener setlist = (SetListener)listener; + setlist.entryRemoved(this); } } diff --git a/src/java/com/threerings/presents/dobj/EntryUpdatedEvent.java b/src/java/com/threerings/presents/dobj/EntryUpdatedEvent.java index 261d98121..ee59a3c19 100644 --- a/src/java/com/threerings/presents/dobj/EntryUpdatedEvent.java +++ b/src/java/com/threerings/presents/dobj/EntryUpdatedEvent.java @@ -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 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 extends NamedEvent protected void notifyListener (Object listener) { if (listener instanceof SetListener) { - ((SetListener)listener).entryUpdated(this); + @SuppressWarnings("unchecked") SetListener setlist = (SetListener)listener; + setlist.entryUpdated(this); } } diff --git a/src/java/com/threerings/presents/dobj/InvocationNotificationEvent.java b/src/java/com/threerings/presents/dobj/InvocationNotificationEvent.java index 959a9af89..8003c30b5 100644 --- a/src/java/com/threerings/presents/dobj/InvocationNotificationEvent.java +++ b/src/java/com/threerings/presents/dobj/InvocationNotificationEvent.java @@ -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; diff --git a/src/java/com/threerings/presents/dobj/InvocationRequestEvent.java b/src/java/com/threerings/presents/dobj/InvocationRequestEvent.java index cd067841e..73f7ec522 100644 --- a/src/java/com/threerings/presents/dobj/InvocationRequestEvent.java +++ b/src/java/com/threerings/presents/dobj/InvocationRequestEvent.java @@ -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; diff --git a/src/java/com/threerings/presents/dobj/InvocationResponseEvent.java b/src/java/com/threerings/presents/dobj/InvocationResponseEvent.java index ce6945c99..ee2d7bf43 100644 --- a/src/java/com/threerings/presents/dobj/InvocationResponseEvent.java +++ b/src/java/com/threerings/presents/dobj/InvocationResponseEvent.java @@ -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; diff --git a/src/java/com/threerings/presents/dobj/MessageEvent.java b/src/java/com/threerings/presents/dobj/MessageEvent.java index 642108ba4..0f14804ec 100644 --- a/src/java/com/threerings/presents/dobj/MessageEvent.java +++ b/src/java/com/threerings/presents/dobj/MessageEvent.java @@ -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; } diff --git a/src/java/com/threerings/presents/dobj/NamedEvent.java b/src/java/com/threerings/presents/dobj/NamedEvent.java index 001cfaa75..e96a00f4b 100644 --- a/src/java/com/threerings/presents/dobj/NamedEvent.java +++ b/src/java/com/threerings/presents/dobj/NamedEvent.java @@ -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; } diff --git a/src/java/com/threerings/presents/net/DownstreamMessage.java b/src/java/com/threerings/presents/net/DownstreamMessage.java index db237cf75..2eb337f42 100644 --- a/src/java/com/threerings/presents/net/DownstreamMessage.java +++ b/src/java/com/threerings/presents/net/DownstreamMessage.java @@ -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. */ diff --git a/src/java/com/threerings/presents/net/EventNotification.java b/src/java/com/threerings/presents/net/EventNotification.java index 02b42c684..1123fbc21 100644 --- a/src/java/com/threerings/presents/net/EventNotification.java +++ b/src/java/com/threerings/presents/net/EventNotification.java @@ -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 + "]"; diff --git a/src/java/com/threerings/presents/net/ForwardEventRequest.java b/src/java/com/threerings/presents/net/ForwardEventRequest.java index f42eab8f6..1f554765b 100644 --- a/src/java/com/threerings/presents/net/ForwardEventRequest.java +++ b/src/java/com/threerings/presents/net/ForwardEventRequest.java @@ -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 + "]"; diff --git a/src/java/com/threerings/presents/net/Message.java b/src/java/com/threerings/presents/net/Message.java new file mode 100644 index 000000000..ebed786cd --- /dev/null +++ b/src/java/com/threerings/presents/net/Message.java @@ -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; + } +} diff --git a/src/java/com/threerings/presents/net/PingRequest.java b/src/java/com/threerings/presents/net/PingRequest.java index 7c79ba684..151f9b510 100644 --- a/src/java/com/threerings/presents/net/PingRequest.java +++ b/src/java/com/threerings/presents/net/PingRequest.java @@ -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; } diff --git a/src/java/com/threerings/presents/net/PongResponse.java b/src/java/com/threerings/presents/net/PongResponse.java index 69c1db189..c838d9edb 100644 --- a/src/java/com/threerings/presents/net/PongResponse.java +++ b/src/java/com/threerings/presents/net/PongResponse.java @@ -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; } diff --git a/src/java/com/threerings/presents/net/Transport.java b/src/java/com/threerings/presents/net/Transport.java new file mode 100644 index 000000000..94c3a02ba --- /dev/null +++ b/src/java/com/threerings/presents/net/Transport.java @@ -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.class); + _ordered = new EnumMap>(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 instances = _ordered.get(type); + if (instances == null) { + _ordered.put(type, instances = new HashIntMap()); + } + 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 _unordered; + + /** Ordered instances mapped by type and channel. */ + protected static EnumMap> _ordered; +} diff --git a/src/java/com/threerings/presents/net/UpstreamMessage.java b/src/java/com/threerings/presents/net/UpstreamMessage.java index 965f88f04..f561ec727 100644 --- a/src/java/com/threerings/presents/net/UpstreamMessage.java +++ b/src/java/com/threerings/presents/net/UpstreamMessage.java @@ -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 diff --git a/src/java/com/threerings/presents/server/InvocationManager.java b/src/java/com/threerings/presents/server/InvocationManager.java index c0308db16..e80f807aa 100644 --- a/src/java/com/threerings/presents/server/InvocationManager.java +++ b/src/java/com/threerings/presents/server/InvocationManager.java @@ -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) { diff --git a/src/java/com/threerings/presents/server/InvocationSender.java b/src/java/com/threerings/presents/server/InvocationSender.java index f8ec470ef..d16a6bce0 100644 --- a/src/java/com/threerings/presents/server/InvocationSender.java +++ b/src/java/com/threerings/presents/server/InvocationSender.java @@ -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)); } } } diff --git a/src/java/com/threerings/presents/server/PresentsClient.java b/src/java/com/threerings/presents/server/PresentsClient.java index 265905d80..f1d6be33a 100644 --- a/src/java/com/threerings/presents/server/PresentsClient.java +++ b/src/java/com/threerings/presents/server/PresentsClient.java @@ -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())); } } diff --git a/src/java/com/threerings/presents/server/net/Connection.java b/src/java/com/threerings/presents/server/net/Connection.java index 911fdb1a5..457e1b25b 100644 --- a/src/java/com/threerings/presents/server/net/Connection.java +++ b/src/java/com/threerings/presents/server/net/Connection.java @@ -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) { diff --git a/src/java/com/threerings/presents/server/net/ConnectionManager.java b/src/java/com/threerings/presents/server/net/ConnectionManager.java index d2bbe1168..039755793 100644 --- a/src/java/com/threerings/presents/server/net/ConnectionManager.java +++ b/src/java/com/threerings/presents/server/net/ConnectionManager.java @@ -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; } diff --git a/src/java/com/threerings/presents/tools/GenDObjectTask.java b/src/java/com/threerings/presents/tools/GenDObjectTask.java index ea3aa742c..71e8cae2e 100644 --- a/src/java/com/threerings/presents/tools/GenDObjectTask.java +++ b/src/java/com/threerings/presents/tools/GenDObjectTask.java @@ -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(); diff --git a/src/java/com/threerings/presents/tools/GenReceiverTask.java b/src/java/com/threerings/presents/tools/GenReceiverTask.java index 5ad2addaa..b1471fd7d 100644 --- a/src/java/com/threerings/presents/tools/GenReceiverTask.java +++ b/src/java/com/threerings/presents/tools/GenReceiverTask.java @@ -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(); diff --git a/src/java/com/threerings/presents/tools/GenServiceTask.java b/src/java/com/threerings/presents/tools/GenServiceTask.java index 0a5b5b0ed..9a2752d2e 100644 --- a/src/java/com/threerings/presents/tools/GenServiceTask.java +++ b/src/java/com/threerings/presents/tools/GenServiceTask.java @@ -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 methods = new ComparableArrayList(); - + /** 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 listeners = new ComparableArrayList(); } - + /** The path to our ActionScript source files. */ protected File _asroot; diff --git a/src/java/com/threerings/presents/tools/InvocationTask.java b/src/java/com/threerings/presents/tools/InvocationTask.java index d3bc0776c..94e3326b2 100644 --- a/src/java/com/threerings/presents/tools/InvocationTask.java +++ b/src/java/com/threerings/presents/tools/InvocationTask.java @@ -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 { @@ -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; diff --git a/src/java/com/threerings/presents/tools/dobject_field.tmpl b/src/java/com/threerings/presents/tools/dobject_field.tmpl index d48a4d7a1..4fcecbfeb 100644 --- a/src/java/com/threerings/presents/tools/dobject_field.tmpl +++ b/src/java/com/threerings/presents/tools/dobject_field.tmpl @@ -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 diff --git a/src/java/com/threerings/presents/tools/dobject_set.tmpl b/src/java/com/threerings/presents/tools/dobject_set.tmpl index 6b6200945..a6480909e 100644 --- a/src/java/com/threerings/presents/tools/dobject_set.tmpl +++ b/src/java/com/threerings/presents/tools/dobject_set.tmpl @@ -25,7 +25,9 @@ */ public void update$upfield ($etype elem) { - requestEntryUpdate($capfield, $field, elem); + requestEntryUpdate( + $capfield, $field, elem, + $transport); } /** diff --git a/src/java/com/threerings/presents/tools/marshaller.tmpl b/src/java/com/threerings/presents/tools/marshaller.tmpl index 512be7297..e2c06a7a3 100644 --- a/src/java/com/threerings/presents/tools/marshaller.tmpl +++ b/src/java/com/threerings/presents/tools/marshaller.tmpl @@ -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 } diff --git a/src/java/com/threerings/presents/tools/sender.tmpl b/src/java/com/threerings/presents/tools/sender.tmpl index 5db04e034..c8ca62a2b 100644 --- a/src/java/com/threerings/presents/tools/sender.tmpl +++ b/src/java/com/threerings/presents/tools/sender.tmpl @@ -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 diff --git a/src/java/com/threerings/presents/util/DatagramSequencer.java b/src/java/com/threerings/presents/util/DatagramSequencer.java index 083ceec6d..e5236d6de 100644 --- a/src/java/com/threerings/presents/util/DatagramSequencer.java +++ b/src/java/com/threerings/presents/util/DatagramSequencer.java @@ -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 null 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; } /**