Obtain our ActionScript dependencies from Maven as well, and publish our swc
artifacts thereto. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6289 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// $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 flash.events.EventDispatcher;
|
||||
|
||||
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 extends EventDispatcher
|
||||
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.
|
||||
*/
|
||||
public function BasicDirector (ctx :PresentsContext)
|
||||
{
|
||||
// save context
|
||||
_ctx = ctx;
|
||||
|
||||
// listen for session start and end
|
||||
var client :Client = ctx.getClient();
|
||||
client.addClientObserver(this);
|
||||
|
||||
// if we're already logged on, fire off a call to fetch services
|
||||
if (client.isLoggedOn()) {
|
||||
// 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 SessionObserver
|
||||
public function clientWillLogon (event :ClientEvent) :void
|
||||
{
|
||||
var client :Client = event.getClient();
|
||||
registerServices(client);
|
||||
}
|
||||
|
||||
// documentation inherited from interface SessionObserver
|
||||
public function clientDidLogon (event :ClientEvent) :void
|
||||
{
|
||||
var client :Client = event.getClient();
|
||||
fetchServices(client);
|
||||
clientObjectUpdated(client);
|
||||
}
|
||||
|
||||
// documentation inherited from interface SessionObserver
|
||||
public function clientObjectDidChange (event :ClientEvent) :void
|
||||
{
|
||||
clientObjectUpdated(event.getClient());
|
||||
}
|
||||
|
||||
// documentation inherited from interface SessionObserver
|
||||
public function clientDidLogoff (event :ClientEvent) :void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 function clientObjectUpdated (client :Client) :void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 function registerServices (client :Client) :void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 function fetchServices (client :Client) :void
|
||||
{
|
||||
}
|
||||
|
||||
/** The application context. */
|
||||
protected var _ctx :PresentsContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
//
|
||||
// $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 flash.events.EventDispatcher;
|
||||
import flash.events.TimerEvent;
|
||||
|
||||
import flash.utils.Timer;
|
||||
|
||||
import com.threerings.util.DelayUtil;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.Throttle;
|
||||
|
||||
import com.threerings.presents.client.InvocationService_ConfirmListener;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.data.InvocationCodes;
|
||||
import com.threerings.presents.data.TimeBaseMarshaller;
|
||||
|
||||
import com.threerings.presents.dobj.DObjectManager;
|
||||
|
||||
import com.threerings.presents.net.AuthResponseData;
|
||||
import com.threerings.presents.net.BootstrapData;
|
||||
import com.threerings.presents.net.Credentials;
|
||||
import com.threerings.presents.net.PingRequest;
|
||||
import com.threerings.presents.net.PongResponse;
|
||||
import com.threerings.presents.net.ThrottleUpdatedMessage;
|
||||
|
||||
public class Client extends EventDispatcher
|
||||
{
|
||||
/** The default port on which the server listens for client connections. */
|
||||
public static const DEFAULT_SERVER_PORTS :Array = [ 47624 ];
|
||||
|
||||
/** Our default maximum outgoing message rate in messages per second. */
|
||||
public static const DEFAULT_MSGS_PER_SECOND :int = 10;
|
||||
|
||||
private static const log :Log = Log.getLog(Client);
|
||||
|
||||
// statically reference classes we require
|
||||
TimeBaseMarshaller;
|
||||
|
||||
public function Client (creds :Credentials)
|
||||
{
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the supplied observer with this client. While registered the observer will receive
|
||||
* notifications of state changes within the client. The function will refuse to register an
|
||||
* already registered observer.
|
||||
*
|
||||
* @see ClientObserver
|
||||
* @see SessionObserver
|
||||
*/
|
||||
public function addClientObserver (observer :SessionObserver) :void
|
||||
{
|
||||
addEventListener(ClientEvent.CLIENT_WILL_LOGON, observer.clientWillLogon);
|
||||
addEventListener(ClientEvent.CLIENT_DID_LOGON, observer.clientDidLogon);
|
||||
addEventListener(ClientEvent.CLIENT_OBJECT_CHANGED, observer.clientObjectDidChange);
|
||||
addEventListener(ClientEvent.CLIENT_DID_LOGOFF, observer.clientDidLogoff);
|
||||
if (observer is ClientObserver) {
|
||||
var cliObs :ClientObserver = (observer as ClientObserver);
|
||||
addEventListener(ClientEvent.CLIENT_FAILED_TO_LOGON, cliObs.clientFailedToLogon);
|
||||
addEventListener(ClientEvent.CLIENT_CONNECTION_FAILED, cliObs.clientConnectionFailed);
|
||||
addEventListener(ClientEvent.CLIENT_WILL_LOGOFF, cliObs.clientWillLogoff);
|
||||
addEventListener(ClientEvent.CLIENT_DID_CLEAR, cliObs.clientDidClear);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the supplied observer. Upon return of this function, the observer will no longer
|
||||
* receive notifications of state changes within the client.
|
||||
*/
|
||||
public function removeClientObserver (observer :SessionObserver) :void
|
||||
{
|
||||
removeEventListener(ClientEvent.CLIENT_WILL_LOGON, observer.clientWillLogon);
|
||||
removeEventListener(ClientEvent.CLIENT_DID_LOGON, observer.clientDidLogon);
|
||||
removeEventListener(ClientEvent.CLIENT_OBJECT_CHANGED, observer.clientObjectDidChange);
|
||||
removeEventListener(ClientEvent.CLIENT_DID_LOGOFF, observer.clientDidLogoff);
|
||||
if (observer is ClientObserver) {
|
||||
var cliObs :ClientObserver = (observer as ClientObserver);
|
||||
removeEventListener(ClientEvent.CLIENT_FAILED_TO_LOGON, cliObs.clientFailedToLogon);
|
||||
removeEventListener(
|
||||
ClientEvent.CLIENT_CONNECTION_FAILED, cliObs.clientConnectionFailed);
|
||||
removeEventListener(ClientEvent.CLIENT_WILL_LOGOFF, cliObs.clientWillLogoff);
|
||||
removeEventListener(ClientEvent.CLIENT_DID_CLEAR, cliObs.clientDidClear);
|
||||
}
|
||||
}
|
||||
|
||||
public function setServer (hostname :String, ports :Array) :void
|
||||
{
|
||||
_hostname = hostname;
|
||||
_ports = ports;
|
||||
}
|
||||
|
||||
public function callLater (fn :Function, args :Array = null) :void
|
||||
{
|
||||
DelayUtil.delayFrame(fn, args);
|
||||
}
|
||||
|
||||
public function getHostname () :String
|
||||
{
|
||||
return _hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ports on which this client is configured to connect.
|
||||
*/
|
||||
public function getPorts () :Array
|
||||
{
|
||||
return _ports;
|
||||
}
|
||||
|
||||
public function getCredentials () :Credentials
|
||||
{
|
||||
return _creds;
|
||||
}
|
||||
|
||||
public function setCredentials (creds :Credentials) :void
|
||||
{
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
public function getVersion () :String
|
||||
{
|
||||
return _version;
|
||||
}
|
||||
|
||||
public function setVersion (version :String) :void
|
||||
{
|
||||
_version = version;
|
||||
}
|
||||
|
||||
public function getAuthResponseData () :AuthResponseData
|
||||
{
|
||||
return _authData;
|
||||
}
|
||||
|
||||
public function setAuthResponseData (data :AuthResponseData) :void
|
||||
{
|
||||
_authData = data;
|
||||
}
|
||||
|
||||
public function getDObjectManager () :DObjectManager
|
||||
{
|
||||
return _omgr;
|
||||
}
|
||||
|
||||
public function getClientOid () :int
|
||||
{
|
||||
return _cloid;
|
||||
}
|
||||
|
||||
public function getClientObject () :ClientObject
|
||||
{
|
||||
return _clobj;
|
||||
}
|
||||
|
||||
public function getInvocationDirector () :InvocationDirector
|
||||
{
|
||||
return _invdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of bootstrap service groups needed by this client.
|
||||
*/
|
||||
public function getBootGroups () :Array
|
||||
{
|
||||
return _bootGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this client as interested in the specified bootstrap services group. Any services
|
||||
* registered as bootstrap services with the supplied group name will be included in this
|
||||
* clients bootstrap services set. This must be called before {@link #logon}.
|
||||
*/
|
||||
public function addServiceGroup (group :String) :void
|
||||
{
|
||||
if (isLoggedOn()) {
|
||||
throw new Error("Services must be registered prior to logon().");
|
||||
}
|
||||
if (_bootGroups.indexOf(group) == -1) {
|
||||
_bootGroups.push(group);
|
||||
}
|
||||
}
|
||||
|
||||
public function getService (clazz :Class) :InvocationService
|
||||
{
|
||||
if (_bstrap != null) {
|
||||
for each (var isvc :InvocationService in _bstrap.services) {
|
||||
if (isvc is clazz) {
|
||||
return isvc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function requireService (clazz :Class) :InvocationService
|
||||
{
|
||||
var isvc :InvocationService = getService(clazz);
|
||||
if (isvc == null) {
|
||||
throw new Error(clazz + " isn't available. I can't bear to go on.");
|
||||
}
|
||||
return isvc;
|
||||
}
|
||||
|
||||
public function getBootstrapData () :BootstrapData
|
||||
{
|
||||
return _bstrap;
|
||||
}
|
||||
|
||||
public function isLoggedOn () :Boolean
|
||||
{
|
||||
return (_clobj != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if the client is currently connected to the server. This can sometimes return false
|
||||
* even though <code>isLoggedOn</code> returns true.
|
||||
*/
|
||||
public function isConnected () :Boolean
|
||||
{
|
||||
return _comm != null && _comm.isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if we have an external logoff request waiting to go through. If the connection is
|
||||
* being throttled, this may return true even though the client <code>isLoggedOn() &&
|
||||
* isConnected()</code>.
|
||||
*/
|
||||
public function isLogoffPending () :Boolean
|
||||
{
|
||||
return _switcher == null && _comm != null && _comm.hasPendingLogoff();
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that this client connect and logon to the server with which it was previously
|
||||
* configured.
|
||||
*
|
||||
* @return false if we're already logged on.
|
||||
*/
|
||||
public function logon () :Boolean
|
||||
{
|
||||
// if we have a communicator, we're already logged on
|
||||
if (_comm != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// let our observers know that we're logging on (this will give directors a chance to
|
||||
// register invocation service groups)
|
||||
notifyObservers(ClientEvent.CLIENT_WILL_LOGON);
|
||||
|
||||
// we need to wait for the CLIENT_WILL_LOGON to have been dispatched before we actually
|
||||
// tell the communicator to logon, so we run this through the callLater pipeline
|
||||
_comm = new Communicator(this);
|
||||
callLater(_comm.logon);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transitions a logged on client from its current server to the specified new server.
|
||||
* Currently this simply logs the client off of its current server (if it is logged on) and
|
||||
* logs it onto the new server, but in the future we may aim to do something fancier.
|
||||
*
|
||||
* <p> If we fail to connect to the new server, the client <em>will not</em> be automatically
|
||||
* reconnected to the old server. It will be in a logged off state. However, it will be
|
||||
* reconfigured with the hostname and ports of the old server so that the caller can notify the
|
||||
* user of the failure and then simply call {@link #logon} to attempt to reconnect to the old
|
||||
* server.
|
||||
*
|
||||
* @param observer an observer that will be notified when we have successfully logged onto the
|
||||
* other server, or if the move failed.
|
||||
*/
|
||||
public function moveToServer (hostname :String, ports :Array,
|
||||
obs :InvocationService_ConfirmListener) :void
|
||||
{
|
||||
// the server switcher will take care of everything for us
|
||||
_switcher = new ServerSwitcher(this, hostname, ports, obs);
|
||||
_switcher.switchServers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the client log off of the server to which it is connected.
|
||||
*
|
||||
* @param abortable if true, the client will call clientWillDisconnect on allthe client
|
||||
* observers and abort the logoff process if any of them return false. If false,
|
||||
* clientWillDisconnect will not be called.
|
||||
*
|
||||
* @return true if the logoff succeeded, false if it failed due to a disagreeable observer.
|
||||
*/
|
||||
public function logoff (abortable :Boolean) :Boolean
|
||||
{
|
||||
if (_comm == null) {
|
||||
log.warning("Ignoring request to log off: not logged on.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// if this is an externally initiated logoff request, clear our active session status
|
||||
if (_switcher == null) {
|
||||
_activeSession = false;
|
||||
}
|
||||
|
||||
// if the request is abortable, let's run it past the observers. if any of them call
|
||||
// preventDefault() then the logoff will be cancelled
|
||||
if (abortable && !notifyObservers(ClientEvent.CLIENT_WILL_LOGOFF)) {
|
||||
_activeSession = true; // restore our active session status
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_tickInterval != null) {
|
||||
_tickInterval.stop();
|
||||
_tickInterval = null;
|
||||
}
|
||||
|
||||
_comm.logoff();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function gotBootstrap (data :BootstrapData, omgr :DObjectManager) :void
|
||||
{
|
||||
// log.debug("Got bootstrap " + data + ".");
|
||||
|
||||
_bstrap = data;
|
||||
_omgr = omgr;
|
||||
_cloid = data.clientOid;
|
||||
|
||||
_invdir.init(omgr, _cloid, this);
|
||||
|
||||
// log.debug("TimeBaseService: " + requireService(TimeBaseService));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every five seconds; ensures that we ping the server if we haven't communicated in a
|
||||
* long while.
|
||||
*/
|
||||
protected function tick (event :TimerEvent) :void
|
||||
{
|
||||
if (_comm == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var now :uint = flash.utils.getTimer();
|
||||
if (now - _comm.getLastWrite() > PingRequest.PING_INTERVAL) {
|
||||
_comm.postMessage(new PingRequest());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the {@link Communicator} if it is experiencing trouble logging on but is still
|
||||
* trying fallback strategies.
|
||||
*/
|
||||
internal function reportLogonTribulations (cause :LogonError) :void
|
||||
{
|
||||
notifyObservers(ClientEvent.CLIENT_FAILED_TO_LOGON, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the invocation director when it successfully subscribes to the client object
|
||||
* immediately following logon.
|
||||
*/
|
||||
public function gotClientObject (clobj :ClientObject) :void
|
||||
{
|
||||
// keep our client object around
|
||||
_clobj = clobj;
|
||||
|
||||
// and start up our tick interval (which will send pings when necessary)
|
||||
if (_tickInterval == null) {
|
||||
_tickInterval = new Timer(5000);
|
||||
_tickInterval.addEventListener(TimerEvent.TIMER, tick);
|
||||
_tickInterval.start();
|
||||
}
|
||||
|
||||
notifyObservers(ClientEvent.CLIENT_DID_LOGON);
|
||||
|
||||
// now that we've logged on at least once, we're in the middle of an active session and
|
||||
// will remain so until we receive an externally initiated logoff request
|
||||
_activeSession = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the invocation director if it fails to subscribe to the client object after logon.
|
||||
*/
|
||||
public function getClientObjectFailed (cause :Error) :void
|
||||
{
|
||||
notifyObservers(ClientEvent.CLIENT_FAILED_TO_LOGON, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the invocation director when it discovers that the client object has changed.
|
||||
*/
|
||||
protected function clientObjectDidChange (clobj :ClientObject) :void
|
||||
{
|
||||
_clobj = clobj;
|
||||
_cloid = clobj.getOid();
|
||||
|
||||
notifyObservers(ClientEvent.CLIENT_OBJECT_CHANGED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to dispatch a client event to any listeners and return the result of
|
||||
* dispatchEvent.
|
||||
*/
|
||||
public function notifyObservers (evtCode :String, cause :Error = null) :Boolean
|
||||
{
|
||||
return dispatchEvent(new ClientEvent(evtCode, this, _activeSession, cause));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the omgr when we receive a pong packet.
|
||||
*/
|
||||
internal function gotPong (pong :PongResponse) :void
|
||||
{
|
||||
// TODO: compute time delta?
|
||||
}
|
||||
|
||||
internal function setOutgoingMessageThrottle (messagesPerSec :int) :void
|
||||
{
|
||||
_comm.postMessage(new ThrottleUpdatedMessage(messagesPerSec));
|
||||
}
|
||||
|
||||
internal function finalizeOutgoingMessageThrottle (messagesPerSec :int) :void
|
||||
{
|
||||
// when the throttle update message goes out to the server
|
||||
_outThrottle.reinit(messagesPerSec, 1000);
|
||||
log.info("Updated outgoing throttle", "messagesPerSec", messagesPerSec);
|
||||
}
|
||||
|
||||
internal function getOutgoingMessageThrottle () :Throttle
|
||||
{
|
||||
return _outThrottle;
|
||||
}
|
||||
|
||||
internal function cleanup (logonError :Error) :void
|
||||
{
|
||||
// clear out our references
|
||||
_comm = null;
|
||||
_bstrap = null;
|
||||
_omgr = null;
|
||||
_clobj = null;
|
||||
_cloid = -1;
|
||||
|
||||
// and let our invocation director know we're logged off
|
||||
_invdir.cleanup();
|
||||
|
||||
// if this was due to a logon error, we can notify our listeners now that we're cleaned up:
|
||||
// they may want to retry logon on another port, or something
|
||||
if (logonError != null) {
|
||||
notifyObservers(ClientEvent.CLIENT_FAILED_TO_LOGON, logonError);
|
||||
} else {
|
||||
notifyObservers(ClientEvent.CLIENT_DID_CLEAR, null);
|
||||
}
|
||||
|
||||
// clear out any server switcher reference
|
||||
_switcher = null;
|
||||
}
|
||||
|
||||
/** The credentials we used to authenticate with the server. */
|
||||
protected var _creds :Credentials;
|
||||
|
||||
/** The version string reported to the server at auth time. */
|
||||
protected var _version :String = "";
|
||||
|
||||
/** The distributed object manager we're using during this session. */
|
||||
protected var _omgr :DObjectManager;
|
||||
|
||||
/** The data associated with our authentication response. */
|
||||
protected var _authData :AuthResponseData;
|
||||
|
||||
/** Our client distributed object id. */
|
||||
protected var _cloid :int = -1;
|
||||
|
||||
/** Our client distributed object. */
|
||||
protected var _clobj :ClientObject;
|
||||
|
||||
/** The game server host. */
|
||||
protected var _hostname :String;
|
||||
|
||||
/** The port on which we connect to the game server. */
|
||||
protected var _ports :Array; /* of int */
|
||||
|
||||
/** The entity that manages our network communications. */
|
||||
protected var _comm :Communicator;
|
||||
|
||||
/** Our outgoing message throttle. */
|
||||
protected var _outThrottle :Throttle = new Throttle(DEFAULT_MSGS_PER_SECOND, 1000);
|
||||
|
||||
/** The set of bootstrap service groups this client cares about. */
|
||||
protected var _bootGroups :Array = new Array(InvocationCodes.GLOBAL_GROUP);
|
||||
|
||||
/** General startup information provided by the server. */
|
||||
protected var _bstrap :BootstrapData;
|
||||
|
||||
/** Manages invocation services. */
|
||||
protected var _invdir :InvocationDirector = new InvocationDirector();
|
||||
|
||||
/** Ticks. */
|
||||
protected var _tickInterval :Timer;
|
||||
|
||||
/** This flag is used to distinguish our *first* willLogon/didLogon and a caller-initiated
|
||||
* willLogoff/didLogoff from similar events generated during server switches. Thus it is true
|
||||
* for most of a normal client session. */
|
||||
protected var _activeSession :Boolean;
|
||||
|
||||
/** Used to temporarily track our server switcher so that we can tell when we're logging off
|
||||
* whether or not we're switching servers or actually ending our session. */
|
||||
protected var _switcher :ServerSwitcher;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// $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 {
|
||||
|
||||
public class ClientAdapter
|
||||
implements ClientObserver
|
||||
{
|
||||
public function ClientAdapter (
|
||||
clientWillLogon :Function = null,
|
||||
clientDidLogon :Function = null,
|
||||
clientObjectDidChange :Function = null,
|
||||
clientDidLogoff :Function = null,
|
||||
clientFailedToLogon :Function = null,
|
||||
clientConnectionFailed :Function = null,
|
||||
clientWillLogoff :Function = null,
|
||||
clientDidClear :Function = null)
|
||||
{
|
||||
_clientWillLogon = clientWillLogon;
|
||||
_clientDidLogon = clientDidLogon;
|
||||
_clientObjectDidChange = clientObjectDidChange;
|
||||
_clientDidLogoff = clientDidLogoff;
|
||||
_clientFailedToLogon = clientFailedToLogon;
|
||||
_clientConnectionFailed = clientConnectionFailed;
|
||||
_clientWillLogoff = clientWillLogoff;
|
||||
_clientDidClear = clientDidClear;
|
||||
}
|
||||
|
||||
// from ClientObserver
|
||||
public function clientWillLogon (event :ClientEvent) :void
|
||||
{
|
||||
if (_clientWillLogon != null) {
|
||||
_clientWillLogon(event);
|
||||
}
|
||||
}
|
||||
|
||||
// from ClientObserver
|
||||
public function clientDidLogon (event :ClientEvent) :void
|
||||
{
|
||||
if (_clientDidLogon != null) {
|
||||
_clientDidLogon(event);
|
||||
}
|
||||
}
|
||||
|
||||
// from ClientObserver
|
||||
public function clientObjectDidChange (event :ClientEvent) :void
|
||||
{
|
||||
if (_clientObjectDidChange != null) {
|
||||
_clientObjectDidChange(event);
|
||||
}
|
||||
}
|
||||
|
||||
// from ClientObserver
|
||||
public function clientDidLogoff (event :ClientEvent) :void
|
||||
{
|
||||
if (_clientDidLogoff != null) {
|
||||
_clientDidLogoff(event);
|
||||
}
|
||||
}
|
||||
|
||||
// from ClientObserver
|
||||
public function clientFailedToLogon (event :ClientEvent) :void
|
||||
{
|
||||
if (_clientFailedToLogon != null) {
|
||||
_clientFailedToLogon(event);
|
||||
}
|
||||
}
|
||||
|
||||
// from ClientObserver
|
||||
public function clientConnectionFailed (event :ClientEvent) :void
|
||||
{
|
||||
if (_clientConnectionFailed != null) {
|
||||
_clientConnectionFailed(event);
|
||||
}
|
||||
}
|
||||
|
||||
// from ClientObserver
|
||||
public function clientWillLogoff (event :ClientEvent) :void
|
||||
{
|
||||
if (_clientWillLogoff != null) {
|
||||
_clientWillLogoff(event);
|
||||
}
|
||||
}
|
||||
|
||||
// from ClientObserver
|
||||
public function clientDidClear (event :ClientEvent) :void
|
||||
{
|
||||
if (_clientDidClear != null) {
|
||||
_clientDidClear(event);
|
||||
}
|
||||
}
|
||||
|
||||
protected var _clientWillLogon :Function;
|
||||
protected var _clientDidLogon :Function;
|
||||
protected var _clientObjectDidChange :Function;
|
||||
protected var _clientDidLogoff :Function;
|
||||
protected var _clientFailedToLogon :Function;
|
||||
protected var _clientConnectionFailed :Function;
|
||||
protected var _clientWillLogoff :Function;
|
||||
protected var _clientDidClear :Function;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
//
|
||||
// $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 flash.events.Event;
|
||||
import flash.events.TimerEvent;
|
||||
import flash.utils.Timer;
|
||||
import flash.utils.getTimer; // function import
|
||||
|
||||
import com.threerings.util.ClassUtil;
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Maps;
|
||||
import com.threerings.util.Log;
|
||||
|
||||
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.ObjectAccessError;
|
||||
import com.threerings.presents.dobj.ObjectDestroyedEvent;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
|
||||
import com.threerings.presents.net.BootstrapNotification;
|
||||
import com.threerings.presents.net.EventNotification;
|
||||
import com.threerings.presents.net.DownstreamMessage;
|
||||
import com.threerings.presents.net.FailureResponse;
|
||||
import com.threerings.presents.net.ForwardEventRequest;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
private static const log :Log = Log.getLog(ClientDObjectMgr);
|
||||
|
||||
/**
|
||||
* 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 function ClientDObjectMgr (comm :Communicator, client :Client)
|
||||
{
|
||||
_comm = comm;
|
||||
_client = client;
|
||||
|
||||
// register a flush interval
|
||||
_flushInterval = new Timer(FLUSH_INTERVAL);
|
||||
_flushInterval.addEventListener(TimerEvent.TIMER, flushObjects);
|
||||
_flushInterval.start();
|
||||
}
|
||||
|
||||
// documentation inherited from interface DObjectManager
|
||||
public function isManager (object :DObject) :Boolean
|
||||
{
|
||||
// we are never authoritative in the present implementation
|
||||
return false;
|
||||
}
|
||||
|
||||
// inherit documentation from the interface DObjectManager
|
||||
public function subscribeToObject (oid :int, target :Subscriber) :void
|
||||
{
|
||||
if (oid <= 0) {
|
||||
target.requestFailed(oid, new ObjectAccessError("Invalid oid " + oid + "."));
|
||||
} else {
|
||||
doSubscribe(oid, target);
|
||||
}
|
||||
}
|
||||
|
||||
// inherit documentation from the interface DObjectManager
|
||||
public function unsubscribeFromObject (oid :int, target :Subscriber) :void
|
||||
{
|
||||
doUnsubscribe(oid, target);
|
||||
}
|
||||
|
||||
// inherit documentation from the interface
|
||||
public function postEvent (event :DEvent) :void
|
||||
{
|
||||
// send a forward event request to the server
|
||||
_comm.postMessage(new ForwardEventRequest(event));
|
||||
}
|
||||
|
||||
// inherit documentation from the interface
|
||||
public function removedLastSubscriber (
|
||||
obj :DObject, deathWish :Boolean) :void
|
||||
{
|
||||
// if this object has a registered flush delay, don't can it just
|
||||
// yet, just slip it onto the flush queue
|
||||
for each (var dclass :Class in _delays.keys()) {
|
||||
if (obj is dclass) {
|
||||
var expire :Number = getTimer() + Number(_delays.get(dclass));
|
||||
_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 function registerFlushDelay (objclass :Class, delay :Number) :void
|
||||
{
|
||||
_delays.put(objclass, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the communicator when a downstream message arrives from
|
||||
* the network layer. We queue it up for processing and request some
|
||||
* processing time on the main thread.
|
||||
*/
|
||||
public function processMessage (msg :DownstreamMessage) :void
|
||||
{
|
||||
// do the proper thing depending on the object
|
||||
if (msg is EventNotification) {
|
||||
var evt :DEvent = (msg as EventNotification).getEvent();
|
||||
// log.info("Dispatch event: " + evt);
|
||||
dispatchEvent(evt);
|
||||
|
||||
} else if (msg is BootstrapNotification) {
|
||||
_client.gotBootstrap((msg as BootstrapNotification).getData(), this);
|
||||
|
||||
} else if (msg is ObjectResponse) {
|
||||
registerObjectAndNotify((msg as ObjectResponse).getObject());
|
||||
|
||||
} else if (msg is UnsubscribeResponse) {
|
||||
var oid :int = (msg as UnsubscribeResponse).getOid();
|
||||
if (_dead.remove(oid) == null) {
|
||||
log.warning("Received unsub ACK from unknown object [oid=" + oid + "].");
|
||||
}
|
||||
|
||||
} else if (msg is FailureResponse) {
|
||||
notifyFailure((msg as FailureResponse).getOid(), (msg as FailureResponse).getMessage());
|
||||
|
||||
} else if (msg is PongResponse) {
|
||||
_client.gotPong(msg as PongResponse);
|
||||
|
||||
} else if (msg is UpdateThrottleMessage) {
|
||||
_client.setOutgoingMessageThrottle((msg as UpdateThrottleMessage).messagesPerSec);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new event arrives from the server that should be
|
||||
* dispatched to subscribers here on the client.
|
||||
*/
|
||||
protected function dispatchEvent (event :DEvent) :void
|
||||
{
|
||||
// if this is a compound event, we need to process its contained
|
||||
// events in order
|
||||
if (event is CompoundEvent) {
|
||||
var events :Array = (event as CompoundEvent).getEvents();
|
||||
var ecount :int = events.length;
|
||||
for (var ii :int = 0; ii < ecount; ii++) {
|
||||
dispatchEvent(events[ii] as DEvent);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// look up the object on which we're dispatching this event
|
||||
var toid :int = event.getTargetOid();
|
||||
var target :DObject = (_ocache.get(toid) as DObject);
|
||||
if (target == null) {
|
||||
if (_dead.get(toid) == null) {
|
||||
log.warning("Unable to dispatch event on non-proxied " +
|
||||
"object [event=" + event + "].");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// apply the event to the object
|
||||
var notify :Boolean = event.applyToObject(target);
|
||||
|
||||
// if this is an object destroyed event, we need to remove the
|
||||
// object from our object table
|
||||
if (event is ObjectDestroyedEvent) {
|
||||
// log.info("Pitching destroyed object " +
|
||||
// "[oid=" + toid + ", class=" +
|
||||
// StringUtil.shortClassName(target) + "].");
|
||||
_ocache.remove(toid);
|
||||
}
|
||||
|
||||
// have the object pass this event on to its listeners
|
||||
if (notify) {
|
||||
target.notifyListeners(event);
|
||||
}
|
||||
|
||||
} catch (e :Error) {
|
||||
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 function registerObjectAndNotify (obj :DObject) :void
|
||||
{
|
||||
// let the object know that we'll be managing it
|
||||
obj.setManager(this);
|
||||
|
||||
var oid :int = obj.getOid();
|
||||
// stick the object into the proxy object table
|
||||
_ocache.put(oid, obj);
|
||||
|
||||
// let the penders know that the object is available
|
||||
var req :PendingRequest = (_penders.remove(oid) as PendingRequest);
|
||||
if (req == null) {
|
||||
log.warning("Got object, but no one cares?! " +
|
||||
"[oid=" + oid + ", obj=" + obj + "].");
|
||||
return;
|
||||
}
|
||||
// log.debug("Got object: pendReq=" + req);
|
||||
|
||||
for each (var target :Subscriber in req.targets) {
|
||||
// log.debug("Notifying subscriber: " + target);
|
||||
// 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 function notifyFailure (oid :int, message :String) :void
|
||||
{
|
||||
// let the penders know that the object is not available
|
||||
var req :PendingRequest = (_penders.remove(oid) as PendingRequest);
|
||||
if (req == null) {
|
||||
log.warning("Failed to get object, but no one cares?! [oid=" + oid + "].");
|
||||
return;
|
||||
}
|
||||
|
||||
for each (var target :Subscriber in req.targets) {
|
||||
// and let them know that the object is in
|
||||
target.requestFailed(oid, new ObjectAccessError(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 function doSubscribe (oid :int, target :Subscriber) :void
|
||||
{
|
||||
// log.info("doSubscribe: " + oid + ": " + target);
|
||||
|
||||
// first see if we've already got the object in our table
|
||||
var obj :DObject = (_ocache.get(oid) as DObject);
|
||||
if (obj != null) {
|
||||
// clear the object out of the flush table if it's in there
|
||||
_flushes.remove(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
|
||||
var req :PendingRequest = (_penders.get(oid) as PendingRequest);
|
||||
if (req != null) {
|
||||
// add this subscriber to the list of subscribers to be
|
||||
// notified when the request is satisfied
|
||||
req.addTarget(target);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise we need to create a new request
|
||||
req = new PendingRequest(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 function doUnsubscribe (oid :int, target :Subscriber) :void
|
||||
{
|
||||
var dobj :DObject = (_ocache.get(oid) as DObject);
|
||||
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 function flushObject (obj :DObject) :void
|
||||
{
|
||||
// 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
|
||||
var ooid :int = 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 function flushObjects (event :TimerEvent) :void
|
||||
{
|
||||
var now :Number = getTimer();
|
||||
for each (var oid :int in _flushes.keys()) {
|
||||
var rec :FlushRecord = (_flushes.get(oid) as FlushRecord);
|
||||
if (rec.expire <= now) {
|
||||
_flushes.remove(oid);
|
||||
flushObject(rec.obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A reference to the communicator that sends and receives messages
|
||||
* for this client. */
|
||||
protected var _comm :Communicator;
|
||||
|
||||
/** A reference to our client instance. */
|
||||
protected var _client :Client;
|
||||
|
||||
/** All of the distributed objects that are active on this client. */
|
||||
protected var _ocache :Map = Maps.newMapOf(int);
|
||||
|
||||
/** Objects that have been marked for death. */
|
||||
protected var _dead :Map = Maps.newMapOf(int);
|
||||
|
||||
/** Pending object subscriptions. */
|
||||
protected var _penders :Map = Maps.newMapOf(int);
|
||||
|
||||
/** A mapping from distributed object class to flush delay. */
|
||||
protected var _delays :Map = Maps.newMapOf(int);
|
||||
|
||||
/** A set of objects waiting to be flushed. */
|
||||
protected var _flushes :Map = Maps.newMapOf(int);
|
||||
|
||||
/** Flushes objects every now and again. */
|
||||
protected var _flushInterval :Timer;
|
||||
|
||||
/** Flush expired objects every 30 seconds. */
|
||||
protected static const FLUSH_INTERVAL :Number = 30 * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
|
||||
class PendingRequest
|
||||
{
|
||||
public var oid :int;
|
||||
public var targets :Array = new Array();
|
||||
|
||||
public function PendingRequest (oid :int)
|
||||
{
|
||||
this.oid = oid;
|
||||
}
|
||||
|
||||
public function addTarget (target :Subscriber) :void
|
||||
{
|
||||
targets.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
/** Used to manage pending object flushes. */
|
||||
class FlushRecord
|
||||
{
|
||||
/** The object to be flushed. */
|
||||
public var obj :DObject;
|
||||
|
||||
/** The time at which we flush it. */
|
||||
public var expire :Number;
|
||||
|
||||
public function FlushRecord (obj :DObject, expire :Number)
|
||||
{
|
||||
this.obj = obj;
|
||||
this.expire = expire;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// $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 flash.events.Event;
|
||||
|
||||
public class ClientEvent extends Event
|
||||
{
|
||||
public static const CLIENT_WILL_LOGON :String = "clientWillLogon";
|
||||
public static const CLIENT_DID_LOGON :String = "clientDidLogon";
|
||||
public static const CLIENT_FAILED_TO_LOGON :String = "clientFailedLogon";
|
||||
public static const CLIENT_OBJECT_CHANGED :String = "clobjChanged";
|
||||
public static const CLIENT_CONNECTION_FAILED :String = "clientConnFailed";
|
||||
/** The logoff itself can be cancelled if a listener calls preventDefault() on this event. */
|
||||
public static const CLIENT_WILL_LOGOFF :String = "clientWillLogoff";
|
||||
public static const CLIENT_DID_LOGOFF :String = "clientDidLogoff";
|
||||
public static const CLIENT_DID_CLEAR :String = "clientDidClear";
|
||||
|
||||
public function ClientEvent (
|
||||
type :String, client :Client, isSwitching :Boolean, cause :Error = null)
|
||||
{
|
||||
super(type, false, (type === CLIENT_WILL_LOGOFF));
|
||||
_client = client;
|
||||
_isSwitching = isSwitching;
|
||||
_cause = cause;
|
||||
}
|
||||
|
||||
public function getClient () :Client
|
||||
{
|
||||
return _client;
|
||||
}
|
||||
|
||||
public function getCause () :Error
|
||||
{
|
||||
return _cause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the client is currently switching servers rather than starting a fresh
|
||||
* session or ending a session due to user requested logoff.
|
||||
*/
|
||||
public function isSwitchingServers () :Boolean
|
||||
{
|
||||
return _isSwitching;
|
||||
}
|
||||
|
||||
override public function clone () :Event
|
||||
{
|
||||
return new ClientEvent(type, _client, _isSwitching, _cause);
|
||||
}
|
||||
|
||||
/** The client that generated this client event. */
|
||||
protected var _client :Client;
|
||||
|
||||
/** Whether or not we're currently switching servers. */
|
||||
protected var _isSwitching :Boolean;
|
||||
|
||||
/** The error that caused this event, if applicable. */
|
||||
protected var _cause :Error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// $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, <code>clientDidLogon</code> will
|
||||
* be called after the client successfully logs on to the server and
|
||||
* <code>clientDidLogoff</code> will be called after the client logs off
|
||||
* of the server. If logon fails for any reson,
|
||||
* <code>clientFailedToLogon</code> will be called to explain the failure.
|
||||
*
|
||||
* <p> <code>clientWillLogoff</code> 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
|
||||
* <code>clientWillLogoff</code></em>).
|
||||
*
|
||||
* <p> If the client connection fails unexpectedly,
|
||||
* <code>clientConnectionFailed</code> will be called to let the
|
||||
* observers know that we lost our connection to the
|
||||
* server. <code>clientDidLogoff</code> 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 LogonError} 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).
|
||||
*/
|
||||
function clientFailedToLogon (event :ClientEvent) :void;
|
||||
|
||||
/**
|
||||
* Called when the connection to the server went away for some
|
||||
* unexpected reason. This will be followed by a call to
|
||||
* <code>clientDidLogoff</code>.
|
||||
*/
|
||||
function clientConnectionFailed (event :ClientEvent) :void;
|
||||
|
||||
/**
|
||||
* Called when an abortable logoff request is made. If the observer
|
||||
* calls event.preventDefault() then it will abort the logoff request.
|
||||
*/
|
||||
function clientWillLogoff (event :ClientEvent) :void;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
function clientDidClear (event :ClientEvent) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
//
|
||||
// $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 flash.errors.IOError;
|
||||
import flash.events.Event;
|
||||
import flash.events.IOErrorEvent;
|
||||
import flash.events.SecurityErrorEvent;
|
||||
import flash.events.TimerEvent;
|
||||
|
||||
import flash.net.Socket;
|
||||
import flash.utils.ByteArray;
|
||||
import flash.utils.Endian;
|
||||
import flash.utils.Timer;
|
||||
|
||||
import com.threerings.util.Log;
|
||||
|
||||
import com.threerings.io.FrameAvailableEvent;
|
||||
import com.threerings.io.FrameReader;
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
import com.threerings.presents.data.AuthCodes;
|
||||
import com.threerings.presents.net.AuthRequest;
|
||||
import com.threerings.presents.net.AuthResponse;
|
||||
import com.threerings.presents.net.AuthResponseData;
|
||||
import com.threerings.presents.net.DownstreamMessage;
|
||||
import com.threerings.presents.net.LogoffRequest;
|
||||
import com.threerings.presents.net.UpstreamMessage;
|
||||
import com.threerings.presents.net.ThrottleUpdatedMessage;
|
||||
|
||||
public class Communicator
|
||||
{
|
||||
public function Communicator (client :Client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public function logon () :void
|
||||
{
|
||||
// create our input/output business
|
||||
_outBuffer = new ByteArray();
|
||||
_outBuffer.endian = Endian.BIG_ENDIAN;
|
||||
_outStream = new ObjectOutputStream(_outBuffer);
|
||||
_inStream = new ObjectInputStream(null, { invDir: _client.getInvocationDirector() });
|
||||
|
||||
attemptLogon(0);
|
||||
}
|
||||
|
||||
public function logoff () :void
|
||||
{
|
||||
if (_socket == null) {
|
||||
return;
|
||||
}
|
||||
postMessage(new LogoffRequest());
|
||||
}
|
||||
|
||||
public function postMessage (msg :UpstreamMessage) :void
|
||||
{
|
||||
_outq.push(msg);
|
||||
if (_writer != null) {
|
||||
sendPendingMessages(null);
|
||||
|
||||
} else {
|
||||
log.warning("Posting message prior to opening socket", "msg", msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if the communicator currently has an open channel to the server. This is guaranteed
|
||||
* to return false while the logoff event is being dispatched. It also returns true during
|
||||
* authentication, but prior to the client appearing to be logged on.
|
||||
*/
|
||||
public function isConnected () :Boolean
|
||||
{
|
||||
return _writer != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects is the communicator has a logoff request pending, i.e. will logoff in the near
|
||||
* future.
|
||||
*/
|
||||
public function hasPendingLogoff () :Boolean
|
||||
{
|
||||
// check for a logoff message
|
||||
for each (var message :UpstreamMessage in _outq) {
|
||||
if (message is LogoffRequest) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to logon on using the port at the specified index.
|
||||
*/
|
||||
protected function attemptLogon (portIdx :int) :Boolean
|
||||
{
|
||||
var ports :Array = _client.getPorts();
|
||||
_portIdx = portIdx; // note the port we're about to try
|
||||
if (_portIdx >= ports.length) {
|
||||
return false;
|
||||
}
|
||||
if (_portIdx != 0) {
|
||||
_client.reportLogonTribulations(new LogonError(AuthCodes.TRYING_NEXT_PORT, true));
|
||||
removeListeners();
|
||||
}
|
||||
|
||||
// create the socket and set up listeners
|
||||
_socket = new Socket();
|
||||
_socket.endian = Endian.BIG_ENDIAN;
|
||||
_socket.addEventListener(Event.CONNECT, socketOpened);
|
||||
_socket.addEventListener(IOErrorEvent.IO_ERROR, socketError);
|
||||
_socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, socketError);
|
||||
_socket.addEventListener(Event.CLOSE, socketClosed);
|
||||
|
||||
_frameReader = new FrameReader(_socket);
|
||||
_frameReader.addEventListener(FrameAvailableEvent.FRAME_AVAILABLE, inputFrameReceived);
|
||||
|
||||
var host :String = _client.getHostname();
|
||||
var pport :int = ports[0];
|
||||
var ppidx :int = Math.max(0, ports.indexOf(pport));
|
||||
var port :int = (ports[(_portIdx + ppidx) % ports.length] as int);
|
||||
|
||||
log.info("Connecting", "host", host, "port", port, "svcs", _client.getBootGroups());
|
||||
_socket.connect(host, port);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function removeListeners () :void
|
||||
{
|
||||
_socket.removeEventListener(Event.CONNECT, socketOpened);
|
||||
_socket.removeEventListener(IOErrorEvent.IO_ERROR, socketError);
|
||||
_socket.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, socketError);
|
||||
_socket.removeEventListener(Event.CLOSE, socketClosed);
|
||||
|
||||
_frameReader.removeEventListener(FrameAvailableEvent.FRAME_AVAILABLE, inputFrameReceived);
|
||||
_frameReader.shutdown();
|
||||
}
|
||||
|
||||
protected function shutdown (logonError :Error) :void
|
||||
{
|
||||
if (_socket != null) {
|
||||
if (_socket.connected) {
|
||||
try {
|
||||
_socket.close();
|
||||
} catch (err :Error) {
|
||||
log.warning("Error closing failed socket", err);
|
||||
}
|
||||
}
|
||||
removeListeners();
|
||||
_socket = null;
|
||||
_outStream = null;
|
||||
_inStream = null;
|
||||
_frameReader = null;
|
||||
_outBuffer = null;
|
||||
}
|
||||
|
||||
if (_writer != null) {
|
||||
_writer.stop();
|
||||
_writer = null;
|
||||
}
|
||||
|
||||
// if we never got our client object, we never dispatched a DID_LOGON and therefore we
|
||||
// don't want to dispatch a DID_LOGOFF, the observers basically never know anything ever
|
||||
// happened
|
||||
if (_client.getClientObject() != null) {
|
||||
_client.notifyObservers(ClientEvent.CLIENT_DID_LOGOFF, null);
|
||||
}
|
||||
_client.cleanup(logonError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends all pending messages from our outgoing message queue. If we hit our throttle while
|
||||
* sending, we stop and wait for the next time around when we'll try sending them again.
|
||||
*/
|
||||
protected function sendPendingMessages (event :TimerEvent) :void
|
||||
{
|
||||
while (_outq.length > 0) {
|
||||
// if we've exceeded our throttle, stop for now
|
||||
if (_client.getOutgoingMessageThrottle().throttleOp()) {
|
||||
if (!_notedThrottle) {
|
||||
log.info("Throttling outgoing messages", "queue", _outq.length,
|
||||
"throttle", _client.getOutgoingMessageThrottle());
|
||||
_notedThrottle = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
_notedThrottle = false;
|
||||
|
||||
// grab the next message from the queue and send it
|
||||
var msg :UpstreamMessage = (_outq.shift() as UpstreamMessage);
|
||||
sendMessage(msg);
|
||||
|
||||
// if this was a logoff request, shutdown
|
||||
if (msg is LogoffRequest) {
|
||||
shutdown(null);
|
||||
} else if (msg is ThrottleUpdatedMessage) {
|
||||
_client.finalizeOutgoingMessageThrottle(ThrottleUpdatedMessage(msg).messagesPerSec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single message to our outgoing socket.
|
||||
*/
|
||||
protected function sendMessage (msg :UpstreamMessage) :void
|
||||
{
|
||||
if (_outStream == null) {
|
||||
log.warning("No socket, dropping msg", "msg", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// write the message (ends up in _outBuffer)
|
||||
_outStream.writeObject(msg);
|
||||
|
||||
// Log.debug("outBuffer: " + StringUtil.unhexlate(_outBuffer));
|
||||
|
||||
// Frame it by writing the length, then the bytes.
|
||||
// We add 4 to the length, because the length is of the entire frame
|
||||
// including the 4 bytes used to encode the length!
|
||||
_socket.writeInt(_outBuffer.length + 4);
|
||||
_socket.writeBytes(_outBuffer);
|
||||
_socket.flush();
|
||||
|
||||
// clean up the output buffer
|
||||
_outBuffer.length = 0;
|
||||
_outBuffer.position = 0;
|
||||
|
||||
// make a note of our most recent write time
|
||||
updateWriteStamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a frame of data from the server is ready to be decoded into a DownstreamMessage.
|
||||
*/
|
||||
protected function inputFrameReceived (event :FrameAvailableEvent) :void
|
||||
{
|
||||
// convert the frame data into a message from the server
|
||||
var frameData :ByteArray = event.getFrameData();
|
||||
_inStream.setSource(frameData);
|
||||
var msg :DownstreamMessage;
|
||||
try {
|
||||
msg = (_inStream.readObject(DownstreamMessage) as DownstreamMessage);
|
||||
} catch (e :Error) {
|
||||
log.warning("Error processing downstream message", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (frameData.bytesAvailable > 0) {
|
||||
log.warning("Beans! We didn't fully read a frame, is there a bug in streaming code?",
|
||||
"bytesLeftOver", frameData.bytesAvailable, "msg", msg);
|
||||
}
|
||||
|
||||
if (_omgr != null) {
|
||||
// if we're logged on, then just do the normal thing
|
||||
_omgr.processMessage(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, this would be the AuthResponse to our logon attempt
|
||||
var rsp :AuthResponse = (msg as AuthResponse);
|
||||
var data :AuthResponseData = rsp.getData();
|
||||
if (data.code !== AuthResponseData.SUCCESS) {
|
||||
shutdown(new Error(data.code));
|
||||
return;
|
||||
}
|
||||
|
||||
// logon success
|
||||
_omgr = new ClientDObjectMgr(this, _client);
|
||||
_client.setAuthResponseData(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the connection to the server was successfully opened.
|
||||
*/
|
||||
protected function socketOpened (event :Event) :void
|
||||
{
|
||||
// reset our port index now that we're successfully logged on; this way if the socket
|
||||
// fails, we won't think that we're in the middle of trying to logon
|
||||
_portIdx = -1;
|
||||
|
||||
// check for a logoff message
|
||||
if (hasPendingLogoff()) {
|
||||
// don't bother authing, just bail
|
||||
log.info("Logged off prior to socket opening, shutting down");
|
||||
shutdown(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// send our authentication request (do so directly rather than putting it on the outgoing
|
||||
// write queue)
|
||||
sendMessage(new AuthRequest(_client.getCredentials(), _client.getVersion(),
|
||||
_client.getBootGroups()));
|
||||
|
||||
// kick off our writer thread now that we know we're ready to write
|
||||
_writer = new Timer(1);
|
||||
_writer.addEventListener(TimerEvent.TIMER, sendPendingMessages);
|
||||
_writer.start();
|
||||
|
||||
// clear the queue, the server doesn't like anything sent prior to auth
|
||||
_outq.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when there is an io error with the socket.
|
||||
*/
|
||||
protected function socketError (event :Event) :void
|
||||
{
|
||||
// if we're still trying ports, try the next one.
|
||||
if (_portIdx != -1) {
|
||||
if (attemptLogon(_portIdx+1)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// total failure
|
||||
log.warning("Socket error", "event", event, "target", event.target, new Error());
|
||||
shutdown(new Error(AuthCodes.NETWORK_ERROR));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the connection to the server was closed.
|
||||
*/
|
||||
protected function socketClosed (event :Event) :void
|
||||
{
|
||||
log.info("Socket was closed", "event", event);
|
||||
_client.notifyObservers(ClientEvent.CLIENT_CONNECTION_FAILED);
|
||||
// if we hadn't loaded our client object yet, behave as if this was a logon failure because
|
||||
// we failed before we dispatched a DID_LOGON
|
||||
var wasLoggedOn :Boolean = (_client.getClientObject() != null);
|
||||
shutdown(wasLoggedOn ? null : new LogonError(AuthCodes.NETWORK_ERROR));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time at which we last sent a packet to the server.
|
||||
*/
|
||||
internal function getLastWrite () :uint
|
||||
{
|
||||
return _lastWrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a note of the time at which we last communicated with the server.
|
||||
*/
|
||||
internal function updateWriteStamp () :void
|
||||
{
|
||||
_lastWrite = flash.utils.getTimer();
|
||||
}
|
||||
|
||||
protected var _client :Client;
|
||||
protected var _omgr :ClientDObjectMgr;
|
||||
|
||||
protected var _outBuffer :ByteArray;
|
||||
protected var _outStream :ObjectOutputStream;
|
||||
|
||||
protected var _inStream :ObjectInputStream;
|
||||
protected var _frameReader :FrameReader;
|
||||
|
||||
protected var _socket :Socket;
|
||||
protected var _lastWrite :uint;
|
||||
|
||||
protected var _outq :Array = new Array();
|
||||
protected var _writer :Timer;
|
||||
protected var _notedThrottle :Boolean = false;
|
||||
|
||||
protected const log :Log = Log.getLog(this);
|
||||
|
||||
/** The current port we'll try to connect to. */
|
||||
protected var _portIdx :int = -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// $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 {
|
||||
|
||||
public class ConfirmAdapter extends InvocationAdapter
|
||||
implements InvocationService_ConfirmListener
|
||||
{
|
||||
/**
|
||||
* Construct a confirm adapter.
|
||||
* The processed function may be null.
|
||||
*/
|
||||
public function ConfirmAdapter (processed :Function, failed :Function)
|
||||
{
|
||||
super(failed);
|
||||
_processed = processed;
|
||||
}
|
||||
|
||||
// documentation inherited from interface ConfirmListener
|
||||
public function requestProcessed () :void
|
||||
{
|
||||
if (_processed != null) {
|
||||
_processed();
|
||||
}
|
||||
}
|
||||
|
||||
protected var _processed :Function;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
public class InvocationAdapter
|
||||
implements InvocationService_InvocationListener
|
||||
{
|
||||
/**
|
||||
* Construct an InvocationAdapter that will call the specified
|
||||
* function on error.
|
||||
*/
|
||||
public function InvocationAdapter (failedFunc :Function)
|
||||
{
|
||||
_failedFunc = failedFunc;
|
||||
}
|
||||
|
||||
// documentation inherited from interface InvocationListener
|
||||
public function requestFailed (cause :String) :void
|
||||
{
|
||||
_failedFunc(cause);
|
||||
}
|
||||
|
||||
/** The Function to call when we've recevied our failure response. */
|
||||
protected var _failedFunc :Function;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// $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.util.Log;
|
||||
|
||||
/**
|
||||
* Provides the basic functionality used to dispatch invocation
|
||||
* notification events.
|
||||
*/
|
||||
public /* abstract */ class InvocationDecoder
|
||||
{
|
||||
/** The receiver for which we're decoding and dipatching
|
||||
* notifications. */
|
||||
public var receiver :InvocationReceiver;
|
||||
|
||||
/**
|
||||
* Returns the generated hash code that is used to identify this
|
||||
* invocation notification service.
|
||||
*/
|
||||
public /* abstract */ function getReceiverCode () :String
|
||||
{
|
||||
return "abstract";
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the specified method to our receiver.
|
||||
*/
|
||||
public function dispatchNotification (methodId :int, args :Array) :void
|
||||
{
|
||||
Log.getLog(this).warning("Requested to dispatch unknown method " +
|
||||
"[receiver=" + receiver + ", methodId=" + methodId +
|
||||
", args=" + args + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
//
|
||||
// $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 flash.errors.IllegalOperationError;
|
||||
import flash.utils.getTimer; // function import
|
||||
|
||||
import com.threerings.util.Boxed;
|
||||
import com.threerings.util.Map;
|
||||
import com.threerings.util.Maps;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.Short;
|
||||
|
||||
import com.threerings.presents.data.InvocationMarshaller_ListenerMarshaller;
|
||||
|
||||
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.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.ObjectAccessError;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
import com.threerings.presents.dobj.SubscriberAdapter;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
|
||||
public class InvocationDirector
|
||||
implements EventListener
|
||||
{
|
||||
private static const log :Log = Log.getLog(InvocationDirector);
|
||||
|
||||
public function init (omgr :DObjectManager, cloid :int, client :Client) :void
|
||||
{
|
||||
if (_clobj != null) {
|
||||
log.warning("Zoiks, client object around during invmgr init!");
|
||||
cleanup();
|
||||
}
|
||||
|
||||
_omgr = omgr;
|
||||
_client = client;
|
||||
|
||||
_omgr.subscribeToObject(cloid, new SubscriberAdapter(
|
||||
this.gotClientObject, this.gotClientObjectFailed));
|
||||
}
|
||||
|
||||
public function cleanup () :void
|
||||
{
|
||||
// wipe our client object, receiver mappings and listener mappings
|
||||
_clobj = null;
|
||||
|
||||
// also reset our counters
|
||||
_requestId = 0;
|
||||
_receiverId = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an invocation notification receiver by way of its notification event decoder.
|
||||
*/
|
||||
public function registerReceiver (decoder :InvocationDecoder) :void
|
||||
{
|
||||
_reclist.push(decoder);
|
||||
|
||||
// if we're already online, assign a recevier id now
|
||||
if (_clobj != null) {
|
||||
assignReceiverId(decoder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a receiver registration.
|
||||
*/
|
||||
public function unregisterReceiver (receiverCode :String) :void
|
||||
{
|
||||
// remove the receiver from the list
|
||||
for (var ii :int = _reclist.length - 1; ii >= 0; ii--) {
|
||||
var decoder :InvocationDecoder = (_reclist[ii] as InvocationDecoder);
|
||||
if (decoder.getReceiverCode() === receiverCode) {
|
||||
_reclist.splice(ii, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// if we're logged on, clear out any receiver id mapping
|
||||
if (_clobj != null) {
|
||||
var rreg :InvocationReceiver_Registration =
|
||||
(_clobj.receivers.get(receiverCode) as InvocationReceiver_Registration);
|
||||
if (rreg == null) {
|
||||
log.warning("Receiver unregistered for which we have no id to code mapping " +
|
||||
"[code=" + receiverCode + "].");
|
||||
} else {
|
||||
var odecoder :Object = _receivers.remove(rreg.receiverId);
|
||||
// log.info("Cleared receiver " + StringUtil.shortClassName(odecoder) +
|
||||
// " " + rreg + ".");
|
||||
}
|
||||
_clobj.removeFromReceivers(receiverCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a receiver id to this decoder and publishes it in the {@link ClientObject#receivers}
|
||||
* field.
|
||||
*/
|
||||
internal function assignReceiverId (decoder :InvocationDecoder) :void
|
||||
{
|
||||
var reg :InvocationReceiver_Registration =
|
||||
new InvocationReceiver_Registration(decoder.getReceiverCode(), nextReceiverId());
|
||||
_clobj.addToReceivers(reg);
|
||||
_receivers.put(reg.receiverId, decoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we log on; generates mappings for all receivers registered prior to logon.
|
||||
*/
|
||||
internal function assignReceiverIds () :void
|
||||
{
|
||||
_clobj.startTransaction();
|
||||
try {
|
||||
for each (var decoder :InvocationDecoder in _reclist) {
|
||||
assignReceiverId(decoder);
|
||||
}
|
||||
} finally {
|
||||
_clobj.commitTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a transaction that allows multiple invocation service requests to be batched into a
|
||||
* single message and sent to the server all at once.
|
||||
*
|
||||
* <p> When the transaction is complete, the caller must call {@link #commitTransaction} to
|
||||
* cause the requests to be sent. Failure to do so will render the entire invocation services
|
||||
* non-functional.
|
||||
*/
|
||||
public function startTransaction () :void
|
||||
{
|
||||
// just increment our transaction nesting count, sendRequest will handle everything else
|
||||
_tcount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits a transaction started with {@link #startTransaction}.
|
||||
*/
|
||||
public function commitTransaction () :void
|
||||
{
|
||||
if (_tcount <= 0) {
|
||||
throw new IllegalOperationError("Cannot commit: not involved in a transaction");
|
||||
}
|
||||
if (--_tcount == 0) {
|
||||
for each (var event :CompoundEvent in _tevents) {
|
||||
event.commit(_omgr);
|
||||
}
|
||||
_tevents = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the specified invocation request be packaged up and sent to the supplied
|
||||
* invocation oid.
|
||||
*/
|
||||
public function sendRequest (invOid :int, invCode :int, methodId :int, args :Array) :void
|
||||
{
|
||||
if (_clobj == null) { // sanitus checkem
|
||||
throw new Error("Can't send invocation request, have no ClientObject.");
|
||||
}
|
||||
|
||||
// configure any invocation listener marshallers among the args
|
||||
for each (var arg :Object in args) {
|
||||
if (arg is InvocationMarshaller_ListenerMarshaller) {
|
||||
var lm :InvocationMarshaller_ListenerMarshaller =
|
||||
(arg as InvocationMarshaller_ListenerMarshaller);
|
||||
lm.requestId = nextRequestId();
|
||||
lm.mapStamp = getTimer();
|
||||
|
||||
// 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 and dispatch it
|
||||
var req :InvocationRequestEvent =
|
||||
new InvocationRequestEvent(invOid, invCode, methodId, args);
|
||||
if (_tcount > 0) {
|
||||
var event :CompoundEvent = _tevents[invOid];
|
||||
if (event == null) {
|
||||
_tevents[invOid] = (event = new CompoundEvent(invOid));
|
||||
}
|
||||
event.postEvent(req);
|
||||
} else {
|
||||
_omgr.postEvent(req);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface EventListener
|
||||
public function eventReceived (event :DEvent) :void
|
||||
{
|
||||
if (event is InvocationResponseEvent) {
|
||||
var ire :InvocationResponseEvent = (event as InvocationResponseEvent);
|
||||
handleInvocationResponse(ire.getRequestId(), ire.getMethodId(), ire.getArgs());
|
||||
|
||||
} else if (event is InvocationNotificationEvent) {
|
||||
var ine :InvocationNotificationEvent = (event as InvocationNotificationEvent);
|
||||
handleInvocationNotification(ine.getReceiverId(), ine.getMethodId(), ine.getArgs());
|
||||
|
||||
} else if (event is MessageEvent) {
|
||||
var me :MessageEvent = (event as MessageEvent);
|
||||
if (me.getName() === ClientObject.CLOBJ_CHANGED) {
|
||||
handleClientObjectChanged(me.getArgs()[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an invocation response.
|
||||
*/
|
||||
protected function handleInvocationResponse (reqId :int, methodId :int, args :Array) :void
|
||||
{
|
||||
var listener :InvocationMarshaller_ListenerMarshaller =
|
||||
(_listeners.remove(reqId) as InvocationMarshaller_ListenerMarshaller);
|
||||
if (listener == null) {
|
||||
log.warning("Received invocation response for which we have no registered listener " +
|
||||
"[reqId=" + reqId + ", methId=" + methodId + ", args=" + args + "]. " +
|
||||
"It is possible that this listener was flushed because the response did " +
|
||||
"not arrive within " + LISTENER_MAX_AGE + " milliseconds.");
|
||||
return;
|
||||
}
|
||||
|
||||
unboxArgs(args);
|
||||
|
||||
// log.info("Dispatching invocation response [listener=" + listener +
|
||||
// ", methId=" + methodId + ", args=" + StringUtil.toString(args) + "].");
|
||||
|
||||
// dispatch the response
|
||||
try {
|
||||
listener.dispatchResponse(methodId, args);
|
||||
} catch (e :Error) {
|
||||
log.warning("Invocation response listener choked", "listener", listener,
|
||||
"methId", methodId, "args", args, e);
|
||||
}
|
||||
|
||||
// flush expired listeners periodically
|
||||
var now :Number = getTimer();
|
||||
if (now - _lastFlushTime > LISTENER_FLUSH_INTERVAL) {
|
||||
_lastFlushTime = now;
|
||||
flushListeners(now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an invocation notification.
|
||||
*/
|
||||
protected function handleInvocationNotification (
|
||||
receiverId :int, methodId :int, args :Array) :void
|
||||
{
|
||||
// look up the decoder registered for this receiver
|
||||
var decoder :InvocationDecoder = (_receivers.get(receiverId) as InvocationDecoder);
|
||||
if (decoder == null) {
|
||||
log.warning("Received notification for which we have no registered receiver " +
|
||||
"[recvId=" + receiverId + ", methodId=" + methodId +
|
||||
", args=" + args + "].");
|
||||
return;
|
||||
}
|
||||
|
||||
unboxArgs(args);
|
||||
|
||||
// log.info("Dispatching invocation notification [receiver=" + decoder.receiver +
|
||||
// ", methodId=" + methodId + ", args=" + StringUtil.toString(args) + "].");
|
||||
|
||||
try {
|
||||
decoder.dispatchNotification(methodId, args);
|
||||
} catch (e :Error) {
|
||||
log.warning("Invocation notification receiver choked", "receiver", decoder.receiver,
|
||||
"methId", methodId, "args", args, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 function flushListeners (now :Number) :void
|
||||
{
|
||||
var then :Number = now - LISTENER_MAX_AGE;
|
||||
for each (var reqId :int in _listeners.keys()) {
|
||||
var lm :InvocationMarshaller_ListenerMarshaller =
|
||||
(_listeners.get(reqId) as InvocationMarshaller_ListenerMarshaller);
|
||||
if (lm.mapStamp < then) {
|
||||
_listeners.remove(reqId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 function handleClientObjectChanged (newCloid :int) :void
|
||||
{
|
||||
// TODO: or fuck it?
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbox any arguments that have arrived from the server in boxed types.
|
||||
*/
|
||||
protected function unboxArgs (args :Array) :void
|
||||
{
|
||||
if (args != null) {
|
||||
for (var ii :int = 0; ii < args.length; ii++) {
|
||||
if (args[ii] is Boxed) {
|
||||
args[ii] = (args[ii] as Boxed).unbox();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to generate (almost) monotonically increasing invocation request ids. The ids wrap
|
||||
* around to Short.MIN_VALUE after Short.MAX_VALUE is reached.
|
||||
*/
|
||||
protected function nextRequestId () :int
|
||||
{
|
||||
// post increment with wraparound - this is necessary because the request id is
|
||||
// serialized as a java short in all instances. since action script does not appear
|
||||
// to support sign extension, wraparound in the integer domain explicitly. This will
|
||||
// make the values in our _listeners map reliable. Cleverer ways to wrapround may exist.
|
||||
var current :int = _requestId;
|
||||
if (_requestId == Short.MAX_VALUE) { // x7fff
|
||||
_requestId = Short.MIN_VALUE; // x8000
|
||||
} else {
|
||||
_requestId++;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to generate monotonically increasing invocation receiver ids.
|
||||
*/
|
||||
protected function nextReceiverId () :int
|
||||
{
|
||||
return _receiverId++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the ClientObject SubscriberAdapter when the client object has been returned by the
|
||||
* server.
|
||||
*/
|
||||
internal function gotClientObject (clobj :ClientObject) :void
|
||||
{
|
||||
clobj.addListener(this);
|
||||
clobj.setReceivers(new DSet());
|
||||
_clobj = clobj;
|
||||
assignReceiverIds();
|
||||
_client.gotClientObject(clobj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the ClientObject SubscriberAdapter when it fails.
|
||||
*/
|
||||
internal function gotClientObjectFailed (oid :int, cause :ObjectAccessError) :void
|
||||
{
|
||||
log.warning("Invocation director unable to subscribe to client object [cloid=" + oid +
|
||||
", cause=" + cause + "]!");
|
||||
_client.getClientObjectFailed(cause);
|
||||
}
|
||||
|
||||
/** The distributed object manager with which we interact. */
|
||||
internal var _omgr :DObjectManager;
|
||||
|
||||
/** The client for whom we're working. */
|
||||
internal var _client :Client;
|
||||
|
||||
/** Our client object; invocation responses and notifications are received on this object. */
|
||||
internal var _clobj :ClientObject;
|
||||
|
||||
/** Used to generate monotonically increasing request ids. */
|
||||
protected var _requestId :int;
|
||||
|
||||
/** Used to generate monotonically increasing receiver ids. */
|
||||
protected var _receiverId :int;
|
||||
|
||||
/** Used to keep track of invocation service listeners which will receive responses from
|
||||
* invocation service requests. */
|
||||
protected var _listeners :Map = Maps.newMapOf(int);
|
||||
|
||||
/** Used to keep track of invocation notification receivers. */
|
||||
protected var _receivers :Map = Maps.newMapOf(int);
|
||||
|
||||
/** A count of how deeply nested we are in transaction land. */
|
||||
protected var _tcount :int;
|
||||
|
||||
/** An event used to accumulate service request events when in a transaction. */
|
||||
protected var _tevents :Array = [];
|
||||
|
||||
/** All registered receivers are maintained in a list so that we can assign receiver ids to
|
||||
* them when we go online. */
|
||||
internal var _reclist :Array = [];
|
||||
|
||||
/** The last time we flushed our listeners. */
|
||||
protected var _lastFlushTime :Number;
|
||||
|
||||
/** The minimum interval between listener flush attempts. */
|
||||
protected const LISTENER_FLUSH_INTERVAL :int = 15000;
|
||||
|
||||
/** Listener mappings older than 90 seconds are reaped. */
|
||||
protected const LISTENER_MAX_AGE :int = 90 * 1000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// $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 {
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
// nada, but see InvocationRegistration
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// $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.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
import com.threerings.presents.dobj.DSet_Entry;
|
||||
|
||||
/**
|
||||
* Used to maintain a registry of invocation receivers that can be
|
||||
* used to convert (large) hash codes into (small) registration
|
||||
* numbers.
|
||||
*/
|
||||
public class InvocationReceiver_Registration
|
||||
implements DSet_Entry
|
||||
{
|
||||
/** The unique hash code associated with this invocation receiver class. */
|
||||
public var receiverCode :String;
|
||||
|
||||
/** The unique id assigned to this invocation receiver class at
|
||||
* registration time. */
|
||||
public var receiverId :int;
|
||||
|
||||
/** Creates and initializes a registration instance. */
|
||||
public function InvocationReceiver_Registration (
|
||||
receiverCode :String = null, receiverId :int = 0)
|
||||
{
|
||||
this.receiverCode = receiverCode;
|
||||
this.receiverId = receiverId;
|
||||
}
|
||||
|
||||
// documentation inherited from interface DSet_Entry
|
||||
public function getKey () :Object
|
||||
{
|
||||
return receiverCode;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public function toString () :String
|
||||
{
|
||||
return "[" + receiverCode + " => " + receiverId + "]";
|
||||
}
|
||||
|
||||
// documentation inherited from superinterface Streamable
|
||||
public function writeObject (out :ObjectOutputStream) :void
|
||||
{
|
||||
out.writeField(receiverCode);
|
||||
out.writeShort(receiverId);
|
||||
}
|
||||
|
||||
// documentation inherited from superinterface Streamable
|
||||
public function readObject (ins :ObjectInputStream) :void
|
||||
{
|
||||
receiverCode = (ins.readField(String) as String);
|
||||
receiverId = ins.readShort();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* public void moveSucceeded (PlaceConfig config);
|
||||
* }
|
||||
*
|
||||
* // Requests that this client's body be moved to the specified
|
||||
* // location.
|
||||
* //
|
||||
* // @param placeId the object id of the place object to which the
|
||||
* // body should be moved.
|
||||
* // @param listener the listener that will be informed of success or
|
||||
* // failure.
|
||||
* public void moveTo (int placeId, MoveListener listener);
|
||||
* }
|
||||
* </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.
|
||||
* public void moveTo (ClientObject caller, int placeId,
|
||||
* MoveListener listener)
|
||||
* throws InvocationException;
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public interface InvocationService
|
||||
{
|
||||
// nada
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// $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 {
|
||||
|
||||
/**
|
||||
* Extends the {@link InvocationListener} with a basic success
|
||||
* callback.
|
||||
*/
|
||||
public interface InvocationService_ConfirmListener
|
||||
extends InvocationService_InvocationListener
|
||||
{
|
||||
/**
|
||||
* Indicates that the request was successfully processed.
|
||||
*/
|
||||
function requestProcessed () :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// $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 {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* public void moveSucceeded (PlaceConfig config);
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public interface InvocationService_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.
|
||||
*/
|
||||
function requestFailed (cause :String) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// $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 {
|
||||
|
||||
/**
|
||||
* Extends the {@link InvocationListener} with a basic success
|
||||
* callback that delivers a result object.
|
||||
*/
|
||||
public interface InvocationService_ResultListener
|
||||
extends InvocationService_InvocationListener
|
||||
{
|
||||
/**
|
||||
* Indicates that the request was successfully processed.
|
||||
*/
|
||||
function requestProcessed (result :Object) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// $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 LogonError extends Error
|
||||
{
|
||||
/**
|
||||
* Constructs a logon exception with the supplied logon failure code.
|
||||
*/
|
||||
public function LogonError (msg :String, stillInProgress :Boolean = false)
|
||||
{
|
||||
super(msg);
|
||||
_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 function isStillInProgress () :Boolean
|
||||
{
|
||||
return _stillInProgress;
|
||||
}
|
||||
|
||||
protected var _stillInProgress :Boolean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// $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.util.Config;
|
||||
|
||||
/**
|
||||
* Provides access to runtime configuration parameters for this package.
|
||||
*/
|
||||
public class PresentsPrefs
|
||||
{
|
||||
/** Used to store our preferences. */
|
||||
public static const config :Config = new Config("rsrc/config/presents");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// $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 {
|
||||
|
||||
/**
|
||||
* ResultAdapter adapts functions to work as a ResultListener.
|
||||
*/
|
||||
public class ResultAdapter extends InvocationAdapter
|
||||
implements InvocationService_ResultListener
|
||||
{
|
||||
public function ResultAdapter (processed :Function, failed :Function)
|
||||
{
|
||||
super(failed);
|
||||
_processed = processed;
|
||||
}
|
||||
|
||||
// documentation inherited from interface ResultListener
|
||||
public function requestProcessed (result :Object) :void
|
||||
{
|
||||
_processed(result);
|
||||
}
|
||||
|
||||
protected var _processed :Function;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// $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.util.Log;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
import com.threerings.presents.data.AuthCodes;
|
||||
|
||||
/**
|
||||
* Handles the process of switching between servers.
|
||||
*
|
||||
* @see Client#moveToServer
|
||||
*/
|
||||
public class ServerSwitcher extends ClientAdapter
|
||||
{
|
||||
public function ServerSwitcher (
|
||||
client :Client, hostname :String, ports :Array, obs :InvocationService_ConfirmListener)
|
||||
{
|
||||
_client = client;
|
||||
_hostname = hostname;
|
||||
_ports = ports;
|
||||
_observer = obs;
|
||||
}
|
||||
|
||||
public function switchServers () :void
|
||||
{
|
||||
_client.addClientObserver(this);
|
||||
if (!_client.isLoggedOn()) {
|
||||
// if we're not logged on right now, just do the switch immediately
|
||||
clientDidClear(null);
|
||||
|
||||
} else {
|
||||
// note our current connection information so that we can restore it if our logon
|
||||
// attempt fails
|
||||
_oldHostname = _client.getHostname();
|
||||
_oldPorts = _client.getPorts();
|
||||
|
||||
// otherwise logoff and wait for all of our callbacks to clear
|
||||
_client.logoff(true);
|
||||
}
|
||||
}
|
||||
|
||||
// from ClientAdapter
|
||||
override public function clientDidClear (event :ClientEvent) :void
|
||||
{
|
||||
// configure the client to point to the new server and logon
|
||||
_client.setServer(_hostname, _ports);
|
||||
|
||||
if (!_client.logon()) {
|
||||
Log.getLog(this).warning("logon() failed during server switch? [hostname=" + _hostname +
|
||||
", ports=" + StringUtil.toString(_ports) + "].");
|
||||
handleLogonFailure(null);
|
||||
}
|
||||
}
|
||||
|
||||
// from ClientAdapter
|
||||
override public function clientDidLogon (event :ClientEvent) :void
|
||||
{
|
||||
_client.removeClientObserver(this);
|
||||
if (_observer != null) {
|
||||
_observer.requestProcessed();
|
||||
}
|
||||
}
|
||||
|
||||
// from ClientAdapter
|
||||
override public function clientFailedToLogon (event :ClientEvent) :void
|
||||
{
|
||||
handleLogonFailure(event.getCause());
|
||||
}
|
||||
|
||||
protected function handleLogonFailure (error :Error) :void
|
||||
{
|
||||
_client.removeClientObserver(this);
|
||||
if (_oldHostname != null) { // restore our previous server and ports
|
||||
_client.setServer(_oldHostname, _oldPorts);
|
||||
}
|
||||
if (_observer != null) {
|
||||
_observer.requestFailed((error is LogonError) ? error.message : AuthCodes.SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
protected var _client :Client;
|
||||
protected var _hostname :String, _oldHostname :String;
|
||||
protected var _ports :Array, _oldPorts :Array;
|
||||
protected var _observer :InvocationService_ConfirmListener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// $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.
|
||||
*/
|
||||
function clientWillLogon (event :ClientEvent) :void;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function clientDidLogon (event :ClientEvent) :void;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function clientObjectDidChange (event :ClientEvent) :void;
|
||||
|
||||
/**
|
||||
* Called after the client has been logged off of the server and has
|
||||
* disconnected.
|
||||
*/
|
||||
function clientDidLogoff (event :ClientEvent) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// $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 {
|
||||
|
||||
|
||||
/**
|
||||
* An ActionScript version of the Java TimeBaseService interface.
|
||||
*/
|
||||
public interface TimeBaseService extends InvocationService
|
||||
{
|
||||
// from Java interface TimeBaseService
|
||||
function getTimeOid (arg1 :String, arg2 :TimeBaseService_GotTimeBaseListener) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// $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 {
|
||||
|
||||
|
||||
/**
|
||||
* An ActionScript version of the Java TimeBaseService_GotTimeBaseListener interface.
|
||||
*/
|
||||
public interface TimeBaseService_GotTimeBaseListener
|
||||
extends InvocationService_InvocationListener
|
||||
{
|
||||
// from Java TimeBaseService_GotTimeBaseListener
|
||||
function gotTimeOid (arg1 :int) :void
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user