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:
Michael Bayne
2010-11-18 20:50:09 +00:00
parent 0a04df1ce0
commit 34722f2ce9
222 changed files with 60 additions and 18 deletions
@@ -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
}
}