From 74dac11f97e003252371492451c233289b807de9 Mon Sep 17 00:00:00 2001 From: Jamie Doornbos Date: Mon, 28 Apr 2008 18:34:57 +0000 Subject: [PATCH] Bureaucracy * Fixed signature of Receiver methods * Implemented create/destroy mechanism and bootstrap request in BureauDirector * Added BureauContext class to expose bureau information on top of usual presents stuff * Added BureauContext implementation in BureauClient * Added code to kick off the director when the client logs on * Added credentials for a bureau logon, which is just a token passed to command line launcher * Added concept of bureau types to distinguish between executable and command line arguments; includes interface BureauRegistry.Launcher * Implemented backend for the registry git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5027 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- src/java/com/threerings/bureau/Log.java | 58 +++ .../com/threerings/bureau/client/Agent.java | 6 + .../bureau/client/BureauClient.java | 64 ++- .../bureau/client/BureauDecoder.java | 5 +- .../bureau/client/BureauDirector.java | 113 ++++- .../bureau/client/BureauReceiver.java | 4 +- .../threerings/bureau/data/AgentObject.java | 49 ++- .../bureau/data/BureauCredentials.java | 38 ++ .../bureau/server/BureauRegistry.java | 410 ++++++++++++++++-- .../bureau/server/BureauSender.java | 8 +- .../threerings/bureau/util/BureauContext.java | 42 ++ 11 files changed, 724 insertions(+), 73 deletions(-) create mode 100644 src/java/com/threerings/bureau/Log.java create mode 100644 src/java/com/threerings/bureau/data/BureauCredentials.java create mode 100644 src/java/com/threerings/bureau/util/BureauContext.java diff --git a/src/java/com/threerings/bureau/Log.java b/src/java/com/threerings/bureau/Log.java new file mode 100644 index 000000000..befeebc7b --- /dev/null +++ b/src/java/com/threerings/bureau/Log.java @@ -0,0 +1,58 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/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.bureau; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Contains a reference to the log object used by the Bureau services. + */ +public class Log +{ + /** We dispatch our log messages through this logger. */ + public static Logger log = Logger.getLogger("com.threerings.narya.bureau"); + + /** Convenience function. */ + public static void debug (String message) + { + log.fine(message); + } + + /** Convenience function. */ + public static void info (String message) + { + log.info(message); + } + + /** Convenience function. */ + public static void warning (String message) + { + log.warning(message); + } + + /** Convenience function. */ + public static void logStackTrace (Throwable t) + { + log.log(Level.WARNING, t.getMessage(), t); + } +} diff --git a/src/java/com/threerings/bureau/client/Agent.java b/src/java/com/threerings/bureau/client/Agent.java index 76a1169eb..a76a57e33 100644 --- a/src/java/com/threerings/bureau/client/Agent.java +++ b/src/java/com/threerings/bureau/client/Agent.java @@ -21,9 +21,15 @@ package com.threerings.bureau.client; +import com.threerings.bureau.data.AgentObject; + /** * Represents an agent running within a bureau client. */ public class Agent { + /** + * The shared agent object. + */ + public AgentObject agentObject; } diff --git a/src/java/com/threerings/bureau/client/BureauClient.java b/src/java/com/threerings/bureau/client/BureauClient.java index 6933e764a..8ab8bb623 100644 --- a/src/java/com/threerings/bureau/client/BureauClient.java +++ b/src/java/com/threerings/bureau/client/BureauClient.java @@ -1,8 +1,33 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/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.bureau.client; import com.threerings.presents.client.Client; import com.threerings.presents.net.Credentials; import com.samskivert.util.RunQueue; +import com.threerings.bureau.data.BureauCredentials; +import com.threerings.bureau.util.BureauContext; +import com.threerings.presents.dobj.DObjectManager; +import com.samskivert.util.Config; /** * Represents a client embedded in a bureau. @@ -14,8 +39,43 @@ public class BureauClient extends Client * @param creds the credentials supplied during connection * @param runQueue the place to post tasks required by clients */ - public BureauClient (Credentials creds, RunQueue runQueue) + public BureauClient (String token, String bureauId, RunQueue runQueue) { - super(creds, runQueue); + super(null, runQueue); + + _bureauId = bureauId; + + BureauCredentials creds = new BureauCredentials(); + creds.sessionToken = token; + _creds = creds; + + _ctx = createContext(); + _director = new BureauDirector(_ctx); } + + protected BureauContext createContext () + { + return new BureauContext () { + public BureauDirector getBureauDirector () { + return _director; + } + public DObjectManager getDObjectManager () { + return _omgr; + } + public Client getClient () { + return BureauClient.this; + } + public Config getConfig () { + return _config; + } + public String getBureauId () { + return _bureauId; + } + }; + } + + protected BureauContext _ctx; + protected String _bureauId; + protected BureauDirector _director; + protected Config _config = new Config("bureau"); } diff --git a/src/java/com/threerings/bureau/client/BureauDecoder.java b/src/java/com/threerings/bureau/client/BureauDecoder.java index fd59631c5..e1c3312fd 100644 --- a/src/java/com/threerings/bureau/client/BureauDecoder.java +++ b/src/java/com/threerings/bureau/client/BureauDecoder.java @@ -23,7 +23,6 @@ package com.threerings.bureau.client; import com.threerings.bureau.client.BureauReceiver; import com.threerings.presents.client.InvocationDecoder; -import com.threerings.presents.data.ClientObject; /** * Dispatches calls to a {@link BureauReceiver} instance. @@ -62,13 +61,13 @@ public class BureauDecoder extends InvocationDecoder switch (methodId) { case CREATE_AGENT: ((BureauReceiver)receiver).createAgent( - (ClientObject)args[0], ((Integer)args[1]).intValue() + ((Integer)args[0]).intValue() ); return; case DESTROY_AGENT: ((BureauReceiver)receiver).destroyAgent( - (ClientObject)args[0], ((Integer)args[1]).intValue() + ((Integer)args[0]).intValue() ); return; diff --git a/src/java/com/threerings/bureau/client/BureauDirector.java b/src/java/com/threerings/bureau/client/BureauDirector.java index 79b4c8901..debf1c1e6 100644 --- a/src/java/com/threerings/bureau/client/BureauDirector.java +++ b/src/java/com/threerings/bureau/client/BureauDirector.java @@ -2,32 +2,127 @@ package com.threerings.bureau.client; import com.threerings.presents.client.BasicDirector; import com.threerings.presents.data.ClientObject; -import com.threerings.presents.util.PresentsContext; +import com.threerings.bureau.data.BureauCodes; +import com.samskivert.util.HashIntMap; +import com.threerings.bureau.data.AgentObject; +import com.threerings.bureau.Log; +import com.threerings.bureau.util.BureauContext; +import com.threerings.presents.client.Client; +import com.threerings.presents.dobj.Subscriber; +import com.threerings.presents.dobj.ObjectAccessException; /** * Allows the server to create and destroy agents on a client. - * @see BureauRegistry + * @see BureauRegistry */ public class BureauDirector extends BasicDirector - implements BureauReceiver + implements BureauReceiver, Subscriber { - protected BureauDirector (PresentsContext ctx) + public BureauDirector (BureauContext ctx) { super(ctx); + _ctx = ctx; + } + + // from BureauReceiver + public synchronized void createAgent (int agentId) + { + _ctx.getDObjectManager().subscribeToObject(agentId, this); + } + + // from BureauReceiver + public synchronized void destroyAgent (int agentId) + { + Agent agent = null; + try { + agent = _agents.remove(agentId); + // TODO: stop the agent somehow + } + catch (Throwable t) { + Log.warning("Could not create agent [id=" + agentId + "]"); + Log.logStackTrace(t); + // TODO: failure notification? + return; + } + + _ctx.getDObjectManager().unsubscribeFromObject(agentId, this); + doStopAgent(agent); + _bureauService.agentDestroyed(_ctx.getClient(), agentId); + } + + // from Subscriber + public synchronized void objectAvailable (AgentObject agentObject) + { + int oid = agentObject.getOid(); + try { + Agent agent = new Agent(); + agent.agentObject = agentObject; + doStartAgent(agent); + _agents.put(oid, agent); + } + catch (Exception e) { + Log.warning("Could not create agent [obj=" + agentObject + "]"); + Log.logStackTrace(e); + // TODO: failure notification? + return; + } + + // TODO: post to runqueue? + _bureauService.agentCreated(_ctx.getClient(), oid); + } + + // from Subscriber + public synchronized void requestFailed (int oid, ObjectAccessException cause) + { + Log.warning("Could not subscribe to agent [oid=" + oid + "]"); + Log.logStackTrace(cause); + } + + @Override // from BasicDirector + public void clientDidLogon (Client client) + { + super.clientDidLogon(client); + _bureauService.bureauInitialized(_ctx.getClient(), _ctx.getBureauId()); + } + + @Override // from BasicDirector + protected void registerServices (Client client) + { + super.registerServices(client); + + // Require the bureau services + client.addServiceGroup(BureauCodes.BUREAU_GROUP); // Set up our decoder so we can receive method calls // from the server - _ctx.getClient().getInvocationDirector(). + client.getInvocationDirector(). registerReceiver(new BureauDecoder(this)); } - // from BureauReceiver - public void createAgent (ClientObject client, int agentId) + @Override // from BasicDirector + protected void fetchServices (Client client) + { + super.fetchServices(client); + + _bureauService = client.getService(BureauService.class); + } + + /** + * Called when the agent object is ready and it is time to run his code. + */ + protected void doStartAgent (Agent agent) { } - // from BureauReceiver - public void destroyAgent (ClientObject client, int agentId) + /** + * Called when the agent object is being destroyed and the client code should stop + * processing. + */ + protected void doStopAgent (Agent agent) { } + + protected BureauContext _ctx; + protected BureauService _bureauService; + protected HashIntMap _agents = new HashIntMap(); } diff --git a/src/java/com/threerings/bureau/client/BureauReceiver.java b/src/java/com/threerings/bureau/client/BureauReceiver.java index 6b5a98441..757e34d35 100644 --- a/src/java/com/threerings/bureau/client/BureauReceiver.java +++ b/src/java/com/threerings/bureau/client/BureauReceiver.java @@ -36,7 +36,7 @@ public interface BureauReceiver extends InvocationReceiver * @param client the client receiving the request * @param agentId the id of the AgentObject that needs an Agent */ - void createAgent (ClientObject client, int agentId); + void createAgent (int agentId); /** * Destroys a previously created agent. Implementors should destroy the agent that was created @@ -46,5 +46,5 @@ public interface BureauReceiver extends InvocationReceiver * @param agentId the id of the AgentObject whose Agent * should be destroyed */ - void destroyAgent (ClientObject client, int agentId); + void destroyAgent (int agentId); } diff --git a/src/java/com/threerings/bureau/data/AgentObject.java b/src/java/com/threerings/bureau/data/AgentObject.java index d87b9c2e5..3d8d77d68 100644 --- a/src/java/com/threerings/bureau/data/AgentObject.java +++ b/src/java/com/threerings/bureau/data/AgentObject.java @@ -29,16 +29,25 @@ public class AgentObject extends DObject /** The field name of the bureauId field. */ public static final String BUREAU_ID = "bureauId"; + /** The field name of the bureauType field. */ + public static final String BUREAU_TYPE = "bureauType"; + /** The field name of the agentCode field. */ public static final String AGENT_CODE = "agentCode"; - - /** The field name of the startConfirmed field. */ - public static final String START_CONFIRMED = "startConfirmed"; // AUTO-GENERATED: FIELDS END + /** The id of the bureau the agent is running in. This is normally a unique id corresponding + * to the game or item that requires some server-side processing. */ public String bureauId; + + /** The type of bureau that the agent is running in. This is normally derived from the kind + * of media that the game or item has specified for its code and determines the method of + * launching the bureau when the first agent is requested. */ + public String bureauType; + + /** The location of the code for the agent. This could be a URL to an action script file or + * some other description that the bureau can use to load and execute the agent's code. */ public String agentCode; - public boolean startConfirmed; // AUTO-GENERATED: METHODS START /** @@ -57,6 +66,22 @@ public class AgentObject extends DObject this.bureauId = value; } + /** + * Requests that the bureauType field be set to the + * specified value. The local value will be updated immediately and an + * event will be propagated through the system to notify all listeners + * that the attribute did change. Proxied copies of this object (on + * clients) will apply the value change when they received the + * attribute changed notification. + */ + public void setBureauType (String value) + { + String ovalue = this.bureauType; + requestAttributeChange( + BUREAU_TYPE, value, ovalue); + this.bureauType = value; + } + /** * Requests that the agentCode field be set to the * specified value. The local value will be updated immediately and an @@ -72,21 +97,5 @@ public class AgentObject extends DObject AGENT_CODE, value, ovalue); this.agentCode = value; } - - /** - * Requests that the startConfirmed field be set to the - * specified value. The local value will be updated immediately and an - * event will be propagated through the system to notify all listeners - * that the attribute did change. Proxied copies of this object (on - * clients) will apply the value change when they received the - * attribute changed notification. - */ - public void setStartConfirmed (boolean value) - { - boolean ovalue = this.startConfirmed; - requestAttributeChange( - START_CONFIRMED, Boolean.valueOf(value), Boolean.valueOf(ovalue)); - this.startConfirmed = value; - } // AUTO-GENERATED: METHODS END } diff --git a/src/java/com/threerings/bureau/data/BureauCredentials.java b/src/java/com/threerings/bureau/data/BureauCredentials.java new file mode 100644 index 000000000..87e247985 --- /dev/null +++ b/src/java/com/threerings/bureau/data/BureauCredentials.java @@ -0,0 +1,38 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/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.bureau.data; + +import com.threerings.presents.net.Credentials; +import com.threerings.util.Name; + +public class BureauCredentials extends Credentials +{ + /** + * The token to pass to the server when logging in. This is usually just passed to the bureau + * on the command line to guard against outside connections being established. */ + public String sessionToken; + + public BureauCredentials () + { + super(new Name("$$$BUREAU$$$")); + } +} diff --git a/src/java/com/threerings/bureau/server/BureauRegistry.java b/src/java/com/threerings/bureau/server/BureauRegistry.java index 4efc908f6..2dee3397d 100644 --- a/src/java/com/threerings/bureau/server/BureauRegistry.java +++ b/src/java/com/threerings/bureau/server/BureauRegistry.java @@ -23,89 +23,433 @@ package com.threerings.bureau.server; import java.util.Map; import java.util.Set; +import java.util.HashMap; +import java.util.HashSet; import com.threerings.bureau.data.AgentObject; import com.threerings.bureau.data.BureauCodes; import com.threerings.presents.data.ClientObject; import com.threerings.presents.dobj.RootDObjectManager; import com.threerings.presents.server.InvocationManager; +import static com.samskivert.util.StringUtil.safeToString; + +import com.threerings.bureau.Log; /** * Abstracts the launching and termination of external processes (bureaus) that host instances of * server-side code (agents). - * TODO: this class needs lots of fleshing out and as such is not 100% documented */ public class BureauRegistry - implements BureauProvider { /** - * The binary executable for the external vm being used. - * TODO: what args are needed? - * TODO: how to format the args? - * TODO: where does the binary path really come from - a bureau specification? + * Defines a way to launch a bureau. Launchers are set by the server on startup and are + * invoked whenever an agent is requested whose bureauType matches the registered type. + * @see registerBureau */ - public String vmBinary = "java %{jar} %{host}:%{port} %{agentclassname}"; + public static interface Launcher + { + /** + * Launches a new bureau using the given server connect-back url and other information. + * Called by the registry when it decides a new bureau is needed. + * @param serverUrl the url the bureau should use to connect back to the server + * @param bureauId the id of the bureau being launched + * @param token the token string to use for the credentials when logging in + */ + Process launch (String serverUrl, String bureauId, String token); + } /** * Creates a new registry, prepared to provide bureau services. */ - public BureauRegistry (InvocationManager invmgr, RootDObjectManager omgr) + public BureauRegistry ( + String serverUrl, + InvocationManager invmgr, + RootDObjectManager omgr) { + _serverUrl = serverUrl; _invmgr = invmgr; _omgr = omgr; - invmgr.registerDispatcher( - new BureauDispatcher(this), + BureauProvider provider = new BureauProvider () { + public void bureauInitialized (ClientObject client, String bureauId) { + BureauRegistry.this.bureauInitialized(client, bureauId); + } + public void agentCreated (ClientObject client, int agentId) { + BureauRegistry.this.agentCreated(client, agentId); + } + public void agentDestroyed (ClientObject client, int agentId) { + BureauRegistry.this.agentDestroyed(client, agentId); + } + }; + + _invmgr.registerDispatcher( + new BureauDispatcher(provider), BureauCodes.BUREAU_GROUP); } + /** + * Registers a launcher of a given type. When an agent is started and no bureaus are currently + * running, the bureauType is used to determine the Launcher to call. + * @param bureauType the type of bureau that will be launched + * @param launcher the launcher to be used when the bureauType is requested + */ + public void setLauncher (String bureauType, Launcher launcher) + { + if (_launchers.get(bureauType) != null) { + Log.warning("Launcher for type already exists [type=" + + bureauType + "]"); + return; + } + + _launchers.put(bureauType, launcher); + } + /** * Starts a new agent using the data in the given object, creating a new bureau if necessary. */ - public void startAgent (AgentObject agent) + public synchronized void startAgent (AgentObject agent) { - // TODO: create new bureau if needed - // TODO: otherwise call BureauSender.createAgent and pend + Bureau bureau = _bureaus.get(agent.bureauId); + if (bureau != null && bureau.ready()) { + + Log.info("Bureau ready, sending createAgent " + + safeToString(agent)); + + BureauSender.createAgent(bureau.clientObj, agent.getOid()); + // !TODO: is this the right place to register the object? + _omgr.registerObject(agent); + bureau.agentStates.put(agent, Bureau.STARTED); + + bureau.summarize(); + + return; + } + + if (bureau == null) { + + Launcher launcher = _launchers.get(agent.bureauType); + if (launcher == null) { + Log.warning("Launcher not found for agent's " + + "bureau type " + safeToString(agent)); + return; + } + + Log.info("Creating new bureau " + + safeToString(agent) + " " + + safeToString(launcher)); + + bureau = new Bureau(); + + try { + bureau.process = launcher.launch(_serverUrl, agent.bureauId, ""); + } + catch (Exception e) { + Log.warning("Could not launch process for bureau " + + safeToString(agent)); + Log.logStackTrace(e); + + // !TODO: how to hook into caller and keep the other clients from hanging + + return; + } + + _bureaus.put(agent.bureauId, bureau); + } + + _omgr.registerObject(agent); + bureau.agentStates.put(agent, Bureau.PENDING); + + bureau.summarize(); } /** - * Destorys a previously started agent using the data in the given object. + * Destroys a previously started agent using the data in the given object. */ - public void destroyAgent (AgentObject agent) + public synchronized void destroyAgent (AgentObject agent) { - // TODO: call BureauSender.destroyAgent and pend + FoundAgent found = resolve(null, agent.getOid(), "destroyAgent"); + + if (found == null) { + return; + } + + Log.warning("Destroying agent " + safeToString(agent)); + + // transition the agent to a new state and perform the effect of the transition + switch (found.state) { + + case Bureau.PENDING: + found.bureau.agentStates.remove(found.agent); + // !TODO: is the the right place to destroy it? + _omgr.destroyObject(found.agent.getOid()); + break; + + case Bureau.STARTED: + found.bureau.agentStates.put(found.agent, Bureau.STILL_BORN); + break; + + case Bureau.RUNNING: + BureauSender.destroyAgent(found.bureau.clientObj, agent.getOid()); + found.bureau.agentStates.put(found.agent, Bureau.DESTROYED); + break; + + case Bureau.DESTROYED: + case Bureau.STILL_BORN: + Log.warning("Acknowledging a request to destory an agent, but agent " + + "is in state " + found.state + ", ignoring request " + + safeToString(found.agent)); + break; + } + + found.bureau.summarize(); + } + + /** + * Callback for when the bureau client acknowledges starting up. Starts all pending agents and + * causes subsequent agent start requests to be sent directly to the bureau. + */ + protected synchronized void bureauInitialized (ClientObject client, String bureauId) + { + Bureau bureau = _bureaus.get(bureauId); + if (bureau == null) { + Log.warning("Acknowledging initialization of non-existent bureau " + + safeToString(bureauId)); + return; + } + + bureau.clientObj = client; + + Log.info("Bureau created " + safeToString(bureau) + + ", launching pending agents"); + + // find all pending agents + Set pending = new HashSet(); + + for (Map.Entry entry : + bureau.agentStates.entrySet()) { + + if (entry.getValue() == Bureau.PENDING) { + pending.add(entry.getKey()); + } + } + + // create them + for (AgentObject agent : pending) { + Log.info("Creating agent " + safeToString(agent)); + BureauSender.createAgent(bureau.clientObj, agent.getOid()); + bureau.agentStates.put(agent, Bureau.STARTED); + } + + bureau.summarize(); + } + + /** + * Callback for when the bureau client acknowledges the creation of an agent. + */ + protected synchronized void agentCreated (ClientObject client, int agentId) + { + FoundAgent found = resolve(client, agentId, "agentCreated"); + if (found == null) { + return; + } + + switch (found.state) { + case Bureau.STARTED: + found.bureau.agentStates.put(found.agent, Bureau.RUNNING); + break; + + case Bureau.STILL_BORN: + BureauSender.destroyAgent(found.bureau.clientObj, agentId); + found.bureau.agentStates.put(found.agent, Bureau.DESTROYED); + break; + + case Bureau.PENDING: + case Bureau.RUNNING: + case Bureau.DESTROYED: + Log.warning("Received acknowledgement of the creation of an " + + "agent in state " + found.state + ", ignoring request " + + safeToString(found.agent)); + break; + } + + found.bureau.summarize(); + } + + /** + * Callback for when the bureau client acknowledges the destruction of an agent. + */ + protected synchronized void agentDestroyed (ClientObject client, int agentId) + { + FoundAgent found = resolve(client, agentId, "agentDestroyed"); + if (found == null) { + return; + } + + switch (found.state) { + case Bureau.DESTROYED: + found.bureau.agentStates.remove(found.agent); + break; + + case Bureau.PENDING: + case Bureau.STARTED: + case Bureau.RUNNING: + case Bureau.STILL_BORN: + Log.warning("Acknowledging agent destruction, but state is " + + found.state + ", ignoring request " + + safeToString(found.agent)); + break; + } + + found.bureau.summarize(); + // TODO: schedule a shutdown event for the bureau if this is the last agent } - // from BureauProvider - public void bureauInitialized (ClientObject client, String bureauId) + /** + * Does lots of null checks and lookups and resolves the given information into FoundAgent. + */ + protected FoundAgent resolve (ClientObject client, int agentId, String resolver) { - // TODO: create pending agents - // TODO: synchronization + com.threerings.presents.dobj.DObject dobj = _omgr.getObject(agentId); + if (dobj == null) { + Log.warning("Non-existent agent in " + resolver + + " [agentId=" + agentId + "]"); + return null; + } + + if (!(dobj instanceof AgentObject)) { + Log.warning("Object not an agent in " + resolver + + " " + safeToString(dobj)); + return null; + } + + AgentObject agent = (AgentObject)dobj; + Bureau bureau = _bureaus.get(agent.bureauId); + if (bureau == null) { + Log.warning("Bureau not found for agent in " + resolver + + " " + safeToString(agent)); + return null; + } + + if (!bureau.agentStates.containsKey(agent)) { + Log.warning("Bureau does not have agent in " + resolver + + " " + safeToString(agent)); + return null; + } + + if (bureau.clientObj == null) { + Log.warning("Bureau not yet connected in " + resolver + + " " + safeToString(agent)); + return null; + } + + if (client != null && bureau.clientObj != client) { + Log.warning("Masquerading request in " + resolver + + " " + safeToString(agent) + + " " + safeToString(bureau.clientObj) + + " " + safeToString(client)); + return null; + } + + return new FoundAgent(bureau, agent, bureau.agentStates.get(agent)); } - // from BureauProvider - public void agentCreated (ClientObject client, int agentId) + // Models the results of searching for an agent + protected static class FoundAgent { - // TODO: remove from pending + FoundAgent ( + Bureau bureau, + AgentObject agent, + int state) + { + this.bureau = bureau; + this.agent = agent; + this.state = state; + } + + // Bureau containing the agent + Bureau bureau; + + // The object + AgentObject agent; + + // The state of the agent + int state; } - // from BureauProvider - public void agentDestroyed (ClientObject caller, int arg1) - { - // TODO: remove from pending destroyed - } - - // first stab at the data structures for holding bureaus and agents + // Models a bureau, including the process handle, all running agents and their states protected static class Bureau { + // Agent states { + + // Not yet stated, waiting for bureau to ack + static final int PENDING = 0; + + // Bureau acked, agent told to start + static final int STARTED = 1; + + // Agent ack'ed, now live and hosting, ready to tell other clients + static final int RUNNING = 2; + + // Agent destruction requested, waiting for acknowledge (after which the agent is removed + // from the Bureau, so has no state) + static final int DESTROYED = 3; + + // Edge case: destroy request prior to RUNNING + static final int STILL_BORN = 4; + + // } + + // Should be non-null once the bureau is kicked off Process process; + + // The bureau's key in the map of bureaus. All requests for this bureau + // with this id should be associated with one instance + String bureauId; + + // The client object of the bureau that has opened a dobj connection to + // the registry ClientObject clientObj; - Set agents; + + // The states of the various agents allocated to this bureau + Map agentStates = + new HashMap(); + + boolean ready () + { + return clientObj != null; + } + + int countInState (int state) + { + Integer state1 = state; + if (!agentStates.containsValue(state1)) { + return 0; + } + + int count = 0; + for (Map.Entry me : agentStates.entrySet()) { + if (me.getValue() == state1) { + ++count; + } + } + return count; + } + + void summarize () + { + Log.info("Bureau " + bureauId + " [" + + countInState(PENDING) + " pending, " + + countInState(STARTED) + " started, " + + countInState(RUNNING) + " running, " + + countInState(DESTROYED) + " destroyed, " + + countInState(STILL_BORN) + " still born]"); + } } + protected String _serverUrl; protected InvocationManager _invmgr; protected RootDObjectManager _omgr; - protected Map _bureaus; - protected Map> _pending; + protected Map _launchers = new HashMap(); + protected Map _bureaus = new HashMap(); } diff --git a/src/java/com/threerings/bureau/server/BureauSender.java b/src/java/com/threerings/bureau/server/BureauSender.java index 8fdab530d..e3bed6091 100644 --- a/src/java/com/threerings/bureau/server/BureauSender.java +++ b/src/java/com/threerings/bureau/server/BureauSender.java @@ -37,11 +37,11 @@ public class BureauSender extends InvocationSender * BureauReceiver#createAgent} on a client. */ public static void createAgent ( - ClientObject target, ClientObject arg1, int arg2) + ClientObject target, int arg1) { sendNotification( target, BureauDecoder.RECEIVER_CODE, BureauDecoder.CREATE_AGENT, - new Object[] { arg1, Integer.valueOf(arg2) }); + new Object[] { Integer.valueOf(arg1) }); } /** @@ -49,11 +49,11 @@ public class BureauSender extends InvocationSender * BureauReceiver#destroyAgent} on a client. */ public static void destroyAgent ( - ClientObject target, ClientObject arg1, int arg2) + ClientObject target, int arg1) { sendNotification( target, BureauDecoder.RECEIVER_CODE, BureauDecoder.DESTROY_AGENT, - new Object[] { arg1, Integer.valueOf(arg2) }); + new Object[] { Integer.valueOf(arg1) }); } } diff --git a/src/java/com/threerings/bureau/util/BureauContext.java b/src/java/com/threerings/bureau/util/BureauContext.java new file mode 100644 index 000000000..956745c69 --- /dev/null +++ b/src/java/com/threerings/bureau/util/BureauContext.java @@ -0,0 +1,42 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/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.bureau.util; + +import com.threerings.presents.util.PresentsContext; +import com.threerings.bureau.client.BureauDirector; + +/** + * Defines the objects held on a bureau client. This includes usual set of objects found on a + * standard presents client. + */ +public interface BureauContext extends PresentsContext +{ + /** + * Access the director object. + */ + BureauDirector getBureauDirector (); + + /** + * Access the bureau id. + */ + String getBureauId (); +}