package kernel; import java.util.Collections; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.List; import java.util.ArrayList; import java.io.IOException; import java.io.File; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import java.util.concurrent.Callable; import rescuecore2.config.Config; import rescuecore2.worldmodel.Entity; import rescuecore2.worldmodel.EntityID; import rescuecore2.worldmodel.WorldModel; import rescuecore2.worldmodel.ChangeSet; import rescuecore2.messages.Command; import rescuecore2.Constants; import rescuecore2.Timestep; import rescuecore2.score.ScoreFunction; //import rescuecore2.misc.gui.ChangeSetComponent; import rescuecore2.log.LogWriter; import rescuecore2.log.FileLogWriter; import rescuecore2.log.InitialConditionsRecord; import rescuecore2.log.StartLogRecord; import rescuecore2.log.EndLogRecord; import rescuecore2.log.ConfigRecord; import rescuecore2.log.PerceptionRecord; import rescuecore2.log.CommandsRecord; import rescuecore2.log.UpdatesRecord; import rescuecore2.log.LogException; import rescuecore2.log.Logger; /** The Robocup Rescue kernel. */ public class Kernel { /** The log context for kernel log messages. */ public static final String KERNEL_LOG_CONTEXT = "kernel"; private Config config; private Perception perception; private CommunicationModel communicationModel; private WorldModel<? extends Entity> worldModel; private LogWriter log; private Set<KernelListener> listeners; private Collection<AgentProxy> agents; private Collection<SimulatorProxy> sims; private Collection<ViewerProxy> viewers; private int time; private Timestep previousTimestep; private EntityIDGenerator idGenerator; private CommandFilter commandFilter; private TerminationCondition termination; private ScoreFunction score; private CommandCollector commandCollector; private boolean isShutdown; // private ChangeSetComponent simulatorChanges; /** Construct a kernel. @param config The configuration to use. @param perception A perception calculator. @param communicationModel A communication model. @param worldModel The world model. @param idGenerator An EntityIDGenerator. @param commandFilter An optional command filter. This may be null. @param termination The termination condition. @param score The score function. @param collector The CommandCollector to use. @throws KernelException If there is a problem constructing the kernel. */ public Kernel(Config config, Perception perception, CommunicationModel communicationModel, WorldModel<? extends Entity> worldModel, EntityIDGenerator idGenerator, CommandFilter commandFilter, TerminationCondition termination, ScoreFunction score, CommandCollector collector) throws KernelException { try { Logger.pushLogContext(KERNEL_LOG_CONTEXT); this.config = config; this.perception = perception; this.communicationModel = communicationModel; this.worldModel = worldModel; this.commandFilter = commandFilter; this.score = score; this.termination = termination; this.commandCollector = collector; this.idGenerator = idGenerator; listeners = new HashSet<KernelListener>(); agents = new HashSet<AgentProxy>(); sims = new HashSet<SimulatorProxy>(); viewers = new HashSet<ViewerProxy>(); time = 0; try { String logName = config.getValue("kernel.logname"); Logger.info("Logging to " + logName); File logFile = new File(logName); if (logFile.getParentFile().mkdirs()) { Logger.info("Created log directory: " + logFile.getParentFile().getAbsolutePath()); } if (logFile.createNewFile()) { Logger.info("Created log file: " + logFile.getAbsolutePath()); } log = new FileLogWriter(logFile); log.writeRecord(new StartLogRecord()); log.writeRecord(new InitialConditionsRecord(worldModel)); log.writeRecord(new ConfigRecord(config)); } catch (IOException e) { throw new KernelException("Couldn't open log file for writing", e); } catch (LogException e) { throw new KernelException("Couldn't open log file for writing", e); } config.setValue(Constants.COMMUNICATION_MODEL_KEY, communicationModel.getClass().getName()); config.setValue(Constants.PERCEPTION_KEY, perception.getClass().getName()); // simulatorChanges = new ChangeSetComponent(); // Initialise perception.initialise(config, worldModel); communicationModel.initialise(config, worldModel); commandFilter.initialise(config); score.initialise(worldModel, config); termination.initialise(config); commandCollector.initialise(config); isShutdown = false; Logger.info("Kernel initialised"); Logger.info("Perception module: " + perception); Logger.info("Communication module: " + communicationModel); Logger.info("Command filter: " + commandFilter); Logger.info("Score function: " + score); Logger.info("Termination condition: " + termination); Logger.info("Command collector: " + collector); } finally { Logger.popLogContext(); } } /** Get the kernel's configuration. @return The configuration. */ public Config getConfig() { return config; } /** Get a snapshot of the kernel's state. @return A new KernelState snapshot. */ public KernelState getState() { return new KernelState(getTime(), getWorldModel()); } /** Add an agent to the system. @param agent The agent to add. */ public void addAgent(AgentProxy agent) { synchronized (this) { agents.add(agent); } fireAgentAdded(agent); } /** Remove an agent from the system. @param agent The agent to remove. */ public void removeAgent(AgentProxy agent) { synchronized (this) { agents.remove(agent); } fireAgentRemoved(agent); } /** Get all agents in the system. @return An unmodifiable view of all agents. */ public Collection<AgentProxy> getAllAgents() { synchronized (this) { return Collections.unmodifiableCollection(agents); } } /** Add a simulator to the system. @param sim The simulator to add. */ public void addSimulator(SimulatorProxy sim) { synchronized (this) { sims.add(sim); sim.setEntityIDGenerator(idGenerator); } fireSimulatorAdded(sim); } /** Remove a simulator from the system. @param sim The simulator to remove. */ public void removeSimulator(SimulatorProxy sim) { synchronized (this) { sims.remove(sim); } fireSimulatorRemoved(sim); } /** Get all simulators in the system. @return An unmodifiable view of all simulators. */ public Collection<SimulatorProxy> getAllSimulators() { synchronized (this) { return Collections.unmodifiableCollection(sims); } } /** Add a viewer to the system. @param viewer The viewer to add. */ public void addViewer(ViewerProxy viewer) { synchronized (this) { viewers.add(viewer); } fireViewerAdded(viewer); } /** Remove a viewer from the system. @param viewer The viewer to remove. */ public void removeViewer(ViewerProxy viewer) { synchronized (this) { viewers.remove(viewer); } fireViewerRemoved(viewer); } /** Get all viewers in the system. @return An unmodifiable view of all viewers. */ public Collection<ViewerProxy> getAllViewers() { synchronized (this) { return Collections.unmodifiableCollection(viewers); } } /** Add a KernelListener. @param l The listener to add. */ public void addKernelListener(KernelListener l) { synchronized (listeners) { listeners.add(l); } } /** Remove a KernelListener. @param l The listener to remove. */ public void removeKernelListener(KernelListener l) { synchronized (listeners) { listeners.remove(l); } } /** Get the current time. @return The current time. */ public int getTime() { synchronized (this) { return time; } } /** Get the world model. @return The world model. */ public WorldModel<? extends Entity> getWorldModel() { return worldModel; } /** Find out if the kernel has terminated. @return True if the kernel has terminated, false otherwise. */ public boolean hasTerminated() { synchronized (this) { return isShutdown || termination.shouldStop(getState()); } } /** Run a single timestep. @throws InterruptedException If this thread is interrupted during the timestep. @throws KernelException If there is a problem executing the timestep. @throws LogException If there is a problem writing the log. */ public void timestep() throws InterruptedException, KernelException, LogException { try { Logger.pushLogContext(KERNEL_LOG_CONTEXT); synchronized (this) { if (time == 0) { fireStarted(); } if (isShutdown) { return; } ++time; // Work out what the agents can see and hear (using the commands from the previous timestep). // Wait for new commands // Send commands to simulators and wait for updates // Collate updates and broadcast to simulators // Send perception, commands and updates to viewers Timestep nextTimestep = new Timestep(time); Logger.info("Timestep " + time); Logger.debug("Sending agent updates"); long start = System.currentTimeMillis(); sendAgentUpdates(nextTimestep, previousTimestep == null ? new HashSet<Command>() : previousTimestep.getCommands()); long perceptionTime = System.currentTimeMillis(); Logger.debug("Waiting for commands"); Collection<Command> commands = waitForCommands(time); nextTimestep.setCommands(commands); log.writeRecord(new CommandsRecord(time, commands)); long commandsTime = System.currentTimeMillis(); Logger.debug("Broadcasting commands"); ChangeSet changes = sendCommandsToSimulators(time, commands); // simulatorUpdates.show(changes); nextTimestep.setChangeSet(changes); log.writeRecord(new UpdatesRecord(time, changes)); long updatesTime = System.currentTimeMillis(); // Merge updates into world model worldModel.merge(changes); long mergeTime = System.currentTimeMillis(); Logger.debug("Broadcasting updates"); sendUpdatesToSimulators(time, changes); sendToViewers(nextTimestep); long broadcastTime = System.currentTimeMillis(); Logger.debug("Computing score"); double s = score.score(worldModel, nextTimestep); long scoreTime = System.currentTimeMillis(); nextTimestep.setScore(s); Logger.info("Timestep " + time + " complete"); Logger.debug("Score: " + s); Logger.debug("Perception took : " + (perceptionTime - start) + "ms"); Logger.debug("Agent commands took : " + (commandsTime - perceptionTime) + "ms"); Logger.debug("Simulator updates took : " + (updatesTime - commandsTime) + "ms"); Logger.debug("World model merge took : " + (mergeTime - updatesTime) + "ms"); Logger.debug("Update broadcast took : " + (broadcastTime - mergeTime) + "ms"); Logger.debug("Score calculation took : " + (scoreTime - broadcastTime) + "ms"); Logger.debug("Total time : " + (scoreTime - start) + "ms"); fireTimestepCompleted(nextTimestep); previousTimestep = nextTimestep; Logger.debug("Commands: " + commands); Logger.debug("Timestep commands: " + previousTimestep.getCommands()); } } finally { Logger.popLogContext(); } } /** Shut down the kernel. This method will notify all agents/simulators/viewers of the shutdown. */ public void shutdown() { synchronized (this) { if (isShutdown) { return; } Logger.info("Kernel is shutting down"); ExecutorService service = Executors.newFixedThreadPool(agents.size() + sims.size() + viewers.size()); List<Callable<Object>> callables = new ArrayList<Callable<Object>>(); for (AgentProxy next : agents) { final AgentProxy proxy = next; callables.add(Executors.callable(new Runnable() { @Override public void run() { proxy.shutdown(); } })); } for (SimulatorProxy next : sims) { final SimulatorProxy proxy = next; callables.add(Executors.callable(new Runnable() { @Override public void run() { proxy.shutdown(); } })); } for (ViewerProxy next : viewers) { final ViewerProxy proxy = next; callables.add(Executors.callable(new Runnable() { @Override public void run() { proxy.shutdown(); } })); } try { service.invokeAll(callables); } catch (InterruptedException e) { Logger.warn("Interrupted during shutdown"); } try { log.writeRecord(new EndLogRecord()); log.close(); } catch (LogException e) { Logger.error("Error closing log", e); } Logger.info("Kernel has shut down"); isShutdown = true; fireShutdown(); } } private void sendAgentUpdates(Timestep timestep, Collection<Command> commandsLastTimestep) throws InterruptedException, KernelException, LogException { perception.setTime(time); communicationModel.process(time, commandsLastTimestep); for (AgentProxy next : agents) { if (Thread.interrupted()) { throw new InterruptedException(); } ChangeSet visible = perception.getVisibleEntities(next); Collection<Command> heard = communicationModel.getHearing(next.getControlledEntity()); EntityID id = next.getControlledEntity().getID(); timestep.registerPerception(id, visible, heard); log.writeRecord(new PerceptionRecord(time, id, visible, heard)); next.sendPerceptionUpdate(time, visible, heard); } } private Collection<Command> waitForCommands(int timestep) throws InterruptedException { Collection<Command> commands = commandCollector.getAgentCommands(agents, timestep); Logger.debug("Raw commands: " + commands); commandFilter.filter(commands, getState()); Logger.debug("Filtered commands: " + commands); return commands; } /** Send commands to all simulators and return which entities have been updated by the simulators. */ private ChangeSet sendCommandsToSimulators(int timestep, Collection<Command> commands) throws InterruptedException { for (SimulatorProxy next : sims) { next.sendAgentCommands(timestep, commands); } // Wait until all simulators have sent updates ChangeSet result = new ChangeSet(); for (SimulatorProxy next : sims) { Logger.debug("Fetching updates from " + next); result.merge(next.getUpdates(timestep)); } return result; } private void sendUpdatesToSimulators(int timestep, ChangeSet updates) throws InterruptedException { for (SimulatorProxy next : sims) { next.sendUpdate(timestep, updates); } } private void sendToViewers(Timestep timestep) { for (ViewerProxy next : viewers) { next.sendTimestep(timestep); } } private Set<KernelListener> getListeners() { Set<KernelListener> result; synchronized (listeners) { result = new HashSet<KernelListener>(listeners); } return result; } private void fireStarted() { for (KernelListener next : getListeners()) { next.simulationStarted(this); } } private void fireShutdown() { for (KernelListener next : getListeners()) { next.simulationEnded(this); } } private void fireTimestepCompleted(Timestep timestep) { for (KernelListener next : getListeners()) { next.timestepCompleted(this, timestep); } } private void fireAgentAdded(AgentProxy agent) { for (KernelListener next : getListeners()) { next.agentAdded(this, agent); } } private void fireAgentRemoved(AgentProxy agent) { for (KernelListener next : getListeners()) { next.agentRemoved(this, agent); } } private void fireSimulatorAdded(SimulatorProxy sim) { for (KernelListener next : getListeners()) { next.simulatorAdded(this, sim); } } private void fireSimulatorRemoved(SimulatorProxy sim) { for (KernelListener next : getListeners()) { next.simulatorRemoved(this, sim); } } private void fireViewerAdded(ViewerProxy viewer) { for (KernelListener next : getListeners()) { next.viewerAdded(this, viewer); } } private void fireViewerRemoved(ViewerProxy viewer) { for (KernelListener next : getListeners()) { next.viewerRemoved(this, viewer); } } }