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();
}
}
}