package kernel; import java.util.Set; import java.util.HashSet; import java.util.Queue; import java.util.List; import java.util.ArrayList; import java.util.LinkedList; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.HashMap; import rescuecore2.config.Config; import rescuecore2.connection.Connection; import rescuecore2.connection.ConnectionException; import rescuecore2.connection.ConnectionListener; import rescuecore2.connection.ConnectionManagerListener; import rescuecore2.messages.Message; import rescuecore2.messages.control.VKConnect; import rescuecore2.messages.control.VKAcknowledge; import rescuecore2.messages.control.KVConnectOK; import rescuecore2.messages.control.SKConnect; import rescuecore2.messages.control.SKAcknowledge; import rescuecore2.messages.control.KSConnectOK; import rescuecore2.messages.control.AKConnect; import rescuecore2.messages.control.AKAcknowledge; import rescuecore2.messages.control.KAConnectError; import rescuecore2.messages.control.KAConnectOK; import rescuecore2.worldmodel.Entity; import rescuecore2.worldmodel.EntityID; import rescuecore2.worldmodel.WorldModel; import rescuecore2.GUIComponent; import rescuecore2.log.Logger; import kernel.ui.ComponentManagerGUI; import javax.swing.JComponent; /** Class that manages connecting components (agents, simulators, viewers) to the kernel. */ public class ComponentManager implements ConnectionManagerListener, GUIComponent { private static final int STARTING_ID = 1; private static final int WAIT_TIME = 10000; private Kernel kernel; private ComponentManagerGUI gui; // Entities that have no controller yet. Map from type to list of entities. private Map<String, Queue<ControlledEntityInfo>> uncontrolledEntities; // Connected agents private Set<AgentAck> agentsToAcknowledge; // Connected simulators private Set<SimulatorAck> simsToAcknowledge; private int nextID; // Connected viewers private Set<ViewerAck> viewersToAcknowledge; // World information private WorldModel<? extends Entity> world; private Config config; /** Lock objects. */ private final Object agentLock = new Object(); private final Object simLock = new Object(); private final Object viewerLock = new Object(); private final Object idLock = new Object(); /** Create a ComponentManager. @param kernel The kernel. @param world The world model. @param config The kernel configuration. */ public ComponentManager(Kernel kernel, WorldModel<? extends Entity> world, Config config) { this.kernel = kernel; this.world = world; this.config = config; uncontrolledEntities = new HashMap<String, Queue<ControlledEntityInfo>>(); agentsToAcknowledge = new HashSet<AgentAck>(); simsToAcknowledge = new HashSet<SimulatorAck>(); viewersToAcknowledge = new HashSet<ViewerAck>(); nextID = STARTING_ID; gui = new ComponentManagerGUI(); } /** Register an agent-controlled entity. @param entity The entity that is agent-controlled. @param visibleOnStartup The set of entities that the agent should be sent on startup. If this is null then all entities will be sent. @param agentConfig A view of the system configuration that should be shared with the agent. */ public void registerAgentControlledEntity(Entity entity, Collection<? extends Entity> visibleOnStartup, Config agentConfig) { Logger.info("Agent controlled entity registered: " + entity); synchronized (agentLock) { Queue<ControlledEntityInfo> q = uncontrolledEntities.get(entity.getURN()); if (q == null) { q = new LinkedList<ControlledEntityInfo>(); uncontrolledEntities.put(entity.getURN(), q); } if (visibleOnStartup == null) { visibleOnStartup = world.getAllEntities(); } q.add(new ControlledEntityInfo(entity, visibleOnStartup, agentConfig)); } updateGUIUncontrolledAgents(); } /** Wait for all agents to connect. This method will block until all agent entities have controllers. @throws InterruptedException If the thread is interrupted. */ public void waitForAllAgents() throws InterruptedException { synchronized (agentLock) { boolean done = false; do { done = true; for (Map.Entry<String, Queue<ControlledEntityInfo>> next : uncontrolledEntities.entrySet()) { if (!next.getValue().isEmpty()) { done = false; Logger.info("Waiting for " + next.getValue().size() + " entities of type " + next.getKey()); } } if (!agentsToAcknowledge.isEmpty()) { done = false; Logger.info("Waiting for " + agentsToAcknowledge.size() + " agents to acknowledge"); } if (!done) { agentLock.wait(WAIT_TIME); } } while (!done); } } /** Wait until all simulators have acknowledged. @throws InterruptedException If the thread is interrupted. */ public void waitForAllSimulators() throws InterruptedException { synchronized (simLock) { while (!simsToAcknowledge.isEmpty()) { simLock.wait(WAIT_TIME); Logger.info("Waiting for " + simsToAcknowledge.size() + " simulators to acknowledge"); } } } /** Wait until all viewers have acknowledged. @throws InterruptedException If the thread is interrupted. */ public void waitForAllViewers() throws InterruptedException { synchronized (viewerLock) { while (!viewersToAcknowledge.isEmpty()) { viewerLock.wait(WAIT_TIME); Logger.info("Waiting for " + viewersToAcknowledge.size() + " viewers to acknowledge"); } } } @Override public void newConnection(Connection c) { c.addConnectionListener(new ComponentConnectionListener()); } @Override public JComponent getGUIComponent() { return gui; } @Override public String getGUIComponentName() { return "Component manager"; } private boolean agentAcknowledge(int requestID, EntityID agentID, Connection c) { synchronized (agentLock) { for (AgentAck next : agentsToAcknowledge) { if (next.requestID == requestID && next.agentID.equals(agentID) && next.connection == c) { agentsToAcknowledge.remove(next); kernel.addAgent(next.agent); agentLock.notifyAll(); return true; } } return false; } } private boolean simAcknowledge(int requestID, int simulatorID, Connection c) { synchronized (simLock) { for (SimulatorAck next : simsToAcknowledge) { if (next.requestID == requestID && next.simulatorID == simulatorID && next.connection == c) { simsToAcknowledge.remove(next); kernel.addSimulator(next.sim); simLock.notifyAll(); return true; } } return false; } } private boolean viewerAcknowledge(int requestID, int viewerID, Connection c) { synchronized (viewerLock) { for (ViewerAck next : viewersToAcknowledge) { if (next.requestID == requestID && next.viewerID == viewerID && next.connection == c) { viewersToAcknowledge.remove(next); kernel.addViewer(next.viewer); viewerLock.notifyAll(); return true; } } return false; } } private int getNextSimulatorID() { synchronized (idLock) { return nextID++; } } private int getNextViewerID() { synchronized (idLock) { return nextID++; } } private ControlledEntityInfo findEntityToControl(List<String> types) { Logger.debug("Finding entity to control. Requested types: " + types); for (String next : types) { Queue<ControlledEntityInfo> q = uncontrolledEntities.get(next); Logger.debug("Uncontrolled entities of type " + next + ": " + q); if (q != null) { ControlledEntityInfo info = q.poll(); if (info != null) { return info; } } } return null; } private void updateGUIUncontrolledAgents() { List<String> data = new ArrayList<String>(); synchronized (agentLock) { for (Queue<ControlledEntityInfo> q : uncontrolledEntities.values()) { for (ControlledEntityInfo info : q) { data.add(info.entity.getURN() + " " + info.entity.getID()); } } } gui.updateUncontrolledAgents(data); } private void updateGUIAgentAck() { List<String> data = new ArrayList<String>(); synchronized (agentLock) { for (AgentAck ack : agentsToAcknowledge) { data.add(ack.toString()); } } gui.updateAgentAck(data); } private void updateGUISimulatorAck() { List<String> data = new ArrayList<String>(); synchronized (simLock) { for (SimulatorAck ack : simsToAcknowledge) { data.add(ack.toString()); } } gui.updateSimulatorAck(data); } private void updateGUIViewerAck() { List<String> data = new ArrayList<String>(); synchronized (viewerLock) { for (ViewerAck ack : viewersToAcknowledge) { data.add(ack.toString()); } } gui.updateViewerAck(data); } private class ComponentConnectionListener implements ConnectionListener { @Override public void messageReceived(Connection connection, Message msg) { if (msg instanceof AKConnect) { handleAKConnect((AKConnect)msg, connection); } if (msg instanceof AKAcknowledge) { handleAKAcknowledge((AKAcknowledge)msg, connection); } if (msg instanceof SKConnect) { handleSKConnect((SKConnect)msg, connection); } if (msg instanceof SKAcknowledge) { handleSKAcknowledge((SKAcknowledge)msg, connection); } if (msg instanceof VKConnect) { handleVKConnect((VKConnect)msg, connection); } if (msg instanceof VKAcknowledge) { handleVKAcknowledge((VKAcknowledge)msg, connection); } } private void handleAKConnect(AKConnect connect, Connection connection) { // Pull out the request ID and requested entity type list int requestID = connect.getRequestID(); List<String> types = connect.getRequestedEntityTypes(); // See if we can find an entity for this agent to control. Message reply = null; Logger.debug("AKConnect received: " + types); synchronized (agentLock) { ControlledEntityInfo result = findEntityToControl(types); if (result == null) { Logger.debug("No suitable entities found"); // Send an error reply = new KAConnectError(requestID, "No more agents"); } else { Logger.debug("Found entity to control: " + result); Entity entity = result.entity; AgentProxy agent = new AgentProxy(connect.getAgentName(), entity, connection); agentsToAcknowledge.add(new AgentAck(agent, entity.getID(), requestID, connection)); Logger.info("Agent '" + connect.getAgentName() + "' id " + entity.getID() + " (" + connection + " request ID " + requestID + ") connected"); // Send an OK reply = new KAConnectOK(requestID, entity.getID(), result.visibleSet, result.config); } } if (reply != null) { try { connection.sendMessage(reply); } catch (ConnectionException e) { Logger.error("Error sending reply", e); } } updateGUIUncontrolledAgents(); updateGUIAgentAck(); } private void handleAKAcknowledge(AKAcknowledge msg, Connection connection) { int requestID = msg.getRequestID(); EntityID agentID = msg.getAgentID(); if (agentAcknowledge(requestID, agentID, connection)) { Logger.info("Agent " + agentID + " (" + connection + " request ID " + requestID + ") acknowledged"); } else { Logger.warn("Unexpected acknowledge from agent " + agentID + " (request ID " + requestID + ")"); } updateGUIAgentAck(); } private void handleSKConnect(SKConnect msg, Connection connection) { int simID = getNextSimulatorID(); int requestID = msg.getRequestID(); Logger.info("Simulator '" + msg.getSimulatorName() + "' id " + simID + " (" + connection + " request ID " + requestID + ") connected"); SimulatorProxy sim = new SimulatorProxy(msg.getSimulatorName(), simID, connection); synchronized (simLock) { simsToAcknowledge.add(new SimulatorAck(sim, simID, requestID, connection)); } // Send an OK sim.send(Collections.singleton(new KSConnectOK(simID, requestID, world.getAllEntities(), config))); updateGUISimulatorAck(); } private void handleSKAcknowledge(SKAcknowledge msg, Connection connection) { int requestID = msg.getRequestID(); int simID = msg.getSimulatorID(); if (simAcknowledge(requestID, simID, connection)) { Logger.info("Simulator " + simID + " (" + connection + " request ID " + requestID + ") acknowledged"); } else { Logger.warn("Unexpected acknowledge from simulator " + simID + " (request ID " + requestID + ")"); } updateGUISimulatorAck(); } private void handleVKConnect(VKConnect msg, Connection connection) { int requestID = msg.getRequestID(); int viewerID = getNextViewerID(); Logger.info("Viewer '" + msg.getViewerName() + "' id " + viewerID + " (" + connection + " request ID " + requestID + ") connected"); ViewerProxy viewer = new ViewerProxy(msg.getViewerName(), viewerID, connection); synchronized (viewerLock) { viewersToAcknowledge.add(new ViewerAck(viewer, viewerID, requestID, connection)); } // Send an OK viewer.send(Collections.singleton(new KVConnectOK(viewerID, requestID, world.getAllEntities(), config))); updateGUIViewerAck(); } private void handleVKAcknowledge(VKAcknowledge msg, Connection connection) { int requestID = msg.getRequestID(); int viewerID = msg.getViewerID(); if (viewerAcknowledge(requestID, viewerID, connection)) { Logger.info("Viewer " + viewerID + " (" + connection + " request ID " + requestID + ") acknowledged"); } else { Logger.warn("Unexpected acknowledge from viewer " + viewerID + " (" + requestID + ")"); } updateGUIViewerAck(); } } private static class AgentAck { AgentProxy agent; EntityID agentID; int requestID; Connection connection; public AgentAck(AgentProxy agent, EntityID agentID, int requestID, Connection c) { this.agent = agent; this.agentID = agentID; this.requestID = requestID; this.connection = c; } @Override public String toString() { return agent.getName() + ": " + agent.getControlledEntity().getURN() + " " + agent.getControlledEntity().getID() + "(" + connection + " request ID " + requestID + ")"; } } private static class SimulatorAck { SimulatorProxy sim; int simulatorID; int requestID; Connection connection; public SimulatorAck(SimulatorProxy sim, int simID, int requestID, Connection c) { this.sim = sim; this.simulatorID = simID; this.requestID = requestID; this.connection = c; } @Override public String toString() { return sim + " " + simulatorID + "(connection request ID " + requestID + ")"; } } private static class ViewerAck { ViewerProxy viewer; int viewerID; int requestID; Connection connection; public ViewerAck(ViewerProxy viewer, int viewerID, int requestID, Connection c) { this.viewer = viewer; this.viewerID = viewerID; this.requestID = requestID; this.connection = c; } @Override public String toString() { return viewer + " " + viewerID + "(connection request ID " + requestID + ")"; } } private static class ControlledEntityInfo { Entity entity; Collection<? extends Entity> visibleSet; Config config; public ControlledEntityInfo(Entity entity, Collection<? extends Entity> visibleSet, Config config) { this.entity = entity; this.visibleSet = visibleSet; this.config = config; } @Override public String toString() { return entity.toString(); } } }