All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.threerings.nexus.client.NexusClient Maven / Gradle / Ivy

The newest version!
//
// Nexus Core - a framework for developing distributed applications
// http://github.com/threerings/nexus/blob/master/LICENSE

package com.threerings.nexus.client;

import java.util.HashMap;
import java.util.Map;

import react.Slot;

import com.threerings.nexus.distrib.Address;
import com.threerings.nexus.distrib.NexusObject;
import com.threerings.nexus.net.Connection;
import com.threerings.nexus.util.Callback;
import com.threerings.nexus.util.CallbackList;

import static com.threerings.nexus.util.Log.log;

/**
 * Manages connections to Nexus servers. Provides access to distributed objects and services.
 */
public abstract class NexusClient
{
    /**
     * Requests to subscribe to the object identified by the supplied address.
     */
    public  void subscribe (final Address addr,
                                                   final Callback callback) {
        withConnection(addr.host, new Callback.Chain(callback) {
            public void onSuccess (Connection conn) {
                conn.subscribe(addr, callback);
            }
        });
    }

    /**
     * Unsubscribes from the specified object. Any events in-flight will be sent to the server, and
     * any events generated after this unsubscription request will be dropped.
     */
    public void unsubscribe (NexusObject object) {
        // TODO: object.postEvent(new UnsubscribeMarker())
        // TODO: catch UnsubscribeMarker in Connection.postEvent, and unsubscribe instead of
        // forwarding the event to the server
    }

    /**
     * Creates a callback that will subscribe to an object of the specified address and pass the
     * successfully subscribed object through to the supplied callback. All failures will be
     * propagated through to the supplied callback as well. This simplifies the handling of a
     * common pattern, which is to make a service request, receive an object address in response
     * and immediately subscribe to the object in question. One can write code like so:
     * 
{@code
     * // assume RoomService.joinRoom (String roomId, Callback> callback);
     * obj.joinRoom(roomId, client.subscriber(new Callback() {
     *     public void onSuccess (RoomObject obj) { ... }
     *     public void onFailure (Throwable cause) { ... }
     * }));
     * }
*/ public Callback> subscriber (final Callback callback) { return new Callback>() { public void onSuccess (Address address) { subscribe(address, callback); } public void onFailure (Throwable cause) { callback.onFailure(cause); } }; } protected abstract void connect (String host, Callback callback); // TODO: should we disconnect immediately when clearing last subscription from a given // connection, or should we periodically poll our open connections and disconnect any with no // active subscriptions (this would allow a little leeway, so that a usage pattern wherein one // unsubscribed from their last object on a given server and then immediately subscribed to a // new one, did not cause needless disconnect and reconnect) protected void disconnect (String serverHost) { // TODO } /** * Establishes a connection with the supplied host (if one does not already exist), and invokes * the supplied action with said connection. Ensures thread-safety in the process. */ protected synchronized void withConnection (final String host, Callback action) { Connection conn = _connections.get(host); if (conn != null) { action.onSuccess(conn); return; } CallbackList plist = _penders.get(host); if (plist != null) { plist.add(action); return; } log.info("Connecting to " + host); _penders.put(host, plist = CallbackList.create(action)); connect(host, new Callback() { public void onSuccess (Connection conn) { onConnectSuccess(conn); } public void onFailure (Throwable cause) { onConnectFailure(host, cause); } }); } protected synchronized void onConnectSuccess (final Connection conn) { CallbackList plist = _penders.remove(conn.getHost()); if (plist == null) { log.warning("Have no penders for established connection?!", "host", conn.getHost()); conn.close(); // shutdown and drop connection } else { _connections.put(conn.getHost(), conn); // we want to be notified when this connection closes conn.onClose.connect(new Slot() { @Override public void onEmit (Throwable error) { onConnectionClose(conn, error); } }); plist.onSuccess(conn); } } protected synchronized void onConnectFailure (String host, Throwable cause) { CallbackList plist = _penders.remove(host); if (plist == null) { log.warning("Have no penders for failed connection?!", "host", host); } else { plist.onFailure(cause); } } protected synchronized void onConnectionClose (Connection conn, Throwable error) { // TODO: do we care about orderly versus exceptional closure? _connections.remove(conn.getHost()); } /** A mapping from hostname to connection instance for all active connections. */ protected Map _connections = new HashMap(); /** A mapping from hostname to listener list, for all pending connections. */ protected Map> _penders = new HashMap>(); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy