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; +}