Presents Notes -*- outline -*- * TODO - Pass cause back to client somehow via FailureResponse in Client.requestFailed - clientWillLogff becomes clientMayLogoff? - Look into nbio waking up all sockets when any data comes in. - (maybe) Allow piggybacking of object subscription onto service defined responses (like moveTo request). - (maybe) Allow better server side control of subscription management (to ensure that clients don't remain subscribed to objects they should no longer be susbcribed to; like scenes they've departed from). - Sort out support for server-side modifiable only fields to DObject. - Create a CompoundEvent that allows packaging up of multiple events to be dispatched in unison. Build dobj source generator and have it add versions of all update methods that take a compound event to which to append the event rather than dispatching them directly. - Maybe make AuthResponseData a Streamable instead of a DObject. - Have the LocationRegistry register the LocationProvider rather than doing it via a config file. Perhaps lose the config file element altogether. - Think about Subscriber business and whether or not DObject needs a list of subscribers or if there's a better way to handle removedLastSubscriber() on the client side and not taint the server side with all dat crap. * Server-side event concentrator - The client objects will not subscribe directly, but will subscribe through the concentrator so that, at least, it can create a single ForwardEventNotification for each Event being dispatched to a group of clients. Optimally, it would be able to flatten that notification as well and the byte array can be written to the socket of each of the individual clients rather than creating a separate byte array for each client. This will require a special "flattened notification" that can be inserted into the queue to preserve message ordering but then is simply sent rather than flattened and sent. * Marshaller - Consider how the dobject marshaller deals with classes loaded and reloaded using flushable classloaders. Also consider whether access to the marshaller cache needs to be synchronized. * Check into "connection closed by peer" thread exiting on client * TypedObjectFactory - Maybe modify so that types are assigned automatically even if everything has to be registered in a single place, since it pretty much does anyway. * Client network mgmt - Client perform all network ops on own thread, will call back to main code through Observer interface to notify of state changes in the authentication process/connectedness: public interface ClientObserver { public void didConnect (); public void connectionFailed (); public void didLogon (); public void logonFailed (); public void didDisconnect (); public void didLogoff (); } * DObject class generation - Distributed objects are defined like a class with a set of public data members which is then converted into an actual class with get/set methods for each member. public dclass GameObject { public int[] players; public String description; } becomes public class GameObject extends DObject { public void setPlayers (int[] players); public void setPlayersAt (int index, int value); public int[] getPlayers (); public int getPlayersAt (int index); public void setDescription (String description); public String getDescription (); } * 5/27/2002 ** Synchronizing time between client and server - After authentication, client begins a process of establishing the time differential between the client clock and the server clock. - The client sends a PING packet, noting the time immediately prior to delivering the packet over the network. - The server notes the at which the PING packet was unserialized. It supplies that time to the constructed PONG packet which then notes the time immediately prior to serialization and uses that to deliver to the client the server time and the number of milliseconds that passed between the unserialization of the PING packet and the serialization (and subsequent network delivery) of the PONG packet. - The client can then subtract the server processing time from the total round trip delay, divide the round trip delay by two, adjust the server time stamp accordingly and then obtain the client/server time differential. - The client then repeats this process some small number of times (five) to attempt to account for spurious differences in upstream vs. downstream transmission times and finally settles on a dT that will be used for the duration of the session. - We assume that the session will not last long enough for clock drift on either the client or server to become significant when compared to the error in the original time differential measurement. - We'll want to use the high-precision timing services once we have those because we don't want unnecessary error introduced into our ping and pong time stamps by the unreliable granularity of System.currentTimeMillis(). * 7/8/2002 ** Improved invocation services - Adding remote method call support to distributed objects doesn't allow us to separate interface from implementation; DObject classes are shared on the client and server by definition; whereas we would want an interface that could be known on the client and the interface and implementation known on the server - Perhaps RMI could somehow be rolled into the DObject system... more likely we'd want to automate the process of instantiating the implementation on the server and the proxy on the client and wouldn't want to use their object registry; we'd also want to do method dispatch on the omgr thread and use our messaging services * 7/15/2002 ** BEEP! - Look into replacing low-level network protocol with BEEP (and rolling our performance enhancements into BEEP's implementation if necessary) * 7/18/2002 ** PRMI (Presents remote method invocation) - PRMI ends up looking a lot like RMI with a few critical differences: + it uses the same message passing infrastructure as the distributed object system to accomplish its calls and responses + it requires asynchronous response delivery (return values from remotely invoked methods are prohibited) - It all starts with an interface that defines the remotely callable methods and remotely callable response interfaces: public interface LocationService extends InvocationService { /** * Used to communicate responses to {@link #moveTo} requests. */ public interface MoveListener extends InvocationListener { /** * Called in response to a successful {@link #moveTo} request. */ public void moveSucceeded (PlaceConfig config); } /** * Requests that this client's body be moved to the specified * location. * * @param placeId the object id of the place object to which the * body should be moved. * @param listener the listener that will be informed of success or * failure. */ public void moveTo (int placeId, MoveListener listener) throws InvocationException; } Note again that remotely callable methods cannot return values. Responses must be communicated asynchronously via listener parameters. The InvocationListener interface provides a standard method for handling request failure: public InvocationListener { public void requestFailed (InvocationException cause) } This will be used to report unexpected failure and can also be used to report expected failures by the remotely callable method implementations if they so desire. This is accomplished by their throwing exceptions that extend InvocationException. Non-InvocationException exceptions thrown by the remotely callable methods will be wrapped in an InvocationException and then passed on to the appropriate listener. For methods that declare multiple result listeners (a design choice that is not recommended), the first listener in the argument list will be the one to which caught exceptions are delivered. - From the interface, marshaller implementations are generated for the service interface and all listener interfaces contained therein: public class LocationMarshaller implements LocationService { // ... public int marshallerId; public void moveTo (int placeId, MoveListener listener) { try { if (_provider != null) { // this is a local request, dispatch it directly _provider.moveTo(placeId, listener); } else { // pass the request to the invocation services for // dispatch over the network } } catch (InvocationException ie) { if (listener != null) { listener.requestFailed(ie); } } } protected transient LocationService _provider; } An InvocationMarshaller is constructed on the server and passed at construct time a InvocationService implementation that will provide the actual implementation of the service. The marshaller will then register itself with the invocation services to receive an invocation object id which will be used to identify that marshaller in client JVMs. The InvocationMarshaller instance can then be passed around the distributed object system as any other object. If it is used on the server, the methods will be passed directly through to the implementation. If it is used on the client, it will marshall the request parameters and send them over the network to the server -- where they will be dispatched to the implementation -- any response from which will be communicated back through InvocationListener proxies which marshall the response and deliver it to the calling client, which then unpacks the response and delivers it to the original InvocationListener. - Notification services? Client provides "marshaller" in ClientObject, server calls down to client through said marshaller object. How to register implementations on the client end?