Files
Michael Bayne cfcf934d74 Notes.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1960 542714f4-19e9-0310-aa3c-eee0fc999fb1
2002-11-18 19:59:19 +00:00

404 lines
15 KiB
Plaintext

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 somewhat 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 static 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);
}
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.
- An InvocationProvider interface is generated from the InvocationService
interface:
public interface LocationProvider extends InvocationProvider
{
/**
* Requests that this client's body be moved to the specified
* location.
*
* @param caller the client object of the client that invoked this
* remotely callable method.
* @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 (ClientObject caller, int placeId,
InvocationService.MoveListener listener)
throws InvocationException;
}
This InvocationProvider interface is what is implemented by a server
entity to provider an actual implementation of the services.
- From the InvocationService 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)
{
// pass the request to the invocation services for dispatch
// over the network
}
}
On the server, an InvocationProvider is registered with the invocation
services which will return an InvocationMarshaller to be used to provide
access to those services. The InvocationMarshaller instance can then be
passed around the distributed object system as any other object. It can
be made a member of a distributed object or delivered in a distributed
object event.
When the InvocationMarshaller 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.
Access to the services on the server would generally be accomplished by
obtaining a reference directly to the provider that implements the
services and calling the methods via normal means. This allows the
server entities to provide whichever "caller" is appropriate.
- Notification services? Client provides "marshaller" in ClientObject,
server calls down to client through said marshaller object. How to
register implementations on the client end?
- Possible to integrate useful general purpose security?
* 8/6/2002
** PRMI notification services
- An interface is defined for the notification services:
/**
* Used to communicate asynchronous tell messages to the client.
*/
public interface ChatReceiver extends InvocationReceiver
{
/**
* Called when another user has sent us a tell message.
*
* @param source the username of the teller.
* @param message the text of the tell message.
*/
public void receivedTell (String source, String message);
}
An entity on the client implements this interface and registers with the
invocation director as the recipient for such invocation notifications
like so:
/**
* Coordinates chat related functionality on the client.
*/
public class ChatDirector implements ChatReceiver
{
public ChatDirector (PresentsContext ctx)
{
InvocationDirector invdir = ctx.getClient().getInvocationDirector();
invdir.registerReceiver(new ChatDecoder(this));
}
// documentation inherited from interface
public void receivedTell (String source, String message)
{
// deal with notification...
}
}
The decoder class referenced above is generated from the interface
specification and is responsible for converting notification events into
the appropriate method calls on the receiver implementation with which
it is constructed.
[Note: should only one receiver be allowed or should a list of receivers
be allowed, each receiving the notification when appropriate?]
A sender class is generated from the interface specification for use on
the server:
/**
* Provides facilities for delivering notifications as defined by the
* {@link ChatReceiver} interface.
*/
public class ChatSender extends InvocationSender
{
/**
* Dispatches a {@link ChatReceiver#receivedTell} notification to
* the specified client.
*/
public static void sendTell (
ClientObject target, String source, String message)
{
// code to marshall the parameters and dispatch an event to the
// target client...
}
}
Server code can then simply call the ChatSender.sendTell() method with
the appropriate client object to dispatch the associated notification to
that client.
Notice that the ChatReceiver.receivedTell method was renamed to
ChatSender.sendTell in the sender class. Methods starting with
"received" are converted to start with "send". If the interface method
does not start with "received" its name will not be changed in the
sender class definition.
* 8/13/2002
** True Names
- Invocation -> Remote? That'd be a hairy pain...
* 8/15/2002
** Invocation service TODO
X genservice + genreceiver need to slurp and replace "Id"
* 9/18/2002
** Client DObjectManager enhancement
- Make a note of recently unsubscribed oids so that we can avoid bogus
"Unable to dispatch event on non-proxied object" reports that inevitably
come in when the client has pitched a dobj but the server hasn't yet
processed the unsubscribe request.
* 10/2/2002
** Compound events/DObject transactions
- They are broken apart prior to dispatch on the server and thus
completely fail to accomplish their intended goal of providing efficient
network delivery to the client; attempt to resolve this in some way
* 10/8/2002
** Possible renamability improvements
- Generate field code rather than name for all DObject classes; send code
over wire; somehow map code to Field instance for setting value (this
could be problematic)
* 11/14/2002
** Possible eventual optimizations
- Have the connection manager maintain a pool of buffers into which
network data is read, when a connection receives the start of a new
message, it grabs a buffer and uses it until it has read its entire
message at which point it releases the buffer back into the pool;
otherwise we have a buffer assigned to each connection whether it needs
it or not and if they grow to, say, 4k per client connection, we could
easily get up to multiple megabytes of memory dedicated to input buffers
- Have PresentClient subscribe to objects through a proxy which serializes
messages to be dispatched to multiple clients immediately and uses the
same buffer to deliver the message to all subscribed clients (currently
the message is serialized once for each client before network delivery
and the serialization is done by the conmgr thread at send time;
ungood). Hrm, on second thought, we may not be able to do that because
each client connection maintains its own custom
ObjectOutputStream/ObjectInputStream pair which may not be in the same
state as another client's. Dooh!