package nl.tudelft.bw4t.client.environment;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.bind.JAXBException;
import org.apache.log4j.Logger;
import eis.AgentListener;
import eis.EnvironmentInterfaceStandard;
import eis.EnvironmentListener;
import eis.exceptions.ActException;
import eis.exceptions.AgentException;
import eis.exceptions.EntityException;
import eis.exceptions.ManagementException;
import eis.exceptions.NoEnvironmentException;
import eis.exceptions.PerceiveException;
import eis.exceptions.QueryException;
import eis.exceptions.RelationException;
import eis.iilang.Action;
import eis.iilang.EnvironmentState;
import eis.iilang.Identifier;
import eis.iilang.Parameter;
import eis.iilang.Percept;
import nl.tudelft.bw4t.client.BW4TClient;
import nl.tudelft.bw4t.client.agent.BW4TAgent;
import nl.tudelft.bw4t.client.agent.HumanAgent;
import nl.tudelft.bw4t.client.controller.ClientController;
import nl.tudelft.bw4t.client.startup.InitParam;
import nl.tudelft.bw4t.eis.MapParameter;
import nl.tudelft.bw4t.map.NewMap;
/**
* A remote BW4TEnvironment is an EIS environment
* {@link EnvironmentInterfaceStandard}. Instead of executing actions directly,
* it delegates all actions towards the central BW4TEnvironment, through RMI.
* This is the "Client", the connector for goal. This object lives on the
* client, and is a singleton (so one per JVM).
* <p>
* You can launch a stand-alone BW4TRemoteEnvironment (via {@link #main}.
* Typical args are: <code>
* -clientip localhost -serverip localhost -clientport 2000
* -serverport 8000 -launchgui true -map
* BW4TClient/environments/maps/ColorTestScenario -agentcount 0 -humancount 2
* </code> to run 2 HumanGUIs. Note though that these agents will not be coupled
* to GOAL, and will not appear to GOAL as entities. So you can not communicate
* with them from GOAL by using the GOAL send action.
* <p>
* This RemoteEnvironment functions multiple roles
* <ul>
* <li>as stand-alone client. It just requests the entities as specified in the
* launch parameters and launches one HumanGUI connecting with the server that
* connects with this entity. The new-entity calls are just used to check if a
* humanGUI needs to be connected.
* <li>as plugin for GOAL. New-entity calls from the server are now also
* forwarded to GOAL.
* </ul>
*/
public class RemoteEnvironment implements EnvironmentInterfaceStandard, EnvironmentListener {
/**
* The log4j Logger which displays logs on console
*/
private static final Logger LOGGER = Logger.getLogger(RemoteEnvironment.class);
private BW4TClient client = null;
private final List<EnvironmentListener> environmentListeners = new LinkedList<>();
private final Map<String, ClientController> entityToGUI = new HashMap<>();
/**
* Stores for each agent (represented by a string) a set of listeners.
*/
private final Map<String, HashSet<AgentListener>> agentsToAgentListeners = new HashMap<>();
/**
* List of all active agents.
*/
private final Map<String, BW4TAgent> runningAgents = new HashMap<>();
private final Map<String, List<Percept>> storedPercepts = new HashMap<>();
/**
* {@inheritDoc}
*/
@Override
public void init(Map<String, Parameter> parameters) throws ManagementException {
InitParam.setParameters(parameters);
try {
LOGGER.info("Connecting to BW4T Server.");
client = new BW4TClient(this);
getClient().connectServer();
sendServerParams();
getClient().register();
} catch (RemoteException e) {
LOGGER.error("Unable to access the remote environment.", e);
} catch (MalformedURLException e) {
LOGGER.error("The URL provided to connect to the remote environment is invalid.");
} catch (NotBoundException e) {
LOGGER.error("Unable to bind to the remote environment.");
}
}
/**
* Send the extra parameters to the server.
*
* @throws ManagementException
* if we fail to load a local map or there is an error in the
* communication with the server.
*/
private void sendServerParams() throws ManagementException {
Map<String, Parameter> serverparams = InitParam.getServerParameters();
final boolean mapPresent = !InitParam.MAP.getValue().isEmpty();
if (!serverparams.isEmpty() || mapPresent) {
LOGGER.info(String.format("Sending extra parameters to server: %s", serverparams));
if (mapPresent) {
File mapfile = new File(InitParam.MAP.getValue());
if (mapfile.exists() && !mapfile.isDirectory()) {
try {
serverparams.put(InitParam.MAP.nameLower(), new MapParameter(mapfile));
} catch (FileNotFoundException | JAXBException e) {
throw new ManagementException("Could not load local Map to send to the server.", e);
}
} else {
serverparams.put(InitParam.MAP.nameLower(), new Identifier(mapfile.getName()));
}
}
getClient().initServer(serverparams);
}
}
/**
* Resets the environment(-interface) with a set of key-value-pairs. to
* combine properly with BatchRunner, this reset does not entirely reset the
* env, it does not disconnect the entities. Note that this is NOT the reset
* attached to the reset button in the {@link ServerContextDisplay}.
*/
@Override
public void reset(Map<String, Parameter> params) throws ManagementException {
getClient().resetServer(params);
}
/**
* We detected that environment suddenly died. Notify our listeners and
* return {@link NoEnvironmentException} reporting the problem.
*
* @param e
* is the exception from which we detected the death.
* @return {@link NoEnvironmentException}
*/
public NoEnvironmentException environmentSuddenDeath(Exception e) {
client = null;
LOGGER.error("The BW4T Server disconnected unexpectedly. Client set to null:" + client);
handleStateChange(EnvironmentState.KILLED);
if (e instanceof NoEnvironmentException) {
return (NoEnvironmentException) e;
}
return new NoEnvironmentException("Unable to access environment.", e);
}
/*
* Listener functionality. Attaching, detaching, notifying listeners.
*/
/**
* {@inheritDoc}
*/
@Override
public void attachEnvironmentListener(EnvironmentListener listener) {
if (!getEnvironmentListeners().contains(listener) || listener == this) {
getEnvironmentListeners().add(listener);
}
}
/**
* {@inheritDoc}
*/
@Override
public void detachEnvironmentListener(EnvironmentListener listener) {
if (getEnvironmentListeners().contains(listener)) {
getEnvironmentListeners().remove(listener);
}
}
/**
* {@inheritDoc}
*/
@Override
public void attachAgentListener(String agent, AgentListener listener) {
if (!getLocalAgents().contains(agent)) {
return;
}
Set<AgentListener> listeners = agentsToAgentListeners.get(agent);
if (listeners == null) {
listeners = new HashSet<>();
}
listeners.add(listener);
agentsToAgentListeners.put(agent, (HashSet<AgentListener>) listeners);
}
/**
* {@inheritDoc}
*/
@Override
public void detachAgentListener(String agent, AgentListener listener) {
if (!getLocalAgents().contains(agent)) {
return;
}
Set<AgentListener> listeners = agentsToAgentListeners.get(agent);
if (listeners == null) {
return;
}
listeners.remove(listener);
}
/**
* Method required for GOAL to work
*
* @param entity
* the name of the entity to check
* @return the type of entity
* @throws EntityException
* thrown if an error occured while getting the type
*/
@Override
public String getType(String entity) throws EntityException {
try {
return getClient().getType(entity);
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
/**
* Register an agent to the environment, is passed towards the server
*
* @param agentId
* the agent that should be registered
* @throws AgentException
* failed to register the agent
*/
@Override
public void registerAgent(String agentId) throws AgentException {
LOGGER.debug("Registering new agent:" + agentId + ".");
try {
getClient().registerAgent(agentId);
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
/**
* Used to unregister an agent on the server side
*
* @param agent
* , the agent to unregister
* @throws AgentException
* if the attempt failed
*/
@Override
public void unregisterAgent(String agent) throws AgentException {
try {
LOGGER.debug("Unregistering agent: " + agent);
removeRunningAgent(agent);
getClient().unregisterAgent(agent);
if (getLocalAgents().isEmpty() && !isConnectedToGoal()) {
LOGGER.info("Last local agent was removed. Closing the client.");
try {
kill();
} catch (ManagementException e) {
LOGGER.error("failed to stop the RemoteEnvironment", e);
}
}
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
/**
* Used to free an agent
*
* @param agent
* , the agent to free
* @throws RelationException
* , if an attempt to manipulate the agents-entities-relation
* has failed.
*/
@Override
public void freeAgent(String agent) throws RelationException {
try {
getClient().freeAgent(agent);
// agent is just freed, not removed. Keep it in #localAgents.
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
@Override
public List<String> getAgents() {
try {
return getClient().getAgents();
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
/**
* Gets all associated agents for a certain entity
*
* @param entity
* , the entity
* @return a list of agents
* @throws EntityException
* , if something unexpected happens when attempting to add or
* remove an entity.
*/
@Override
public Collection<String> getAssociatedAgents(String entity) throws EntityException {
try {
return getClient().getAssociatedAgents(entity);
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
@Override
public Set<String> getAssociatedEntities(String agent) throws AgentException {
try {
return getClient().getAssociatedEntities(agent);
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
/**
* Gets all the entities that exist on the server
*
* @return the list of entity names
*/
@Override
public Collection<String> getEntities() {
try {
return getClient().getEntities();
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void associateEntity(String agentId, String entityId) throws RelationException {
LOGGER.debug("Associating Agent " + agentId + " with Entity " + entityId + ".");
try {
getClient().associateEntity(agentId, entityId);
startEntityGUI(agentId, entityId);
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
} catch (Exception e) {
throw new RelationException("failed to associate entity", e);
}
}
/**
* Startup the GUI by instantiating a {@link ClientController}.
*
* @param agentId
* the agent to attach the controller to
* @param entityId
* the entity displayed by the controller
* @throws EntityException
* if we fail to create the controller or agent
* @throws AgentException
* if we fail to register the new human agent
* @throws RelationException
* if we fail to associate the new human agent
*/
private void startEntityGUI(String agentId, String entityId)
throws EntityException, AgentException, RelationException {
if (hasEntityGUI(entityId)) {
ClientController control = null;
if ("human".equals(getType(entityId))) {
HumanAgent agent = (HumanAgent) getRunningAgent(agentId);
if (agent == null) {
agent = new HumanAgent("Human" + getAgents().size(), this);
agent.registerEntity(entityId);
addRunningAgent(agent);
associateEntity(agent.getAgentId(), entityId);
agent.start();
return;
}
control = new ClientController(this, entityId, agent);
} else {
control = new ClientController(this, entityId);
}
control.startupGUI();
putEntityController(entityId, control);
}
}
/**
* Used to free an entity
*
* @param entity
* , the entity to free
* @throws RelationException
* , if an attempt to manipulate the agents-entities-relation
* has failed.
* @throws EntityException
* , if something unexpected happens when attempting to add or
* remove an entity.
*/
@Override
public void freeEntity(String entity) throws RelationException, EntityException {
try {
getClient().freeEntity(entity);
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
/**
* Used to free an agent-entity pair.
*
* @param agent
* , the agent
* @param entity
* , the entity
* @throws RelationException
* , if an attempt to manipulate the agents-entities-relation
* has failed.
*/
@Override
public void freePair(String agent, String entity) throws RelationException {
try {
removeEntityController(entity);
getClient().freePair(agent, entity);
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
/**
* Check whether the given entity has a gui attached or will have one
* attached.
*
* @param entity
* the entity to check
* @return true iff the entity can have a gui
*/
public boolean hasEntityGUI(String entity) {
if (entityToGUI.containsKey(entity) || storedPercepts.containsKey(entity)) {
return true;
}
try {
String type = getType(entity);
if ("human".equals(type)) {
return true;
} else if ("bot".equals(type)) {
return !isConnectedToGoal() || InitParam.LAUNCHGUI.getBoolValue();
}
} catch (EntityException e) {
LOGGER.error(String.format("Could not retrieve the type of entity %s!", entity), e);
}
return false;
}
/**
* Perform an entity action, is passed towards the server
*
* @param entity
* , the entity that should perform the action
* @param action
* , the action that should be performed
* @return the percept resulting from the action, null if an error occurred.
* @throws ActException
* @throws RemoteException
*/
public Percept performEntityAction(String entity, Action action) throws RemoteException, ActException {
if (isConnectedToGoal() && "sendToGUI".equals(action.getName())) {
final ClientController entityGUI = getEntityController(entity);
if (entityGUI == null) {
ActException e = new ActException("sendToGUI failed:" + entity + " is not connected to a GUI.");
e.setType(ActException.FAILURE);
throw e;
}
return entityGUI.sendToGUI(action.getParameters());
} else {
return getClient().performEntityAction(entity, action);
}
}
/**
* Unfortunately goal requires the main class to be implementing the
* {@link EnvironmentInterfaceStandard}, that is why we had to reroute the
* main method through here.
*
* @see Launcher#launch(String[])
* @param args
* the commandline arguments
*/
public static void main(String[] args) {
Launcher.launch(args);
}
/**
* Check whether an action is supported by this environment.
*
* @param arg0
* The action to be checked
* @return true if the Action is supported by the environment
* @throws ActException
*/
public boolean isSupportedByEnvironment(Action arg0) throws ActException {
try {
return getClient().isSupportedByEnvironment(arg0);
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
/**
* Check whether an action is supported by a type, for now always returns
* false as it should not be used
*
* @return the result
*/
public boolean isSupportedByType(Action arg0, String arg1) {
return true;
}
/**
* Get the required version of EIS
*
* @return the required version of EIS
*/
@Override
public String requiredVersion() {
return "0.5";
}
/**
* Gets all percepts for a certain agent for a specified list of entities
*
* @param agent
* the agent's id
* @param entities
* the list of entities
* @return a list of Percepts for every entity
* @throws PerceiveException
* if an attempt to perform an action or to retrieve percepts
* has failed.
* @throws NoEnvironmentException
* if an attempt to perform an action or to retrieve percepts
* has failed.
*/
@Override
public Map<String, Collection<Percept>> getAllPercepts(String agent, String... entities)
throws PerceiveException, NoEnvironmentException {
/** fail if the environment does not run */
EnvironmentState state = getState();
if (state == EnvironmentState.KILLED) {
throw new NoEnvironmentException("Environment is dead.");
}
if (!(state == EnvironmentState.RUNNING || state == EnvironmentState.PAUSED)) {
throw new PerceiveException("Environment is not running/paused");
}
/** fail if the agent is not registered */
if (!getAgents().contains(agent)) {
throw new PerceiveException("Agent \"" + agent + "\" is not registered.");
}
/** get the associated entities */
Set<String> associatedEntities;
try {
associatedEntities = getAssociatedEntities(agent);
} catch (AgentException e) {
throw new PerceiveException("can't get associated entities of agent " + agent, e);
}
// fail if there are no associated entities */
if ((associatedEntities == null) || associatedEntities.isEmpty()) {
throw new PerceiveException("Agent \"" + agent + "\" has no associated entities.");
}
final Map<String, Collection<Percept>> percepts = gatherPercepts(agent, associatedEntities, entities);
try {
// allow other threads to be processed
Thread.sleep(2);
} catch (InterruptedException e) {
// Ignore being interupted
}
return percepts;
}
/**
* Gets all percepts for a certain agent for a specified list of entities
*
* @param agent
* the agent's name
* @param associatedEntities
* the set of attached entities
* @param entities
* the entities we want percepts for
* @return Returns a map with all percepts
* @throws PerceiveException
* unable to get percepts because the entity does not belong to
* this agent
*/
Map<String, Collection<Percept>> gatherPercepts(String agent, Set<String> associatedEntities, String... entities)
throws PerceiveException {
if (entities.length == 0) {
// No entities selected, get percepts for all associated entities
entities = associatedEntities.toArray(new String[associatedEntities.size()]);
}
Map<String, Collection<Percept>> perceptsMap = new HashMap<>(entities.length);
for (String entity : entities) {
if (!associatedEntities.contains(entity)) {
throw new PerceiveException(
"Entity \"" + entity + "\" has not been associated with the agent \"" + agent + "\".");
}
perceptsMap.put(entity, gatherPercepts(entity));
}
return perceptsMap;
}
/**
* Gather all the new percepts for the given entity from the environment
*
* @param name
* name of the entity
* @return the percepts for the given entity
* @throws PerceiveException
* if we failed to get the entity
*/
public List<Percept> gatherPercepts(String name) throws PerceiveException {
List<Percept> all = PerceptsHandler.getAllPerceptsFromEntity(name, this);
for (Percept p : all) {
p.setSource(name);
}
saveAndSendPercepts(name, all);
return all;
}
/**
* Tries to send the percepts to the given entity, if it is not yet present
* we will store the percepts in a map.
*
* @param entity
* the entity name
* @param percepts
* the percepts for the entity
*/
protected void saveAndSendPercepts(String entity, Collection<Percept> percepts) {
if (!hasEntityGUI(entity)) {
return;
}
ClientController cc = this.getEntityController(entity);
if (cc != null) {
if (isConnectedToGoal() && storedPercepts.containsKey(entity)) {
cc.handlePercepts(storedPercepts.get(entity));
storedPercepts.remove(entity);
}
cc.handlePercepts(percepts);
} else {
storePercepts(entity, percepts);
}
}
/**
* Store the current concatenated with the given percepts to the list of
* stored percepts
*
* @param entity
* the entity to whom the percepts belong
* @param percepts
* the percepts to be used
*/
public void storePercepts(String entity, Collection<Percept> percepts) {
List<Percept> tpercepts = new LinkedList<>();
if (storedPercepts.containsKey(entity)) {
tpercepts.addAll(storedPercepts.get(entity));
}
tpercepts.addAll(percepts);
storedPercepts.put(entity, tpercepts);
}
/**
* Check if an action is supported by an entity, is not used so returns true
*
* @param action
* the action
* @param entity
* the entity
*/
public boolean isSupportedByEntity(Action action, String entity) {
return true;
}
/**
* Check if a certain state transition is valid, always returns true for
* now.
*
* @param oldState
* , the old state of the environment
* @param newState
* , the new state of the environment
*/
@Override
public boolean isStateTransitionValid(EnvironmentState oldState, EnvironmentState newState) {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public void start() throws ManagementException {
getClient().start();
}
/**
* {@inheritDoc}
*/
@Override
public void pause() throws ManagementException {
getClient().pause();
}
/**
* {@inheritDoc}
*/
@Override
public void kill() throws ManagementException {
disposeAllAgents();
try {
getClient().kill();
client = null;
} catch (Exception e) {
throw new ManagementException("problem while killing client", e);
}
}
private Set<String> getLocalAgents() {
try {
return getClient().getLocalAgents();
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
/**
* Remove all agents that are local to this RemoteEnvironment.
*
* @throws ManagementException
*/
private void disposeAllAgents() throws ManagementException {
Set<String> remainingLocalAgents = getLocalAgents();
for (String agentname : remainingLocalAgents) {
try {
for (String entity : getAssociatedEntities(agentname)) {
freePair(agentname, entity);
}
unregisterAgent(agentname);
} catch (AgentException | RelationException e) {
throw new ManagementException("kill failed because agent could not be freed", e);
}
}
}
/**
* Used to kill a human entity. When a GUI is closed, the other GUI's will
* remain open and the environment will not be killed.
*
* @param entity
* The entity to free
* @throws ManagementException
*/
public void killHumanEntity(String entity) throws ManagementException {
try {
freeEntity(entity);
// for (String agent : getAssociatedAgents(entity)) {
//
// // #3335 if GOAL is attached, we have an agent
// // but even then, it seems not clear why the entity would
// // persist
// // It only will leave a dead entity in the maps.
// // if (agent.isEmpty()) {
// // freeEntity(entity); // Why free multiple times?
// // } else {
// // freePair(agent, entity);
// // unregisterAgent(agent);
// // }
// }
} catch (RelationException | EntityException e) {
throw new ManagementException("kill failed because agent could not be freed", e);
}
}
public void addRunningAgent(BW4TAgent agent) throws AgentException {
registerAgent(agent.getAgentId());
runningAgents.put(agent.getAgentId(), agent);
}
/**
* Removes the given agent object from the running agents list. If this was
* the last running agent, and we are not connected with GOAL, then we close
* this client
*
* @param agent
* the name of the agent to remove
*/
public void removeRunningAgent(String agent) {
try {
for (String entity : getAssociatedEntities(agent)) {
removeEntityController(entity);
}
} catch (AgentException e) {
LOGGER.warn("Unable to get associated entities.", e);
}
runningAgents.remove(agent);
}
public void removeRunningAgent(BW4TAgent agent) {
removeRunningAgent(agent.getAgentId());
}
public BW4TAgent getRunningAgent(String name) {
return runningAgents.get(name);
}
/**
* Gets all free entities
*
* @return a list of free entities
*/
@Override
public Collection<String> getFreeEntities() {
if (client == null) {
return new HashSet<>(0);
}
try {
return getClient().getFreeEntities();
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
/**
* Used to let one or more entities perform an action
*
* @param agent
* , the agent that should be connected to the entity
* @param action
* , the action that should be performed
* @param entities
* , the entities that should perform the action
* @return a map of entityname->percept that was the result of the action
* @throws ActException
* , if an attempt to perform an action has failed.
*/
@Override
public Map<String, Percept> performAction(String agent, Action action, String... entities) throws ActException {
try {
return ActionHandler.performActionDelegated(agent, action, this, entities);
} catch (ActException e) {
throw e;
} catch (Exception e) {
ActException e1 = new ActException("failed to perform action", ActException.FAILURE);
e1.initCause(e);
throw e1;
}
}
/**
* Used to get the current state of the environment
*
* @return the current state of the environment
*/
@Override
public EnvironmentState getState() {
if (getClient() != null) {
try {
final EnvironmentState state = getClient().getState();
LOGGER.debug("Getting the environment state: " + state);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// ignore interruptions
}
return state;
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
return EnvironmentState.KILLED;
}
public NewMap getMap() {
return getClient().getMap();
}
public BW4TClient getClient() {
return client;
}
public boolean isConnectedToGoal() {
return InitParam.GOAL.getBoolValue();
}
@Override
public boolean isInitSupported() {
return true;
}
@Override
public boolean isStartSupported() {
return true;
}
@Override
public boolean isPauseSupported() {
return true;
}
@Override
public boolean isKillSupported() {
return true;
}
/**
* Get a property from the environment
*
* @param property
* , the property to query
* @return the property
* @throws QueryException
* when the query fails
*/
@Override
public String queryProperty(String property) throws QueryException {
try {
return getClient().queryProperty(property);
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
/**
* Get a property from a certain entity
*
* @param entity
* , the entity
* @param property
* , the property to query
* @return the property
* @throws QueryException
* when the query fails
*/
@Override
public String queryEntityProperty(String entity, String property) throws QueryException {
try {
return getClient().queryEntityProperty(entity, property);
} catch (RemoteException e) {
throw environmentSuddenDeath(e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void handleNewEntity(String entity) {
EntityNotifiers.notifyNewEntity(entity, this);
}
/**
* {@inheritDoc}
*/
@Override
public void handleFreeEntity(String entity, Collection<String> agents) {
EntityNotifiers.notifyFreeEntity(entity, agents, this);
}
/**
* {@inheritDoc}
*/
@Override
public void handleDeletedEntity(String entity, Collection<String> agents) {
EntityNotifiers.notifyDeletedEntity(entity, agents, this);
}
/**
* This is called from the server, via the BW4T Client.
*
* @param newState
*/
@Override
public void handleStateChange(EnvironmentState newState) {
for (EnvironmentListener listener : getEnvironmentListeners()) {
listener.handleStateChange(newState);
}
}
/**
* Get the gui controller associated with the given entity.
*
* @param entity
* the name of the entity
* @return the gui controller
*/
public ClientController getEntityController(String entity) {
return entityToGUI.get(entity);
}
/**
* Add or remove a gui controller to/from the system.
*
* @param entity
* the name of the entity
* @param control
* the gui controller
*/
public void putEntityController(String entity, ClientController control) {
if (control == null) {
entityToGUI.remove(entity);
} else {
entityToGUI.put(entity, control);
}
}
/**
* Stops any associated gui from this client connected to the given entity.
*
* @param entity
* the name of the entity to be disconnected
*/
public void removeEntityController(String entity) {
ClientController control = getEntityController(entity);
if (control != null) {
control.stop();
putEntityController(entity, null);
try {
// Take some time to let the thread stop itself
Thread.sleep(50);
} catch (InterruptedException e) {
// ignore interruptions
}
}
}
List<EnvironmentListener> getEnvironmentListeners() {
return environmentListeners;
}
public List<Percept> getStoredPercepts(String selectedEntity) {
return storedPercepts.get(selectedEntity);
}
}