diff --git a/src/java/com/threerings/presents/client/TimeBaseService.java b/src/java/com/threerings/presents/client/TimeBaseService.java new file mode 100644 index 000000000..82f520892 --- /dev/null +++ b/src/java/com/threerings/presents/client/TimeBaseService.java @@ -0,0 +1,36 @@ +// +// $Id: TimeBaseService.java,v 1.1 2002/05/28 23:14:06 mdb Exp $ + +package com.threerings.presents.client; + +import com.threerings.presents.Log; +import com.threerings.presents.client.Client; +import com.threerings.presents.client.InvocationDirector; +import com.threerings.presents.data.TimeBaseCodes; + +/** + * Provides a means by which to obtain access to a time base object which + * can be used to convert delta times into absolute times. + */ +public class TimeBaseService + implements TimeBaseCodes +{ + /** + * Requests the oid of the specified time base object be returned. The + * supplied response target must provide two methods to handle the + * responses generated by this request: + * + *
+ * public void handleTimdOidResponse (int invid, int timeOid); + * public void handleGetTimeOidFailed (int invid, String reason); + *+ */ + public static void getTimeOid ( + Client client, String timeBase, Object rsptarget) + { + InvocationDirector invdir = client.getInvocationDirector(); + invdir.invoke(MODULE_NAME, GET_TIME_OID_REQUEST, + new Object[] { timeBase }, rsptarget); + Log.debug("Sent getTimeOid request [timeBase=" + timeBase + "]."); + } +} diff --git a/src/java/com/threerings/presents/data/TimeBaseCodes.java b/src/java/com/threerings/presents/data/TimeBaseCodes.java new file mode 100644 index 000000000..3f48a6d1f --- /dev/null +++ b/src/java/com/threerings/presents/data/TimeBaseCodes.java @@ -0,0 +1,23 @@ +// +// $Id: TimeBaseCodes.java,v 1.1 2002/05/28 23:14:06 mdb Exp $ + +package com.threerings.presents.data; + +/** + * Codes and constants relating to the Presents time base services. + */ +public interface TimeBaseCodes +{ + /** The module name for the time services. */ + public static final String MODULE_NAME = "time"; + + /** The message identifier for a request to obtain a particular time + * object. */ + public static final String GET_TIME_OID_REQUEST = "GetTimeOid"; + + /** A response generated for a successful getTimeOid request. */ + public static final String TIME_OID_RESPONSE = "TimeOid"; + + /** An error response generated for GetTimeOid requests. */ + public static final String NO_SUCH_TIME_BASE = "m.no_such_time_base"; +} diff --git a/src/java/com/threerings/presents/data/TimeBaseObject.dobj b/src/java/com/threerings/presents/data/TimeBaseObject.dobj new file mode 100644 index 000000000..5297f073c --- /dev/null +++ b/src/java/com/threerings/presents/data/TimeBaseObject.dobj @@ -0,0 +1,104 @@ +// +// $Id: TimeBaseObject.dobj,v 1.1 2002/05/28 23:14:06 mdb Exp $ + +package com.threerings.presents.data; + +import com.threerings.presents.dobj.DObject; + +/** + * Used to communicate time bases to clients so that more efficient delta + * times can be transmitted over the network. Two time stamps are + * maintained: even and odd. When the even time base is sufficiently old + * that our delta time container (usually a short or an int) will exceed + * its maximum value, we switch to the odd time base. The bouncing between + * two separate values prevents problems from arising when the base time + * is changed, yet values using the old base time might still be + * propagating through the system. + * + *
Note that for sufficiently small delta time containers, stale + * values could still linger longer than the time required for two swaps + * (two byte delta stamps for example, must swap every 30 seconds). + */ +public class TimeBaseObject extends DObject +{ + /** The even time base, used to decode even delta times. */ + public long evenBase; + + /** The odd time base, used to decode odd delta times. */ + public long oddBase; + + /** + * Converts the supplied time stamp into a time delta (measured + * relative to the appropriate time base, even or odd) with maximum + * value of 2^15. (One bit must be used to indicate that it is an even + * or odd time stamp). + */ + public short toShortDelta (long timeStamp) + { + return (short)getDelta(timeStamp, Short.MAX_VALUE); + } + + /** + * Converts the supplied time stamp into a time delta (measured + * relative to the appropriate time base, even or odd) with maximum + * value of 2^31. (One bit must be used to indicate that it is an even + * or odd time stamp). + */ + public int toIntDelta (long timeStamp) + { + return (int)getDelta(timeStamp, Integer.MAX_VALUE); + } + + /** + * Converts the supplied delta time back to a wall time based on the + * base time in this time base object. Either an int or short delta + * can be passed to this method (the short will have been promoted to + * an int in the process but that will not mess up its encoded value). + */ + public long fromDelta (int delta) + { + boolean even = (delta > 0); + long time = even ? evenBase : oddBase; + if (even) { + time += delta; + } else { + time += (-1 - delta); + } + return time; + } + + /** + * Obtains a delta with the specified maximum value, swapping from + * even to odd, if necessary. + */ + protected long getDelta (long timeStamp, long maxValue) + { + boolean even = (evenBase > oddBase); + long base = even ? evenBase : oddBase; + long delta = timeStamp - base; + + // make sure this timestamp is not sufficiently old that we can't + // generate a delta time with it + if (delta < 0) { + String errmsg = "Time stamp too old for conversion to delta time"; + throw new IllegalArgumentException(errmsg); + } + + // see if it's time to swap + if (delta > maxValue) { + if (even) { + setOddBase(timeStamp); + } else { + setEvenBase(timeStamp); + } + delta = 0; + } + + // if we're odd, we need to mark the value as such + if (!even) { + delta = (-1 - delta); + } + + return delta; + } +} diff --git a/src/java/com/threerings/presents/data/TimeBaseObject.java b/src/java/com/threerings/presents/data/TimeBaseObject.java new file mode 100644 index 000000000..1e3398d00 --- /dev/null +++ b/src/java/com/threerings/presents/data/TimeBaseObject.java @@ -0,0 +1,138 @@ +// +// $Id: TimeBaseObject.java,v 1.1 2002/05/28 23:14:06 mdb Exp $ + +package com.threerings.presents.data; + +import com.threerings.presents.dobj.DObject; + +/** + * Used to communicate time bases to clients so that more efficient delta + * times can be transmitted over the network. Two time stamps are + * maintained: even and odd. When the even time base is sufficiently old + * that our delta time container (usually a short or an int) will exceed + * its maximum value, we switch to the odd time base. The bouncing between + * two separate values prevents problems from arising when the base time + * is changed, yet values using the old base time might still be + * propagating through the system. + * + *
Note that for sufficiently small delta time containers, stale
+ * values could still linger longer than the time required for two swaps
+ * (two byte delta stamps for example, must swap every 30 seconds).
+ */
+public class TimeBaseObject extends DObject
+{
+ /** The field name of the evenBase field. */
+ public static final String EVEN_BASE = "evenBase";
+
+ /** The field name of the oddBase field. */
+ public static final String ODD_BASE = "oddBase";
+
+ /** The even time base, used to decode even delta times. */
+ public long evenBase;
+
+ /** The odd time base, used to decode odd delta times. */
+ public long oddBase;
+
+ /**
+ * Converts the supplied time stamp into a time delta (measured
+ * relative to the appropriate time base, even or odd) with maximum
+ * value of 2^15. (One bit must be used to indicate that it is an even
+ * or odd time stamp).
+ */
+ public short toShortDelta (long timeStamp)
+ {
+ return (short)getDelta(timeStamp, Short.MAX_VALUE);
+ }
+
+ /**
+ * Converts the supplied time stamp into a time delta (measured
+ * relative to the appropriate time base, even or odd) with maximum
+ * value of 2^31. (One bit must be used to indicate that it is an even
+ * or odd time stamp).
+ */
+ public int toIntDelta (long timeStamp)
+ {
+ return (int)getDelta(timeStamp, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Converts the supplied delta time back to a wall time based on the
+ * base time in this time base object. Either an int or short delta
+ * can be passed to this method (the short will have been promoted to
+ * an int in the process but that will not mess up its encoded value).
+ */
+ public long fromDelta (int delta)
+ {
+ boolean even = (delta > 0);
+ long time = even ? evenBase : oddBase;
+ if (even) {
+ time += delta;
+ } else {
+ time += (-1 - delta);
+ }
+ return time;
+ }
+
+ /**
+ * Obtains a delta with the specified maximum value, swapping from
+ * even to odd, if necessary.
+ */
+ protected long getDelta (long timeStamp, long maxValue)
+ {
+ boolean even = (evenBase > oddBase);
+ long base = even ? evenBase : oddBase;
+ long delta = timeStamp - base;
+
+ // make sure this timestamp is not sufficiently old that we can't
+ // generate a delta time with it
+ if (delta < 0) {
+ String errmsg = "Time stamp too old for conversion to delta time";
+ throw new IllegalArgumentException(errmsg);
+ }
+
+ // see if it's time to swap
+ if (delta > maxValue) {
+ if (even) {
+ setOddBase(timeStamp);
+ } else {
+ setEvenBase(timeStamp);
+ }
+ delta = 0;
+ }
+
+ // if we're odd, we need to mark the value as such
+ if (!even) {
+ delta = (-1 - delta);
+ }
+
+ return delta;
+ }
+
+ /**
+ * Requests that the evenBase field be set to the specified
+ * value. The local value will be updated immediately and an event
+ * will be propagated through the system to notify all listeners that
+ * the attribute did change. Proxied copies of this object (on
+ * clients) will apply the value change when they received the
+ * attribute changed notification.
+ */
+ public void setEvenBase (long evenBase)
+ {
+ this.evenBase = evenBase;
+ requestAttributeChange(EVEN_BASE, new Long(evenBase));
+ }
+
+ /**
+ * Requests that the oddBase field be set to the specified
+ * value. The local value will be updated immediately and an event
+ * will be propagated through the system to notify all listeners that
+ * the attribute did change. Proxied copies of this object (on
+ * clients) will apply the value change when they received the
+ * attribute changed notification.
+ */
+ public void setOddBase (long oddBase)
+ {
+ this.oddBase = oddBase;
+ requestAttributeChange(ODD_BASE, new Long(oddBase));
+ }
+}
diff --git a/src/java/com/threerings/presents/server/PresentsServer.java b/src/java/com/threerings/presents/server/PresentsServer.java
index 3174297b0..6583a5899 100644
--- a/src/java/com/threerings/presents/server/PresentsServer.java
+++ b/src/java/com/threerings/presents/server/PresentsServer.java
@@ -1,5 +1,5 @@
//
-// $Id: PresentsServer.java,v 1.21 2002/05/28 22:25:44 mdb Exp $
+// $Id: PresentsServer.java,v 1.22 2002/05/28 23:14:06 mdb Exp $
package com.threerings.presents.server;
@@ -63,6 +63,9 @@ public class PresentsServer
// create our invocation manager
invmgr = new InvocationManager(omgr);
+ // initialize the time base services
+ TimeBaseProvider.init(invmgr, omgr);
+
// register our invocation service providers
registerProviders(PresentsConfig.getProviders());
}
diff --git a/src/java/com/threerings/presents/server/TimeBaseProvider.java b/src/java/com/threerings/presents/server/TimeBaseProvider.java
new file mode 100644
index 000000000..58d33d6d5
--- /dev/null
+++ b/src/java/com/threerings/presents/server/TimeBaseProvider.java
@@ -0,0 +1,106 @@
+//
+// $Id: TimeBaseProvider.java,v 1.1 2002/05/28 23:14:06 mdb Exp $
+
+package com.threerings.presents.server;
+
+import java.util.HashMap;
+import com.samskivert.util.ResultListener;
+
+import com.threerings.presents.Log;
+import com.threerings.presents.data.ClientObject;
+import com.threerings.presents.data.TimeBaseCodes;
+import com.threerings.presents.data.TimeBaseObject;
+
+import com.threerings.presents.dobj.DObject;
+import com.threerings.presents.dobj.ObjectAccessException;
+import com.threerings.presents.dobj.RootDObjectManager;
+import com.threerings.presents.dobj.Subscriber;
+
+/**
+ * Provides the server-side of the time base services. The time base
+ * services provide a means by which delta times can be sent over the
+ * network which are expanded based on a shared base time into full time
+ * stamps.
+ */
+public class TimeBaseProvider extends InvocationProvider
+ implements TimeBaseCodes
+{
+ /**
+ * Registers the time provider with the appropriate managers. Called
+ * by the presents server at startup.
+ */
+ public static void init (InvocationManager invmgr, RootDObjectManager omgr)
+ {
+ // we'll need these later
+ _invmgr = invmgr;
+ _omgr = omgr;
+
+ // register an invocation provider instance
+ _invmgr.registerProvider(MODULE_NAME, new TimeBaseProvider());
+ }
+
+ /**
+ * Creates a time base object which can subsequently be fetched by the
+ * client and used to send delta times.
+ *
+ * @param timeBase the name of the time base to create.
+ * @param resl the result listener that will be informed when the time
+ * base object is created or if the creation fails.
+ */
+ public static void createTimeBase (
+ final String timeBase, final ResultListener resl)
+ {
+ _omgr.createObject(TimeBaseObject.class, new Subscriber () {
+ public void objectAvailable (DObject object) {
+ // stuff it into our table
+ _timeBases.put(timeBase, object);
+ // and notify the listener
+ resl.requestCompleted(object);
+ }
+
+ public void requestFailed (int oid, ObjectAccessException cause) {
+ Log.warning("Ack. Unable to create time base object " +
+ "[timeBase=" + timeBase +
+ ", cause=" + cause + "].");
+ // notify the listener that we're borked
+ resl.requestFailed(cause);
+ }
+ });
+ }
+
+ /**
+ * Returns the named timebase object, or null if no time base object
+ * has been created with that name.
+ */
+ public static TimeBaseObject getTimeBase (String timeBase)
+ {
+ return (TimeBaseObject)_timeBases.get(timeBase);
+ }
+
+ /**
+ * Processes a request from a client to fetch the oid of the specified
+ * time object.
+ */
+ public void handleGetTimeOidRequest (
+ ClientObject source, int invid, String timeBase)
+ throws ServiceFailedException
+ {
+ // look up the time base object in question
+ TimeBaseObject time = getTimeBase(timeBase);
+ if (time == null) {
+ throw new ServiceFailedException(NO_SUCH_TIME_BASE);
+ }
+ // and send the response
+ sendResponse(source, invid, TIME_OID_RESPONSE,
+ new Integer(time.getOid()));
+ }
+
+ /** Used to keep track of our time base objects. */
+ protected static HashMap _timeBases = new HashMap();
+
+ /** The invocation manager with which we interoperate. */
+ protected static InvocationManager _invmgr;
+
+ /** The distributed object manager with which we interoperate. */
+ protected static RootDObjectManager _omgr;
+}