Convert Narya (most of the way) over to a Maven Ant task based build. The
ActionScript bits remain belligerent, but the Java stuff is mostly shipshape. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6222 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import com.threerings.presents.util.PresentsContext;
|
||||
|
||||
/**
|
||||
* Handles functionality common to nearly all client directors. They generally need to be session
|
||||
* observers so that they can set themselves up when the client logs on (by overriding {@link
|
||||
* #clientDidLogon}) and clean up after themselves when the client logs off (by overriding {@link
|
||||
* #clientDidLogoff}).
|
||||
*/
|
||||
public class BasicDirector
|
||||
implements SessionObserver
|
||||
{
|
||||
/**
|
||||
* Derived directors will need to provide the basic director with a context that it can use to
|
||||
* register itself with the necessary entities.
|
||||
*/
|
||||
protected BasicDirector (PresentsContext ctx)
|
||||
{
|
||||
// save context
|
||||
_ctx = ctx;
|
||||
|
||||
// listen for session start and end
|
||||
Client client = ctx.getClient();
|
||||
client.addClientObserver(this);
|
||||
|
||||
// if we're already logged on, fire off a call to fetch services
|
||||
if (client.isLoggedOn()) {
|
||||
if (isAvailable()) {
|
||||
// this is a sanity check: it will fail if this post-logon initialized director
|
||||
// claims to need service groups (it must make that known prior to logon)
|
||||
registerServices(client);
|
||||
fetchServices(client);
|
||||
}
|
||||
clientObjectUpdated(client);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void clientWillLogon (Client client)
|
||||
{
|
||||
registerServices(client);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void clientDidLogon (Client client)
|
||||
{
|
||||
if (isAvailable()) {
|
||||
fetchServices(client);
|
||||
}
|
||||
clientObjectUpdated(client);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void clientObjectDidChange (Client client)
|
||||
{
|
||||
clientObjectUpdated(client);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void clientDidLogoff (Client client)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not this director is available in standalone mode.
|
||||
*/
|
||||
public void setAvailableInStandalone (boolean available)
|
||||
{
|
||||
_availableInStandalone = available;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not this director is available in standalone mode (defaults to false).
|
||||
*/
|
||||
public boolean isAvailableInStandalone ()
|
||||
{
|
||||
return _availableInStandalone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this director is available in the current mode.
|
||||
*/
|
||||
protected boolean isAvailable ()
|
||||
{
|
||||
return isAvailableInStandalone() || !_ctx.getClient().isStandalone();
|
||||
}
|
||||
|
||||
/**
|
||||
* If this director is not currently available, throws a {@link RuntimeException}.
|
||||
*/
|
||||
protected void assertAvailable ()
|
||||
{
|
||||
if (!isAvailable()) {
|
||||
throw new RuntimeException(getClass().getName() +
|
||||
" not available in standalone mode!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in three circumstances: when a director is created and we've already logged on; when
|
||||
* we first log on and when the client object changes after we've already logged on.
|
||||
*/
|
||||
protected void clientObjectUpdated (Client client)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* If a director makes use of bootstrap invocation services which are part of a bootstrap
|
||||
* service group, it should register interest in that group here with a call to {@link
|
||||
* Client#addServiceGroup}.
|
||||
*/
|
||||
protected void registerServices (Client client)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived directors can override this method and obtain any services they'll need during their
|
||||
* operation via calls to {@link Client#getService}. If the director is available, it will
|
||||
* automatically be called when the client logs on or when the director is constructed if it is
|
||||
* constructed after the client is already logged on.
|
||||
*/
|
||||
protected void fetchServices (Client client)
|
||||
{
|
||||
}
|
||||
|
||||
/** The application context. */
|
||||
protected PresentsContext _ctx;
|
||||
|
||||
/** Whether or not this director is available in standalone mode. */
|
||||
protected boolean _availableInStandalone = true;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
/**
|
||||
* The client adapter makes life easier for client observer classes that
|
||||
* only care about one or two of the client observer callbacks. They can
|
||||
* either extend client adapter or create an anonymous class that extends
|
||||
* it and overrides just the callbacks they care about.
|
||||
*
|
||||
* <p> Note that the client adapter defaults to always ratifying a call to
|
||||
* {@link #clientWillLogoff} by returning true.
|
||||
*/
|
||||
public class ClientAdapter implements ClientObserver
|
||||
{
|
||||
// documentation inherited
|
||||
public void clientWillLogon (Client client)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void clientDidLogon (Client client)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void clientFailedToLogon (Client client, Exception cause)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void clientObjectDidChange (Client client)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void clientConnectionFailed (Client client, Exception cause)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public boolean clientWillLogoff (Client client)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void clientDidLogoff (Client client)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void clientDidClear (Client client)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import com.samskivert.util.IntListUtil;
|
||||
import com.samskivert.util.Interval;
|
||||
|
||||
import com.samskivert.swing.RuntimeAdjust;
|
||||
|
||||
import com.threerings.presents.data.AuthCodes;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Customizes the blocking communicator with some things that we only do on users' machines (where
|
||||
* there's only one client running, not potentially dozens, and where we're not sending high
|
||||
* volumes of traffic through the client like we do for inter-server communications). This will go
|
||||
* away when we create a special non-blocking communicator for use on the server that integrates
|
||||
* with the ClientManager.
|
||||
*/
|
||||
public class ClientCommunicator extends BlockingCommunicator
|
||||
{
|
||||
public ClientCommunicator (Client client)
|
||||
{
|
||||
super(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets our preferred connection port via our preferences mechanism.
|
||||
*/
|
||||
protected void setPrefPort (String key, int port) {
|
||||
PresentsPrefs.config.setValue(key, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets our preferred connection port via our preferences mechanism.
|
||||
*/
|
||||
protected int getPrefPort (String key, int defaultPort) {
|
||||
return PresentsPrefs.config.getValue(key, defaultPort);
|
||||
}
|
||||
|
||||
@Override // from BlockingCommunicator
|
||||
protected void openChannel (InetAddress host)
|
||||
throws IOException
|
||||
{
|
||||
// obtain the list of available ports on which to attempt our client connection and
|
||||
// determine our preferred port
|
||||
String pportKey = _client.getHostname() + ".preferred_port";
|
||||
int[] ports = _client.getPorts();
|
||||
int pport = getPrefPort(pportKey, ports[0]);
|
||||
int ppidx = Math.max(0, IntListUtil.indexOf(ports, pport));
|
||||
|
||||
// try connecting on each of the ports in succession
|
||||
for (int ii = 0; ii < ports.length; ii++) {
|
||||
int port = ports[(ii+ppidx)%ports.length];
|
||||
int nextPort = ports[(ii+ppidx+1)%ports.length];
|
||||
log.info("Connecting", "host", host, "port", port);
|
||||
InetSocketAddress addr = new InetSocketAddress(host, port);
|
||||
try {
|
||||
synchronized (this) {
|
||||
clearPPI(true);
|
||||
_prefPortInterval = new PrefPortInterval(pportKey, port, nextPort);
|
||||
_channel = SocketChannel.open(addr);
|
||||
_prefPortInterval.schedule(PREF_PORT_DELAY);
|
||||
}
|
||||
break;
|
||||
|
||||
} catch (IOException ioe) {
|
||||
if (ioe instanceof ConnectException && ii < (ports.length-1)) {
|
||||
_client.reportLogonTribulations(
|
||||
new LogonException(AuthCodes.TRYING_NEXT_PORT, true));
|
||||
continue; // try the next port
|
||||
}
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from BlockingCommunicator
|
||||
protected void readerDidExit ()
|
||||
{
|
||||
// if we haven't recorded a preferred port yet, instead do the failure action since we
|
||||
// didn't stay connected long enough
|
||||
clearPPI(true);
|
||||
super.readerDidExit();
|
||||
}
|
||||
|
||||
@Override // from BlockingCommunicator
|
||||
protected boolean debugLogMessages ()
|
||||
{
|
||||
return _logMessages.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels our preferred port saving interval. This method is called from the communication
|
||||
* reader thread and the interval thread and must thus be synchronized.
|
||||
*/
|
||||
protected synchronized boolean clearPPI (boolean cancel)
|
||||
{
|
||||
if (_prefPortInterval != null) {
|
||||
if (cancel) {
|
||||
_prefPortInterval.cancel();
|
||||
_prefPortInterval.failed();
|
||||
}
|
||||
_prefPortInterval = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Used to save our preferred port once we know our connection is not going to be
|
||||
* unceremoniously closed by Windows Connection Sharing. */
|
||||
protected class PrefPortInterval extends Interval
|
||||
{
|
||||
public PrefPortInterval (String key, int thisPort, int nextPort) {
|
||||
super();
|
||||
_key = key;
|
||||
_thisPort = thisPort;
|
||||
_nextPort = nextPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expired () {
|
||||
if (clearPPI(false)) {
|
||||
setPrefPort(_key, _thisPort);
|
||||
}
|
||||
}
|
||||
|
||||
public void failed () {
|
||||
setPrefPort(_key, _nextPort);
|
||||
}
|
||||
|
||||
protected String _key;
|
||||
protected int _thisPort;
|
||||
protected int _nextPort;
|
||||
}
|
||||
|
||||
/** We use this interval to record the preferred port if it stays connected long enough. */
|
||||
protected PrefPortInterval _prefPortInterval;
|
||||
|
||||
/** Used to control low-level message logging. */
|
||||
protected static RuntimeAdjust.BooleanAdjust _logMessages =
|
||||
new RuntimeAdjust.BooleanAdjust("Toggles whether or not all sent and received low-level " +
|
||||
"network events are logged.", "narya.presents.log_events",
|
||||
PresentsPrefs.config, false);
|
||||
|
||||
/** Time a port must remain connected before we mark it as preferred. */
|
||||
protected static long PREF_PORT_DELAY = 5000L;
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.samskivert.util.DebugChords;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.IntMap;
|
||||
import com.samskivert.util.Interval;
|
||||
import com.samskivert.util.Queue;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.presents.dobj.CompoundEvent;
|
||||
import com.threerings.presents.dobj.DEvent;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.DObjectManager;
|
||||
import com.threerings.presents.dobj.ObjectAccessException;
|
||||
import com.threerings.presents.dobj.ObjectDestroyedEvent;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
import com.threerings.presents.net.BootstrapData;
|
||||
import com.threerings.presents.net.BootstrapNotification;
|
||||
import com.threerings.presents.net.EventNotification;
|
||||
import com.threerings.presents.net.FailureResponse;
|
||||
import com.threerings.presents.net.ForwardEventRequest;
|
||||
import com.threerings.presents.net.Message;
|
||||
import com.threerings.presents.net.ObjectResponse;
|
||||
import com.threerings.presents.net.PongResponse;
|
||||
import com.threerings.presents.net.SubscribeRequest;
|
||||
import com.threerings.presents.net.UnsubscribeRequest;
|
||||
import com.threerings.presents.net.UnsubscribeResponse;
|
||||
import com.threerings.presents.net.UpdateThrottleMessage;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* The client distributed object manager manages a set of proxy objects which mirror the
|
||||
* distributed objects maintained on the server. Requests for modifications, etc. are forwarded to
|
||||
* the server and events are dispatched from the server to this client for objects to which this
|
||||
* client is subscribed.
|
||||
*/
|
||||
public class ClientDObjectMgr
|
||||
implements DObjectManager, Runnable
|
||||
{
|
||||
/**
|
||||
* Constructs a client distributed object manager.
|
||||
*
|
||||
* @param comm a communicator instance by which it can communicate with the server.
|
||||
* @param client a reference to the client that is managing this whole communications and event
|
||||
* dispatch business.
|
||||
*/
|
||||
public ClientDObjectMgr (Communicator comm, Client client)
|
||||
{
|
||||
_comm = comm;
|
||||
_client = client;
|
||||
|
||||
// register a debug hook for dumping all objects in the distributed object table
|
||||
DebugChords.registerHook(DUMP_OTABLE_MODMASK, DUMP_OTABLE_KEYCODE, new DebugChords.Hook() {
|
||||
public void invoke () {
|
||||
log.info("Dumping " + _ocache.size() + " objects:");
|
||||
for (DObject obj : _ocache.values()) {
|
||||
log.info(obj.getClass().getName() + " " + obj);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// register a flush interval
|
||||
_flusher = new Interval(client.getRunQueue()) {
|
||||
@Override public void expired () {
|
||||
flushObjects();
|
||||
}
|
||||
};
|
||||
_flusher.schedule(FLUSH_INTERVAL, true);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public boolean isManager (DObject object)
|
||||
{
|
||||
// we are never authoritative in the present implementation
|
||||
return false;
|
||||
}
|
||||
|
||||
// inherit documentation from the interface
|
||||
public <T extends DObject> void subscribeToObject (int oid, Subscriber<T> target)
|
||||
{
|
||||
if (oid <= 0) {
|
||||
target.requestFailed(oid, new ObjectAccessException("Invalid oid " + oid + "."));
|
||||
} else {
|
||||
queueAction(oid, target, true);
|
||||
}
|
||||
}
|
||||
|
||||
// inherit documentation from the interface
|
||||
public <T extends DObject> void unsubscribeFromObject (int oid, Subscriber<T> target)
|
||||
{
|
||||
queueAction(oid, target, false);
|
||||
}
|
||||
|
||||
// inherit documentation from the interface
|
||||
public void postEvent (DEvent event)
|
||||
{
|
||||
// send a forward event request to the server
|
||||
_comm.postMessage(new ForwardEventRequest(event));
|
||||
}
|
||||
|
||||
// inherit documentation from the interface
|
||||
public void removedLastSubscriber (DObject obj, boolean deathWish)
|
||||
{
|
||||
// if this object has a registered flush delay, don't can it just yet, just slip it onto
|
||||
// the flush queue
|
||||
Class<?> oclass = obj.getClass();
|
||||
for (Class<?> dclass : _delays.keySet()) {
|
||||
if (dclass.isAssignableFrom(oclass)) {
|
||||
long expire = System.currentTimeMillis() + _delays.get(dclass).longValue();
|
||||
_flushes.put(obj.getOid(), new FlushRecord(obj, expire));
|
||||
// Log.info("Flushing " + obj.getOid() + " at " + new java.util.Date(expire));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if we didn't find a delay registration, flush immediately
|
||||
flushObject(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an object flush delay.
|
||||
*
|
||||
* @see Client#registerFlushDelay
|
||||
*/
|
||||
public void registerFlushDelay (Class<?> objclass, long delay)
|
||||
{
|
||||
_delays.put(objclass, Long.valueOf(delay));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the communicator when a message arrives from the network layer. We queue it up for
|
||||
* processing and request some processing time on the main thread.
|
||||
*/
|
||||
public void processMessage (Message msg)
|
||||
{
|
||||
if (_client.getRunQueue().isRunning()) {
|
||||
// append it to our queue
|
||||
_actions.append(msg);
|
||||
// and queue ourselves up to be run
|
||||
_client.getRunQueue().postRunnable(this);
|
||||
} else {
|
||||
log.info("Dropping message as RunQueue is shutdown", "msg", msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked on the main client thread to process any newly arrived messages that we have waiting
|
||||
* in our queue.
|
||||
*/
|
||||
public void run ()
|
||||
{
|
||||
// process the next event on our queue
|
||||
Object obj;
|
||||
if ((obj = _actions.getNonBlocking()) != null) {
|
||||
// do the proper thing depending on the object
|
||||
if (obj instanceof EventNotification) {
|
||||
dispatchEvent(((EventNotification)obj).getEvent());
|
||||
|
||||
} else if (obj instanceof BootstrapNotification) {
|
||||
BootstrapData data = ((BootstrapNotification)obj).getData();
|
||||
_client.gotBootstrap(data, this);
|
||||
|
||||
} else if (obj instanceof ObjectResponse<?>) {
|
||||
registerObjectAndNotify((ObjectResponse<?>)obj);
|
||||
|
||||
} else if (obj instanceof UnsubscribeResponse) {
|
||||
int oid = ((UnsubscribeResponse)obj).getOid();
|
||||
if (_dead.remove(oid) == null) {
|
||||
log.warning("Received unsub ACK from unknown object", "oid", oid);
|
||||
}
|
||||
|
||||
} else if (obj instanceof FailureResponse) {
|
||||
notifyFailure(((FailureResponse)obj).getOid(), ((FailureResponse)obj).getMessage());
|
||||
|
||||
} else if (obj instanceof PongResponse) {
|
||||
_client.gotPong((PongResponse)obj);
|
||||
|
||||
} else if (obj instanceof UpdateThrottleMessage) {
|
||||
UpdateThrottleMessage upmsg = (UpdateThrottleMessage)obj;
|
||||
_client.setOutgoingMessageThrottle(upmsg.messagesPerSec);
|
||||
|
||||
} else if (obj instanceof ObjectAction<?>) {
|
||||
ObjectAction<?> act = (ObjectAction<?>)obj;
|
||||
if (act.subscribe) {
|
||||
doSubscribe(act);
|
||||
} else {
|
||||
doUnsubscribe(act.oid, act.target);
|
||||
}
|
||||
|
||||
} else {
|
||||
log.warning("Unknown action", "action", obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the client is cleaned up due to having disconnected from the server.
|
||||
*/
|
||||
public void cleanup ()
|
||||
{
|
||||
// tell any pending object subscribers that they're not getting their bits
|
||||
for (PendingRequest<?> req : _penders.values()) {
|
||||
for (Subscriber<?> sub : req.targets) {
|
||||
sub.requestFailed(req.oid, new ObjectAccessException("Client connection closed"));
|
||||
}
|
||||
}
|
||||
_penders.clear();
|
||||
_flusher.cancel();
|
||||
}
|
||||
|
||||
protected <T extends DObject> void queueAction (int oid, Subscriber<T> target, boolean subscribe)
|
||||
{
|
||||
if (_client.getRunQueue().isRunning()) {
|
||||
// queue up an action
|
||||
_actions.append(new ObjectAction<T>(oid, target, subscribe));
|
||||
// and queue up the omgr to get invoked on the invoker thread
|
||||
_client.getRunQueue().postRunnable(this);
|
||||
} else {
|
||||
log.info("Dropping subscribe action as RunQueue is stopped",
|
||||
"oid", oid, "subscribe", subscribe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new event arrives from the server that should be dispatched to subscribers
|
||||
* here on the client.
|
||||
*/
|
||||
protected void dispatchEvent (DEvent event)
|
||||
{
|
||||
// Log.info("Dispatching event: " + evt);
|
||||
|
||||
// look up the object on which we're dispatching this event
|
||||
int remoteOid = event.getTargetOid();
|
||||
DObject target = _ocache.get(remoteOid);
|
||||
if (target == null) {
|
||||
if (!_dead.containsKey(remoteOid)) {
|
||||
log.warning("Unable to dispatch event on non-proxied object " + event + ".");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// because we might be acting as a proxy for a remote server, we may need to fiddle with
|
||||
// this event before we dispatch it
|
||||
_client.convertFromRemote(target, event);
|
||||
|
||||
// if this is a compound event, we need to process its contained events in order
|
||||
if (event instanceof CompoundEvent) {
|
||||
// notify our proxy subscribers in one fell swoop
|
||||
target.notifyProxies(event);
|
||||
|
||||
// now break the event up and dispatch each event to listeners individually
|
||||
List<DEvent> events = ((CompoundEvent)event).getEvents();
|
||||
int ecount = events.size();
|
||||
for (int ii = 0; ii < ecount; ii++) {
|
||||
dispatchEvent(remoteOid, target, events.get(ii));
|
||||
}
|
||||
|
||||
} else {
|
||||
// forward to any proxies (or not if we're dispatching part of a compound event)
|
||||
target.notifyProxies(event);
|
||||
|
||||
// and dispatch the event to regular listeners
|
||||
dispatchEvent(remoteOid, target, event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an event on an already resolved target object.
|
||||
*
|
||||
* @param remoteOid is specified explicitly because we will have already translated the event's
|
||||
* target oid into our local object managers oid space if we're acting on behalf of the peer
|
||||
* manager.
|
||||
*/
|
||||
protected void dispatchEvent (int remoteOid, DObject target, DEvent event)
|
||||
{
|
||||
try {
|
||||
// apply the event to the object
|
||||
boolean notify = event.applyToObject(target);
|
||||
|
||||
// if this is an object destroyed event, we need to remove the object from our table
|
||||
if (event instanceof ObjectDestroyedEvent) {
|
||||
// Log.info("Pitching destroyed object [oid=" + remoteOid +
|
||||
// ", class=" + StringUtil.shortClassName(target) + "].");
|
||||
_ocache.remove(remoteOid);
|
||||
}
|
||||
|
||||
// have the object pass this event on to its listeners
|
||||
if (notify) {
|
||||
target.notifyListeners(event);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("Failure processing event", "event", event, "target", target, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this object in our proxy cache and notifies the subscribers that were waiting for
|
||||
* subscription to this object.
|
||||
*/
|
||||
protected <T extends DObject> void registerObjectAndNotify (ObjectResponse<T> orsp)
|
||||
{
|
||||
// let the object know that we'll be managing it
|
||||
T obj = orsp.getObject();
|
||||
obj.setManager(this);
|
||||
|
||||
// stick the object into the proxy object table
|
||||
_ocache.put(obj.getOid(), obj);
|
||||
|
||||
// let the penders know that the object is available
|
||||
PendingRequest<?> req = _penders.remove(obj.getOid());
|
||||
if (req == null) {
|
||||
log.warning("Got object, but no one cares?!", "oid", obj.getOid(), "obj", obj);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int ii = 0; ii < req.targets.size(); ii++) {
|
||||
@SuppressWarnings("unchecked") Subscriber<T> target = (Subscriber<T>)req.targets.get(ii);
|
||||
// add them as a subscriber
|
||||
obj.addSubscriber(target);
|
||||
// and let them know that the object is in
|
||||
target.objectAvailable(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the subscribers that had requested this object (for subscription) that it is not
|
||||
* available.
|
||||
*/
|
||||
protected void notifyFailure (int oid, String message)
|
||||
{
|
||||
// let the penders know that the object is not available
|
||||
PendingRequest<?> req = _penders.remove(oid);
|
||||
if (req == null) {
|
||||
log.warning("Failed to get object, but no one cares?!", "oid", oid);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int ii = 0; ii < req.targets.size(); ii++) {
|
||||
req.targets.get(ii).requestFailed(oid, new ObjectAccessException(message));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is guaranteed to be invoked via the invoker and can safely do main thread type things
|
||||
* like call back to the subscriber.
|
||||
*/
|
||||
protected <T extends DObject> void doSubscribe (ObjectAction<T> action)
|
||||
{
|
||||
// Log.info("doSubscribe: " + oid + ": " + target);
|
||||
|
||||
int oid = action.oid;
|
||||
Subscriber<T> target = action.target;
|
||||
|
||||
// first see if we've already got the object in our table
|
||||
@SuppressWarnings("unchecked") T obj = (T)_ocache.get(oid);
|
||||
if (obj != null) {
|
||||
// clear the object out of the flush table if it's in there
|
||||
if (_flushes.remove(oid) != null) {
|
||||
// Log.info("Resurrected " + oid + ".");
|
||||
}
|
||||
// add the subscriber and call them back straight away
|
||||
obj.addSubscriber(target);
|
||||
target.objectAvailable(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
// see if we've already got an outstanding request for this object
|
||||
@SuppressWarnings("unchecked") PendingRequest<T> req = (PendingRequest<T>)_penders.get(oid);
|
||||
if (req != null) {
|
||||
// add this subscriber to the list to be notified when the request is satisfied
|
||||
req.addTarget(target);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise we need to create a new request
|
||||
req = new PendingRequest<T>(oid);
|
||||
req.addTarget(target);
|
||||
_penders.put(oid, req);
|
||||
// Log.info("Registering pending request [oid=" + oid + "].");
|
||||
|
||||
// and issue a request to get things rolling
|
||||
_comm.postMessage(new SubscribeRequest(oid));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is guaranteed to be invoked via the invoker and can safely do main thread type things
|
||||
* like call back to the subscriber.
|
||||
*/
|
||||
protected void doUnsubscribe (int oid, Subscriber<?> target)
|
||||
{
|
||||
DObject dobj = _ocache.get(oid);
|
||||
if (dobj != null) {
|
||||
dobj.removeSubscriber(target);
|
||||
|
||||
} else {
|
||||
log.info("Requested to remove subscriber from non-proxied object", "oid", oid,
|
||||
"sub", target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes a distributed object subscription, issuing an unsubscribe request to the server.
|
||||
*/
|
||||
protected void flushObject (DObject obj)
|
||||
{
|
||||
// move this object into the dead pool so that we don't claim to have it around anymore;
|
||||
// once our unsubscribe message is processed, it'll be 86ed
|
||||
int ooid = obj.getOid();
|
||||
_ocache.remove(ooid);
|
||||
_dead.put(ooid, obj);
|
||||
|
||||
// ship off an unsubscribe message to the server; we'll remove the object from our table
|
||||
// when we get the unsub ack
|
||||
_comm.postMessage(new UnsubscribeRequest(ooid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called periodically to flush any objects that have been lingering due to a previously
|
||||
* enacted flush delay.
|
||||
*/
|
||||
protected void flushObjects ()
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
for (Iterator<IntMap.IntEntry<FlushRecord>> iter = _flushes.intEntrySet().iterator();
|
||||
iter.hasNext(); ) {
|
||||
IntMap.IntEntry<FlushRecord> entry = iter.next();
|
||||
// int oid = entry.getIntKey();
|
||||
FlushRecord rec = entry.getValue();
|
||||
if (rec.expire <= now) {
|
||||
iter.remove();
|
||||
flushObject(rec.object);
|
||||
// Log.info("Flushed object " + oid + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The object action is used to queue up a subscribe or unsubscribe request.
|
||||
*/
|
||||
protected static final class ObjectAction<T extends DObject>
|
||||
{
|
||||
public int oid;
|
||||
public Subscriber<T> target;
|
||||
public boolean subscribe;
|
||||
|
||||
public ObjectAction (int oid, Subscriber<T> target, boolean subscribe)
|
||||
{
|
||||
this.oid = oid;
|
||||
this.target = target;
|
||||
this.subscribe = subscribe;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
return StringUtil.fieldsToString(this);
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents a pending subscription request. */
|
||||
protected static final class PendingRequest<T extends DObject>
|
||||
{
|
||||
public int oid;
|
||||
public ArrayList<Subscriber<T>> targets = Lists.newArrayList();
|
||||
|
||||
public PendingRequest (int oid)
|
||||
{
|
||||
this.oid = oid;
|
||||
}
|
||||
|
||||
public void addTarget (Subscriber<T> target)
|
||||
{
|
||||
targets.add(target);
|
||||
}
|
||||
}
|
||||
|
||||
/** Used to manage pending object flushes. */
|
||||
protected static final class FlushRecord
|
||||
{
|
||||
/** The object to be flushed. */
|
||||
public DObject object;
|
||||
|
||||
/** The time at which we flush it. */
|
||||
public long expire;
|
||||
|
||||
public FlushRecord (DObject object, long expire)
|
||||
{
|
||||
this.object = object;
|
||||
this.expire = expire;
|
||||
}
|
||||
}
|
||||
|
||||
/** A reference to the communicator that sends and receives messages for this client. */
|
||||
protected Communicator _comm;
|
||||
|
||||
/** A reference to our client instance. */
|
||||
protected Client _client;
|
||||
|
||||
/** Periodically calls {@link #flushObject}. */
|
||||
protected Interval _flusher;
|
||||
|
||||
/** Our primary dispatch queue. */
|
||||
protected Queue<Object> _actions = new Queue<Object>();
|
||||
|
||||
/** All of the distributed objects that are active on this client. */
|
||||
protected HashIntMap<DObject> _ocache = new HashIntMap<DObject>();
|
||||
|
||||
/** Objects that have been marked for death. */
|
||||
protected HashIntMap<DObject> _dead = new HashIntMap<DObject>();
|
||||
|
||||
/** Pending object subscriptions. */
|
||||
protected HashIntMap<PendingRequest<?>> _penders = new HashIntMap<PendingRequest<?>>();
|
||||
|
||||
/** A mapping from distributed object class to flush delay. */
|
||||
protected HashMap<Class<?>, Long> _delays = Maps.newHashMap();
|
||||
|
||||
/** A set of objects waiting to be flushed. */
|
||||
protected HashIntMap<FlushRecord> _flushes = new HashIntMap<FlushRecord>();
|
||||
|
||||
/** The modifiers for our dump table debug hook (Alt+Shift). */
|
||||
protected static int DUMP_OTABLE_MODMASK = KeyEvent.ALT_DOWN_MASK|KeyEvent.SHIFT_DOWN_MASK;
|
||||
|
||||
/** The key code for our dump table debug hook (o). */
|
||||
protected static int DUMP_OTABLE_KEYCODE = KeyEvent.VK_O;
|
||||
|
||||
/** Flush expired objects every 30 seconds. */
|
||||
protected static final long FLUSH_INTERVAL = 30 * 1000L;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
/**
|
||||
* A client observer is a more detailed version of the {@link SessionObserver} for entities that
|
||||
* are interested in more detail about the logon/logoff process.
|
||||
*
|
||||
* <p> In the normal course of affairs, {@link SessionObserver#clientDidLogon} will be called after
|
||||
* the client successfully logs on to the server and {@link SessionObserver#clientDidLogoff} will
|
||||
* be called after the client logs off of the server. If logon fails for any reason,
|
||||
* {@link #clientFailedToLogon} will be called to explain the failure.
|
||||
*
|
||||
* <p> {@link #clientWillLogoff} will only be called when an abortable logoff is requested (like
|
||||
* when the user clicks on a logoff button of some sort). It will not be called during
|
||||
* non-abortable logoff requests (like when the browser calls stop on the applet and is about to
|
||||
* yank the rug out from under us). If an observer aborts the logoff request, it should notify the
|
||||
* user in some way why the request was aborted (<em>but it shouldn't do so on the thread that
|
||||
* calls {@link #clientWillLogoff}</em>).
|
||||
*
|
||||
* <p> If the client connection fails unexpectedly, {@link #clientConnectionFailed} will be called
|
||||
* to let the observers know that we lost our connection to the server.
|
||||
* {@link SessionObserver#clientDidLogoff} will be called immediately afterwards as a normal logoff
|
||||
* procedure is effected.
|
||||
*/
|
||||
public interface ClientObserver extends SessionObserver
|
||||
{
|
||||
/**
|
||||
* Called if anything fails during the logon attempt. This could be a network failure,
|
||||
* authentication failure or otherwise. The exception provided will indicate the cause of the
|
||||
* failure.
|
||||
*
|
||||
* @param cause an exception indicating the cause of the logon failure. <em>Note:</em> this
|
||||
* may be a {@link LogonException} and if so, the caller <em>must</em> check {@link
|
||||
* LogonException#isStillInProgress} to find out if the logon process has totally failed or if
|
||||
* we are simply reporting intermediate status (we might be falling back to an alternative port
|
||||
* or delaying our auto-retry attempt due to server overload).
|
||||
*/
|
||||
void clientFailedToLogon (Client client, Exception cause);
|
||||
|
||||
/**
|
||||
* Called when the connection to the server went away for some unexpected reason. This will be
|
||||
* followed by a call to {@link SessionObserver#clientDidLogoff}.
|
||||
*/
|
||||
void clientConnectionFailed (Client client, Exception cause);
|
||||
|
||||
/**
|
||||
* Called when an abortable logoff request is made. If the observer returns false from this
|
||||
* method, the client will abort the logoff request.
|
||||
*/
|
||||
boolean clientWillLogoff (Client client);
|
||||
|
||||
/**
|
||||
* Called after the client is completely logged off from a successful session and is ready to
|
||||
* reconnect to a new server if desired. This will only be called after an active session was
|
||||
* terminated, not after a logon attempt failed as that failure will be reported by {@link
|
||||
* #clientFailedToLogon}.
|
||||
*/
|
||||
void clientDidClear (Client client);
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import com.samskivert.util.RunAnywhere;
|
||||
|
||||
import com.threerings.presents.net.AuthResponse;
|
||||
import com.threerings.presents.net.AuthResponseData;
|
||||
import com.threerings.presents.net.Message;
|
||||
import com.threerings.presents.net.UpstreamMessage;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Handles sending and receiving messages for the client.
|
||||
*/
|
||||
public abstract class Communicator
|
||||
{
|
||||
/**
|
||||
* Creates a new communicator instance which is associated with the supplied client.
|
||||
*/
|
||||
public Communicator (Client client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs on to the server and initiates our full-duplex message exchange.
|
||||
*/
|
||||
public abstract void logon ();
|
||||
|
||||
/**
|
||||
* Delivers a logoff notification to the server and shuts down the network connection. Also
|
||||
* causes all communication threads to terminate.
|
||||
*/
|
||||
public abstract void logoff ();
|
||||
|
||||
/**
|
||||
* Notifies the communicator that the client has received its bootstrap data.
|
||||
*/
|
||||
public abstract void gotBootstrap ();
|
||||
|
||||
/**
|
||||
* Queues up the specified message for delivery upstream.
|
||||
*/
|
||||
public abstract void postMessage (UpstreamMessage msg);
|
||||
|
||||
/**
|
||||
* Configures this communicator with a custom class loader to be used when reading and writing
|
||||
* objects over the network.
|
||||
*/
|
||||
public abstract void setClassLoader (ClassLoader loader);
|
||||
|
||||
/**
|
||||
* Returns the time at which we last sent a packet to the server.
|
||||
*/
|
||||
public long getLastWrite ()
|
||||
{
|
||||
return _lastWrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we should transmit datagrams.
|
||||
*/
|
||||
public boolean getTransmitDatagrams ()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a note of the time at which we last communicated with the server.
|
||||
*/
|
||||
protected synchronized void updateWriteStamp ()
|
||||
{
|
||||
_lastWrite = RunAnywhere.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses must call this method when they receive the authentication response.
|
||||
*/
|
||||
protected void gotAuthResponse (AuthResponse rsp)
|
||||
throws LogonException
|
||||
{
|
||||
AuthResponseData data = rsp.getData();
|
||||
if (!data.code.equals(AuthResponseData.SUCCESS)) {
|
||||
throw new LogonException(data.code);
|
||||
}
|
||||
logonSucceeded(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the authentication process completes successfully. Derived classes can override
|
||||
* this method and complete any initialization that need wait for authentication success.
|
||||
*/
|
||||
protected synchronized void logonSucceeded (AuthResponseData data)
|
||||
{
|
||||
// create our distributed object manager
|
||||
_omgr = new ClientDObjectMgr(this, _client);
|
||||
|
||||
// fill the auth data into the client's local field so that it can be requested by external
|
||||
// entities
|
||||
_client._authData = data;
|
||||
|
||||
// wait for the bootstrap notification before we claim that we're actually logged on
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback called by the reader thread when it has parsed a new message from the socket and
|
||||
* wishes to have it processed.
|
||||
*/
|
||||
protected void processMessage (Message msg)
|
||||
{
|
||||
// post this message to the dobjmgr queue
|
||||
_omgr.processMessage(msg);
|
||||
}
|
||||
|
||||
protected void notifyClientObservers (ObserverOps.Session op)
|
||||
{
|
||||
if (_client != null) {
|
||||
_client.notifyObservers(op);
|
||||
} else {
|
||||
log.warning("Dropping client observer notification.", "op", op);
|
||||
}
|
||||
}
|
||||
|
||||
protected void clientCleanup (Exception logonError)
|
||||
{
|
||||
if (_client != null) {
|
||||
_client.cleanup(logonError);
|
||||
_client = null; // prevent any post-cleanup tomfoolery
|
||||
}
|
||||
}
|
||||
|
||||
protected Client _client;
|
||||
protected ClientDObjectMgr _omgr;
|
||||
protected long _lastWrite;
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.threerings.presents.net.PingRequest;
|
||||
import com.threerings.presents.net.PongResponse;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Used to compute the client/server time delta, attempting to account for
|
||||
* the network delay experienced when the server sends its current time to
|
||||
* the client.
|
||||
*/
|
||||
public class DeltaCalculator
|
||||
{
|
||||
/**
|
||||
* Constructs a delta calculator which is used to calculate the time
|
||||
* delta between the client and server, accounting reasonably well for
|
||||
* the delay introduced by sending a timestamp over the network from
|
||||
* the server to the client.
|
||||
*/
|
||||
public DeltaCalculator ()
|
||||
{
|
||||
_deltas = new long[CLOCK_SYNC_PING_COUNT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we send another ping?
|
||||
*/
|
||||
public boolean shouldSendPing ()
|
||||
{
|
||||
return (_ping == null) && !isDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called when a ping message is sent to the server.
|
||||
*/
|
||||
public void sentPing (PingRequest ping)
|
||||
{
|
||||
_ping = ping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called when the pong response arrives back from the server.
|
||||
*
|
||||
* @return true if we've iterated sufficiently many times to establish
|
||||
* a stable time delta estimate.
|
||||
*/
|
||||
public boolean gotPong (PongResponse pong)
|
||||
{
|
||||
if (_ping == null) {
|
||||
// an errant pong that is likely being processed late after
|
||||
// a new connection was opened.
|
||||
return false;
|
||||
}
|
||||
// don't freak out if they keep calling gotPong() after we're done
|
||||
if (_iter >= _deltas.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// make a note of when the ping message was sent and when the pong
|
||||
// response was received (both in client time)
|
||||
long send = _ping.getPackStamp(), recv = pong.getUnpackStamp();
|
||||
_ping = null; // clear out the saved sent ping
|
||||
|
||||
// make a note of when the pong response was sent (in server time)
|
||||
// and the processing delay incurred on the server
|
||||
long server = pong.getPackStamp(), delay = pong.getProcessDelay();
|
||||
|
||||
// compute the network delay (round-trip time divided by two)
|
||||
long nettime = (recv - send - delay)/2;
|
||||
|
||||
// the time delta is the client time when the pong was received
|
||||
// minus the server's send time (plus network delay): dT = C - S
|
||||
_deltas[_iter] = recv - (server + nettime);
|
||||
|
||||
log.debug("Calculated delta", "delay", delay, "nettime", nettime, "delta", _deltas[_iter],
|
||||
"rtt", (recv-send));
|
||||
|
||||
return (++_iter >= CLOCK_SYNC_PING_COUNT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best estimate client/server time-delta.
|
||||
*/
|
||||
public long getTimeDelta ()
|
||||
{
|
||||
if (_iter == 0) { // no responses yet
|
||||
return 0L;
|
||||
}
|
||||
|
||||
// Return a median value as our estimate, rather than an average.
|
||||
// Mdb writes:
|
||||
// -----------
|
||||
// I used the median because that was more likely to result in a
|
||||
// sensible value.
|
||||
//
|
||||
// Assuming there are two kinds of packets, one that goes and comes
|
||||
// back without delay and provides an accurate time value, and one
|
||||
// that gets delayed somewhere on the way there or the way back and
|
||||
// provides an inaccurate time value.
|
||||
//
|
||||
// If no packets are delayed, both algorithms should be fine. If one
|
||||
// packet is delayed the median will select the middle, non-delayed
|
||||
// packet, whereas the average will skew everything a bit because
|
||||
// of the delayed packet. If two packets are delayed, the median
|
||||
// will be more skewed than the average because it will benefit
|
||||
// from the one accurate packet and if all three packets are delayed
|
||||
// both algorithms will be (approximately) equally inaccurate.
|
||||
//
|
||||
// I believe the chances are most likely that zero or one packets
|
||||
// will be delayed, so I chose the median rather than the average.
|
||||
// -----------
|
||||
|
||||
// copy the deltas array so that we don't alter things before
|
||||
// all pongs have arrived
|
||||
long[] deltasCopy = new long[_iter];
|
||||
System.arraycopy(_deltas, 0, deltasCopy, 0, _iter);
|
||||
|
||||
// sort the estimates and return one from the middle
|
||||
Arrays.sort(deltasCopy);
|
||||
return deltasCopy[deltasCopy.length/2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this calculator has enough data to compute a time
|
||||
* delta estimate. Stick a fork in it!
|
||||
*/
|
||||
public boolean isDone ()
|
||||
{
|
||||
return (_iter >= CLOCK_SYNC_PING_COUNT);
|
||||
}
|
||||
|
||||
/** The number of ping/pong iterations we've made. */
|
||||
protected int _iter;
|
||||
|
||||
/** Client/server time delta estimates. */
|
||||
protected long[] _deltas;
|
||||
|
||||
/** A reference to the most recently sent ping which we use to obtain
|
||||
* the appropriate send stamp when we get the corresponding receive
|
||||
* stamp. */
|
||||
protected PingRequest _ping;
|
||||
|
||||
/** The number of times we PING during clock sync to try to smooth out
|
||||
* network jiggling. */
|
||||
protected static final int CLOCK_SYNC_PING_COUNT = 3;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Provides the basic functionality used to dispatch invocation notification events.
|
||||
*/
|
||||
public abstract class InvocationDecoder
|
||||
{
|
||||
/** The receiver for which we're decoding and dispatching notifications. */
|
||||
public InvocationReceiver receiver;
|
||||
|
||||
/**
|
||||
* Returns the generated hash code that is used to identify this invocation notification
|
||||
* service.
|
||||
*/
|
||||
public abstract String getReceiverCode ();
|
||||
|
||||
/**
|
||||
* Dispatches the specified method to our receiver.
|
||||
*/
|
||||
public void dispatchNotification (int methodId, Object[] args)
|
||||
{
|
||||
log.warning("Requested to dispatch unknown method", "receiver", receiver,
|
||||
"methodId", methodId, "args", args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.samskivert.util.HashIntMap;
|
||||
|
||||
import com.threerings.presents.client.InvocationReceiver.Registration;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.data.InvocationMarshaller.ListenerMarshaller;
|
||||
import com.threerings.presents.dobj.DEvent;
|
||||
import com.threerings.presents.dobj.DObjectManager;
|
||||
import com.threerings.presents.dobj.DSet;
|
||||
import com.threerings.presents.dobj.EventListener;
|
||||
import com.threerings.presents.dobj.InvocationNotificationEvent;
|
||||
import com.threerings.presents.dobj.InvocationRequestEvent;
|
||||
import com.threerings.presents.dobj.InvocationResponseEvent;
|
||||
import com.threerings.presents.dobj.MessageEvent;
|
||||
import com.threerings.presents.dobj.ObjectAccessException;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
import com.threerings.presents.net.Transport;
|
||||
|
||||
import static com.threerings.presents.Log.log;
|
||||
|
||||
/**
|
||||
* Handles the client side management of the invocation services.
|
||||
*/
|
||||
public class InvocationDirector
|
||||
implements EventListener
|
||||
{
|
||||
/**
|
||||
* Initializes the invocation director. This is called when the client establishes a connection
|
||||
* with the server.
|
||||
*
|
||||
* @param omgr the distributed object manager via which the invocation manager will send and
|
||||
* receive events.
|
||||
* @param cloid the oid of the object on which invocation notifications as well as invocation
|
||||
* responses will be received.
|
||||
* @param client a reference to the client for whom we're doing our business.
|
||||
*/
|
||||
public void init (DObjectManager omgr, final int cloid, Client client)
|
||||
{
|
||||
// sanity check
|
||||
if (_clobj != null) {
|
||||
log.warning("Zoiks, client object around during invmgr init!");
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// keep these for later
|
||||
_omgr = omgr;
|
||||
_client = client;
|
||||
|
||||
// add ourselves as a subscriber to the client object
|
||||
_omgr.subscribeToObject(cloid, new Subscriber<ClientObject>() {
|
||||
public void objectAvailable (ClientObject clobj) {
|
||||
// add ourselves as an event listener
|
||||
clobj.addListener(InvocationDirector.this);
|
||||
|
||||
// clear out our previous registrations
|
||||
clobj.setReceivers(new DSet<Registration>());
|
||||
|
||||
// keep a handle on this bad boy
|
||||
_clobj = clobj;
|
||||
|
||||
// assign a mapping to already registered receivers
|
||||
assignReceiverIds();
|
||||
|
||||
// let the client know that we're ready to go now that we've got our subscription
|
||||
// to the client object
|
||||
_client.gotClientObject(_clobj);
|
||||
}
|
||||
|
||||
public void requestFailed (int oid, ObjectAccessException cause) {
|
||||
// aiya! we were unable to subscribe to the client object. we're hosed!
|
||||
log.warning("Invocation director unable to subscribe to client object",
|
||||
"cloid", cloid, "cause", cause + "]!");
|
||||
_client.getClientObjectFailed(cause);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears out our session information. This is called when the client ends its session with the
|
||||
* server.
|
||||
*/
|
||||
public void cleanup ()
|
||||
{
|
||||
// wipe our client object, receiver mappings and listener mappings
|
||||
_clobj = null;
|
||||
_receivers.clear();
|
||||
_listeners.clear();
|
||||
|
||||
// also reset our counters
|
||||
_requestId = 0;
|
||||
_receiverId = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an invocation notification receiver by way of its notification event decoder.
|
||||
*/
|
||||
public void registerReceiver (InvocationDecoder decoder)
|
||||
{
|
||||
// add the receiver to the list
|
||||
_reclist.add(decoder);
|
||||
|
||||
// if we're already online, assign a receiver id to this decoder
|
||||
if (_clobj != null) {
|
||||
assignReceiverId(decoder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a receiver registration.
|
||||
*/
|
||||
public void unregisterReceiver (String receiverCode)
|
||||
{
|
||||
// remove the receiver from the list
|
||||
for (Iterator<InvocationDecoder> iter = _reclist.iterator(); iter.hasNext(); ) {
|
||||
InvocationDecoder decoder = iter.next();
|
||||
if (decoder.getReceiverCode().equals(receiverCode)) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// if we're logged on, clear out any receiver id mapping
|
||||
if (_clobj != null) {
|
||||
Registration rreg = _clobj.receivers.get(receiverCode);
|
||||
if (rreg == null) {
|
||||
log.warning("Receiver unregistered for which we have no id to code mapping",
|
||||
"code", receiverCode);
|
||||
} else {
|
||||
_receivers.remove(rreg.receiverId);
|
||||
// Log.info("Cleared receiver " + StringUtil.shortClassName(decoder) +
|
||||
// " " + rreg + ".");
|
||||
}
|
||||
_clobj.removeFromReceivers(receiverCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a receiver id to this decoder and publishes it in the {@link ClientObject#receivers}
|
||||
* field.
|
||||
*/
|
||||
protected void assignReceiverId (InvocationDecoder decoder)
|
||||
{
|
||||
Registration reg = new Registration(decoder.getReceiverCode(), nextReceiverId());
|
||||
// stick the mapping into the client object
|
||||
_clobj.addToReceivers(reg);
|
||||
// and map the receiver in our receivers table
|
||||
_receivers.put(reg.receiverId, decoder);
|
||||
// Log.info("Registered receiver " + StringUtil.shortClassName(decoder) + " " + reg + ".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we log on; generates mappings for all receivers registered prior to logon.
|
||||
*/
|
||||
protected void assignReceiverIds ()
|
||||
{
|
||||
// pack all the set add events into a single transaction
|
||||
_clobj.startTransaction();
|
||||
try {
|
||||
for (InvocationDecoder decoder : _reclist) {
|
||||
assignReceiverId(decoder);
|
||||
}
|
||||
} finally {
|
||||
_clobj.commitTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
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,
|
||||
"methodId", methodId);
|
||||
return;
|
||||
}
|
||||
|
||||
// configure any invocation listener marshallers among the arguments
|
||||
int acount = args.length;
|
||||
for (int ii = 0; ii < acount; ii++) {
|
||||
Object arg = args[ii];
|
||||
if (arg instanceof ListenerMarshaller) {
|
||||
ListenerMarshaller lm = (ListenerMarshaller)arg;
|
||||
lm.requestId = nextRequestId();
|
||||
lm.mapStamp = System.currentTimeMillis();
|
||||
// create a mapping for this marshaller so that we can properly dispatch responses
|
||||
// sent to it
|
||||
_listeners.put(lm.requestId, lm);
|
||||
}
|
||||
}
|
||||
|
||||
// create an invocation request event
|
||||
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
|
||||
// server-side entities only sort of pretending to be a client
|
||||
event.setSourceOid(_clobj.getOid());
|
||||
|
||||
// Log.info("Sending invreq " + event + ".");
|
||||
|
||||
// now dispatch the event
|
||||
_omgr.postEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process notification and response events arriving on user object.
|
||||
*/
|
||||
public void eventReceived (DEvent event)
|
||||
{
|
||||
if (event instanceof InvocationResponseEvent) {
|
||||
InvocationResponseEvent ire = (InvocationResponseEvent)event;
|
||||
handleInvocationResponse(ire.getRequestId(), ire.getMethodId(), ire.getArgs());
|
||||
|
||||
} else if (event instanceof InvocationNotificationEvent) {
|
||||
InvocationNotificationEvent ine = (InvocationNotificationEvent)event;
|
||||
handleInvocationNotification(ine.getReceiverId(), ine.getMethodId(), ine.getArgs());
|
||||
|
||||
} else if (event instanceof MessageEvent) {
|
||||
MessageEvent mevt = (MessageEvent)event;
|
||||
if (mevt.getName().equals(ClientObject.CLOBJ_CHANGED)) {
|
||||
handleClientObjectChanged(((Integer)mevt.getArgs()[0]).intValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an invocation response.
|
||||
*/
|
||||
protected void handleInvocationResponse (int reqId, int methodId, Object[] args)
|
||||
{
|
||||
// look up the invocation marshaller registered for that response
|
||||
ListenerMarshaller listener = _listeners.remove(reqId);
|
||||
if (listener == null) {
|
||||
log.warning("Received invocation response for which we have no registered listener. " +
|
||||
"It is possible that this listener was flushed because the response did " +
|
||||
"not arrive within " + LISTENER_MAX_AGE + " milliseconds.",
|
||||
"reqId", reqId, "methId", methodId, "args", args);
|
||||
return;
|
||||
}
|
||||
|
||||
// log.info("Dispatching invocation response", "listener", listener,
|
||||
// "methId", methodId, "args", args);
|
||||
|
||||
// dispatch the response
|
||||
try {
|
||||
listener.dispatchResponse(methodId, args);
|
||||
} catch (Throwable t) {
|
||||
log.warning("Invocation response listener choked", "listener", listener,
|
||||
"methId", methodId, "args", args, t);
|
||||
}
|
||||
|
||||
// flush expired listeners periodically
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - _lastFlushTime > LISTENER_FLUSH_INTERVAL) {
|
||||
_lastFlushTime = now;
|
||||
flushListeners(now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an invocation notification.
|
||||
*/
|
||||
protected void handleInvocationNotification (int receiverId, int methodId, Object[] args)
|
||||
{
|
||||
// look up the decoder registered for this receiver
|
||||
InvocationDecoder decoder = _receivers.get(receiverId);
|
||||
if (decoder == null) {
|
||||
log.warning("Received notification for which we have no registered receiver",
|
||||
"recvId", receiverId, "methodId", methodId, "args", args);
|
||||
return;
|
||||
}
|
||||
|
||||
// log.info("Dispatching invocation notification", "receiver", decoder.receiver,
|
||||
// "methodId", methodId, "args", args);
|
||||
|
||||
try {
|
||||
decoder.dispatchNotification(methodId, args);
|
||||
} catch (Throwable t) {
|
||||
log.warning("Invocation notification receiver choked", "receiver", decoder.receiver,
|
||||
"methId", methodId, "args", args, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the server has informed us that our previous client object is going the way of
|
||||
* the Dodo because we're changing screen names. We subscribe to the new object and report to
|
||||
* the client once we've got our hands on it.
|
||||
*/
|
||||
protected void handleClientObjectChanged (int newCloid)
|
||||
{
|
||||
// subscribe to the new client object
|
||||
_omgr.subscribeToObject(newCloid, new Subscriber<ClientObject>() {
|
||||
public void objectAvailable (ClientObject clobj) {
|
||||
// grab a reference to our old receiver registrations
|
||||
DSet<Registration> receivers = _clobj.receivers;
|
||||
|
||||
// replace the client object
|
||||
_clobj = clobj;
|
||||
|
||||
// add ourselves as an event listener
|
||||
_clobj.addListener(InvocationDirector.this);
|
||||
|
||||
// reregister our receivers
|
||||
_clobj.startTransaction();
|
||||
try {
|
||||
_clobj.setReceivers(new DSet<Registration>());
|
||||
for (Registration reg : receivers) {
|
||||
_clobj.addToReceivers(reg);
|
||||
}
|
||||
} finally {
|
||||
_clobj.commitTransaction();
|
||||
}
|
||||
|
||||
// and report the switcheroo back to the client
|
||||
_client.clientObjectDidChange(_clobj);
|
||||
}
|
||||
|
||||
public void requestFailed (int oid, ObjectAccessException cause) {
|
||||
log.warning("Aiya! Unable to subscribe to changed client object", "cloid", oid,
|
||||
"cause", cause);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes listener mappings that are older than {@link #LISTENER_MAX_AGE} milliseconds. An
|
||||
* alternative to flushing listeners that did not explicitly receive a response within our
|
||||
* expiry time period is to have the server's proxy listener send a message to the client when
|
||||
* it is finalized. We then know that no server entity will subsequently use that proxy
|
||||
* listener to send a response to the client. This involves more network traffic and complexity
|
||||
* than seems necessary and if a user of the system does respond after their listener has been
|
||||
* flushed, an informative warning will be logged. (Famous last words.)
|
||||
*/
|
||||
protected void flushListeners (long now)
|
||||
{
|
||||
if (_listeners.size() > 0) {
|
||||
long then = now - LISTENER_MAX_AGE;
|
||||
Iterator<ListenerMarshaller> iter = _listeners.values().iterator();
|
||||
while (iter.hasNext()) {
|
||||
ListenerMarshaller lm = iter.next();
|
||||
if (then > lm.mapStamp) {
|
||||
// Log.info("Flushing marshaller " + lm + ".");
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to generate monotonically increasing invocation request ids.
|
||||
*/
|
||||
protected synchronized short nextRequestId ()
|
||||
{
|
||||
return _requestId++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to generate monotonically increasing invocation receiver ids.
|
||||
*/
|
||||
protected synchronized short nextReceiverId ()
|
||||
{
|
||||
return _receiverId++;
|
||||
}
|
||||
|
||||
/** The distributed object manager with which we interact. */
|
||||
protected DObjectManager _omgr;
|
||||
|
||||
/** The client for whom we're working. */
|
||||
protected Client _client;
|
||||
|
||||
/** Our client object; invocation responses and notifications are received on this object. */
|
||||
protected ClientObject _clobj;
|
||||
|
||||
/** Used to generate monotonically increasing request ids. */
|
||||
protected short _requestId;
|
||||
|
||||
/** Used to generate monotonically increasing receiver ids. */
|
||||
protected short _receiverId;
|
||||
|
||||
/** Used to keep track of invocation service listeners which will receive responses from
|
||||
* invocation service requests. */
|
||||
protected HashIntMap<ListenerMarshaller> _listeners = new HashIntMap<ListenerMarshaller>();
|
||||
|
||||
/** Used to keep track of invocation notification receivers. */
|
||||
protected HashIntMap<InvocationDecoder> _receivers = new HashIntMap<InvocationDecoder>();
|
||||
|
||||
/** All registered receivers are maintained in a list so that we can assign receiver ids to
|
||||
* them when we go online. */
|
||||
protected ArrayList<InvocationDecoder> _reclist = Lists.newArrayList();
|
||||
|
||||
/** The last time we flushed our listeners. */
|
||||
protected long _lastFlushTime;
|
||||
|
||||
/** The minimum interval between listener flush attempts. */
|
||||
protected static final long LISTENER_FLUSH_INTERVAL = 15000L;
|
||||
|
||||
/** Listener mappings older than 90 seconds are reaped. */
|
||||
protected static final long LISTENER_MAX_AGE = 90 * 1000L;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import com.threerings.presents.dobj.DSet;
|
||||
|
||||
/**
|
||||
* Invocation notification receipt interfaces should be defined as
|
||||
* extending this interface. Actual notification receivers will implement
|
||||
* the requisite receiver interface definition and register themselves
|
||||
* with the {@link InvocationDirector} using the generated {@link
|
||||
* InvocationDecoder} class specific to the notification receiver
|
||||
* interface in question. For example:
|
||||
*
|
||||
* <pre>
|
||||
* public class FooDirector implements FooReceiver
|
||||
* {
|
||||
* public FooDirector (PresentsContext ctx)
|
||||
* {
|
||||
* InvocationDirector idir = ctx.getClient().getInvocationDirector();
|
||||
* idir.registerReceiver(new FooDecoder(this));
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @see InvocationDirector#registerReceiver
|
||||
*/
|
||||
public interface InvocationReceiver
|
||||
{
|
||||
/**
|
||||
* Used to maintain a registry of invocation receivers that can be
|
||||
* used to convert (large) hash codes into (small) registration
|
||||
* numbers.
|
||||
*/
|
||||
public static class Registration implements DSet.Entry
|
||||
{
|
||||
/** The unique hash code associated with this invocation receiver
|
||||
* class. */
|
||||
public String receiverCode;
|
||||
|
||||
/** The unique id assigned to this invocation receiver class at
|
||||
* registration time. */
|
||||
public short receiverId;
|
||||
|
||||
/** Creates and initializes a registration instance. */
|
||||
public Registration (String receiverCode, short receiverId)
|
||||
{
|
||||
this.receiverCode = receiverCode;
|
||||
this.receiverId = receiverId;
|
||||
}
|
||||
|
||||
/** Creates a blank instance suitable for unserialization. */
|
||||
public Registration ()
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public Comparable<?> getKey ()
|
||||
{
|
||||
return receiverCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
return "[" + receiverCode + " => " + receiverId + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
/**
|
||||
* Serves as the base interface for invocation services. An invocation service can be defined by
|
||||
* extending this interface and defining service methods, as well as response listeners (which must
|
||||
* extend {@link InvocationListener}). For example:
|
||||
*
|
||||
* <pre>
|
||||
* public interface LocationService extends InvocationService
|
||||
* {
|
||||
*
|
||||
* // Used to communicate responses to moveTo() requests.
|
||||
* public interface MoveListener extends InvocationListener
|
||||
* {
|
||||
* // Called in response to a successful moveTo() request.
|
||||
* 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.
|
||||
* void moveTo (int placeId, MoveListener listener);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* From this interface, a <code>LocationProvider</code> interface will be generated which should be
|
||||
* implemented by whatever server entity that will actually provide the server side of this
|
||||
* invocation service. That provider interface would look like the following:
|
||||
*
|
||||
* <pre>
|
||||
* 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 should be informed of success or failure.
|
||||
* void moveTo (ClientObject caller, int placeId, MoveListener listener)
|
||||
* throws InvocationException;
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public interface InvocationService
|
||||
{
|
||||
/**
|
||||
* Invocation service methods that require a response should take a listener argument that can
|
||||
* be notified of request success or failure. The listener argument should extend this
|
||||
* interface so that generic failure can be reported in all cases. For example:
|
||||
*
|
||||
* <pre>
|
||||
* // Used to communicate responses to <code>moveTo</code> requests.
|
||||
* public interface MoveListener extends InvocationListener
|
||||
* {
|
||||
* // Called in response to a successful <code>moveTo</code> request.
|
||||
* void moveSucceeded (PlaceConfig config);
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public static interface InvocationListener
|
||||
{
|
||||
/**
|
||||
* Called to report request failure. If the invocation services system detects failure of
|
||||
* any kind, it will report it via this callback. Particular services may also make use of
|
||||
* this callback to report failures of their own, or they may opt to define more specific
|
||||
* failure callbacks.
|
||||
*/
|
||||
void requestFailed (String cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the {@link InvocationListener} with a basic success callback.
|
||||
*/
|
||||
public static interface ConfirmListener extends InvocationListener
|
||||
{
|
||||
/**
|
||||
* Indicates that the request was successfully processed.
|
||||
*/
|
||||
void requestProcessed ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the {@link InvocationListener} with a basic success callback that delivers a result
|
||||
* object.
|
||||
*/
|
||||
public static interface ResultListener extends InvocationListener
|
||||
{
|
||||
/**
|
||||
* Indicates that the request was successfully processed.
|
||||
*/
|
||||
void requestProcessed (Object result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import com.samskivert.util.Logger;
|
||||
|
||||
/**
|
||||
* Implements the basic {@link InvocationService.InvocationListener} and logs the failure.
|
||||
*/
|
||||
public class LoggingListener
|
||||
implements InvocationService.InvocationListener
|
||||
{
|
||||
/**
|
||||
* Constructs a listener that will report the supplied error message along with the reason for
|
||||
* failure to the supplied log object.
|
||||
*/
|
||||
public LoggingListener (Logger log, String errmsg)
|
||||
{
|
||||
_logger = log;
|
||||
_errmsg = errmsg;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void requestFailed (String reason)
|
||||
{
|
||||
_logger.warning(_errmsg + " [reason=" + reason + "].");
|
||||
}
|
||||
|
||||
protected Logger _logger;
|
||||
protected String _errmsg;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
/**
|
||||
* A logon exception is used to indicate a failure to log on to the
|
||||
* server. The reason for failure is encoded as a string and stored in the
|
||||
* message field of the exception.
|
||||
*/
|
||||
public class LogonException extends Exception
|
||||
{
|
||||
/**
|
||||
* Constructs a logon exception with the supplied logon failure code.
|
||||
*/
|
||||
public LogonException (String code)
|
||||
{
|
||||
this(code, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a logon exception with the supplied logon failure code.
|
||||
*
|
||||
* @param stillInProgress indicates that the logon attempt has not totally
|
||||
* failed, rather we are still trying but want to report that our initial
|
||||
* attempt did not work and that we're falling back to alternative methods.
|
||||
*/
|
||||
public LogonException (String code, boolean stillInProgress)
|
||||
{
|
||||
super(code);
|
||||
_stillInProgress = stillInProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this exception is reporting an intermediate status
|
||||
* rather than total logon failure. The client may be falling back to an
|
||||
* alternative port or delaying an auto-retry attempt due to server
|
||||
* overload. If this method returns true, the client <em>should not</em>
|
||||
* allow additional calls to {@link Client#logon} as the current attempt is
|
||||
* still in progress.
|
||||
*/
|
||||
public boolean isStillInProgress ()
|
||||
{
|
||||
return _stillInProgress;
|
||||
}
|
||||
|
||||
protected boolean _stillInProgress;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import com.threerings.presents.net.DownstreamMessage;
|
||||
import com.threerings.presents.net.UpstreamMessage;
|
||||
|
||||
/**
|
||||
* Used to listen to low-level message handling for the purpose of statistics tracking. Methods
|
||||
* must be thread-safe.
|
||||
*/
|
||||
public interface MessageTracker
|
||||
{
|
||||
/**
|
||||
* An implementation of the interface that does nothing.
|
||||
*/
|
||||
public static final MessageTracker NOOP = new MessageTracker() {
|
||||
public void messageSent (boolean datagram, int size, UpstreamMessage msg) {
|
||||
}
|
||||
public void messageReceived (
|
||||
boolean datagram, int size, DownstreamMessage msg, int missed) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Notes that a message has been sent.
|
||||
*/
|
||||
public void messageSent (boolean datagram, int size, UpstreamMessage msg);
|
||||
|
||||
/**
|
||||
* Notes that a message has been received.
|
||||
*
|
||||
* @param msg the received message, or <code>null</code> if received out of order.
|
||||
* @param missed the number of messages missed between this message and the one before it.
|
||||
*/
|
||||
public void messageReceived (boolean datagram, int size, DownstreamMessage msg, int missed);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import com.samskivert.util.ObserverList;
|
||||
|
||||
/**
|
||||
* Used to notify session and client observers.
|
||||
*/
|
||||
public class ObserverOps
|
||||
{
|
||||
public abstract static class Session implements ObserverList.ObserverOp<SessionObserver>
|
||||
{
|
||||
public Session (com.threerings.presents.client.Client client) {
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public boolean apply (SessionObserver obs) {
|
||||
notify(obs);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract void notify (SessionObserver obs);
|
||||
|
||||
protected com.threerings.presents.client.Client _client;
|
||||
}
|
||||
|
||||
public abstract static class Client extends Session
|
||||
{
|
||||
public Client (com.threerings.presents.client.Client client) {
|
||||
super(client);
|
||||
}
|
||||
|
||||
@Override public void notify (SessionObserver obs) {
|
||||
if (obs instanceof ClientObserver) {
|
||||
notify((ClientObserver)obs);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void notify (ClientObserver obs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import com.samskivert.util.PrefsConfig;
|
||||
|
||||
/**
|
||||
* Provides access to runtime configuration parameters for this package
|
||||
* and its subpackages.
|
||||
*/
|
||||
public class PresentsPrefs
|
||||
{
|
||||
/** Used to load our preferences from a properties file and map them
|
||||
* to the persistent Java preferences repository. */
|
||||
public static PrefsConfig config = new PrefsConfig("rsrc/config/presents");
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
/**
|
||||
* A session observer is registered with the client instance to be notified when the client
|
||||
* establishes and ends their session with the server.
|
||||
*
|
||||
* @see ClientObserver
|
||||
*/
|
||||
public interface SessionObserver
|
||||
{
|
||||
/**
|
||||
* Called immediately before a logon is attempted.
|
||||
*/
|
||||
void clientWillLogon (Client client);
|
||||
|
||||
/**
|
||||
* Called after the client successfully connected to and authenticated with the server. The
|
||||
* entire object system is up and running by the time this method is called.
|
||||
*/
|
||||
void clientDidLogon (Client client);
|
||||
|
||||
/**
|
||||
* For systems that allow switching screen names after logon, this method is called whenever a
|
||||
* screen name change takes place to report that the client object has been replaced to
|
||||
* potential client-side subscribers.
|
||||
*/
|
||||
void clientObjectDidChange (Client client);
|
||||
|
||||
/**
|
||||
* Called after the client has been logged off of the server and has disconnected.
|
||||
*/
|
||||
void clientDidLogoff (Client client);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
/**
|
||||
* 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 interface TimeBaseService extends InvocationService
|
||||
{
|
||||
/**
|
||||
* Used to communicated the result of a {@link TimeBaseService#getTimeOid} request.
|
||||
*/
|
||||
public static interface GotTimeBaseListener extends InvocationListener
|
||||
{
|
||||
/**
|
||||
* Communicates the result of a successful {@link TimeBaseService#getTimeOid} request.
|
||||
*/
|
||||
void gotTimeOid (int timeOid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the oid of the specified time base object be fetched.
|
||||
*/
|
||||
void getTimeOid (Client client, String timeBase, GotTimeBaseListener listener);
|
||||
}
|
||||
Reference in New Issue
Block a user