package rescuecore2.components;
import rescuecore2.connection.Connection;
import rescuecore2.connection.ConnectionListener;
import rescuecore2.connection.ConnectionException;
import rescuecore2.messages.Message;
import rescuecore2.messages.control.KSUpdate;
import rescuecore2.messages.control.KSCommands;
import rescuecore2.messages.control.SKUpdate;
import rescuecore2.messages.control.SKConnect;
import rescuecore2.messages.control.SKAcknowledge;
import rescuecore2.messages.control.KSConnectOK;
import rescuecore2.messages.control.KSConnectError;
import rescuecore2.messages.control.EntityIDRequest;
import rescuecore2.messages.control.EntityIDResponse;
import rescuecore2.worldmodel.Entity;
import rescuecore2.worldmodel.EntityID;
import rescuecore2.worldmodel.ChangeSet;
import rescuecore2.worldmodel.WorldModel;
import rescuecore2.config.Config;
import rescuecore2.log.Logger;
import java.util.Collection;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
Abstract base class for simulator implementations.
@param <T> The subclass of WorldModel that this simulator understands.
*/
public abstract class AbstractSimulator<T extends WorldModel<? extends Entity>> extends AbstractComponent<T> implements Simulator {
/**
The ID of this simulator.
*/
protected int simulatorID;
private int lastUpdateTime;
private Map<Integer, List<EntityID>> idRequests;
private int nextIDRequest;
/**
Create a new AbstractSimulator.
*/
protected AbstractSimulator() {
}
/**
Get this simulator's ID.
@return The simulator ID.
*/
public final int getSimulatorID() {
return simulatorID;
}
@Override
public void postConnect(Connection c, int id, Collection<Entity> entities, Config kernelConfig) {
this.simulatorID = id;
lastUpdateTime = 0;
nextIDRequest = 0;
idRequests = new HashMap<Integer, List<EntityID>>();
super.postConnect(c, entities, kernelConfig);
}
@Override
public void connect(Connection connection, RequestIDGenerator generator, Config config) throws ConnectionException, ComponentConnectionException, InterruptedException {
this.config = config;
int requestID = generator.generateRequestID();
SKConnect connect = new SKConnect(requestID, 1, getName());
CountDownLatch latch = new CountDownLatch(1);
SimulatorConnectionListener l = new SimulatorConnectionListener(requestID, latch);
connection.addConnectionListener(l);
connection.sendMessage(connect);
// Wait for a reply
latch.await();
l.testSuccess();
}
@Override
public void shutdown() {
super.shutdown();
}
/**
Handle a KSUpdate object from the server. The default implementation just updates the world model.
@param u The Update object.
*/
protected void handleUpdate(KSUpdate u) {
ChangeSet changes = u.getChangeSet();
int time = u.getTime();
if (time != lastUpdateTime + 1) {
Logger.warn("Recieved an unexpected update from the kernel. Last update: " + lastUpdateTime + ", this update: " + time);
}
lastUpdateTime = time;
model.merge(changes);
}
/**
Handle a KSCommands object from the server. The default implementation tells the kernel that nothing has changed.
@param c The Commands object.
*/
protected void handleCommands(KSCommands c) {
ChangeSet changes = new ChangeSet();
processCommands(c, changes);
send(new SKUpdate(simulatorID, c.getTime(), changes));
}
/**
Process the commands from the server and populate a ChangeSet.
@param c The commands to process.
@param changes The ChangeSet to populate.
*/
protected void processCommands(KSCommands c, ChangeSet changes) {
}
/**
Request some new entity IDs from the kernel.
@param count The number to request.
@return A list of new entity IDs.
*/
protected List<EntityID> requestNewEntityIDs(int count) throws InterruptedException {
synchronized (idRequests) {
int id = nextIDRequest++;
Logger.debug("Requesting " + count + " new IDs: request number " + id);
send(new EntityIDRequest(simulatorID, id, count));
// Wait for a reply
Integer key = id;
while (!idRequests.containsKey(key)) {
Logger.debug("Waiting for response");
idRequests.wait();
}
List<EntityID> result = idRequests.get(key);
idRequests.remove(key);
return result;
}
}
@Override
protected void processMessage(Message msg) {
if (msg instanceof KSUpdate) {
KSUpdate u = (KSUpdate)msg;
if (u.getTargetID() == simulatorID) {
handleUpdate(u);
}
}
else if (msg instanceof KSCommands) {
KSCommands commands = (KSCommands)msg;
if (commands.getTargetID() == simulatorID) {
handleCommands(commands);
}
}
else {
super.processMessage(msg);
}
}
@Override
protected boolean processImmediately(Message msg) {
if (msg instanceof EntityIDResponse) {
EntityIDResponse resp = (EntityIDResponse)msg;
Logger.debug("Received " + msg);
if (resp.getSimulatorID() == simulatorID) {
synchronized (idRequests) {
Logger.debug("ID response: " + resp.getRequestID() + ", " + resp.getEntityIDs());
idRequests.put(resp.getRequestID(), resp.getEntityIDs());
idRequests.notifyAll();
}
}
return true;
}
else {
return super.processImmediately(msg);
}
}
private class SimulatorConnectionListener implements ConnectionListener {
private int requestID;
private CountDownLatch latch;
private ComponentConnectionException failureReason;
public SimulatorConnectionListener(int requestID, CountDownLatch latch) {
this.requestID = requestID;
this.latch = latch;
failureReason = null;
}
@Override
public void messageReceived(Connection c, Message msg) {
if (msg instanceof KSConnectOK) {
handleConnectOK(c, (KSConnectOK)msg);
}
if (msg instanceof KSConnectError) {
handleConnectError(c, (KSConnectError)msg);
}
}
private void handleConnectOK(Connection c, KSConnectOK ok) {
if (ok.getRequestID() == requestID) {
c.removeConnectionListener(this);
postConnect(c, ok.getSimulatorID(), ok.getEntities(), ok.getConfig());
try {
c.sendMessage(new SKAcknowledge(requestID, ok.getSimulatorID()));
}
catch (ConnectionException e) {
failureReason = new ComponentConnectionException(e);
}
latch.countDown();
}
}
private void handleConnectError(Connection c, KSConnectError error) {
if (error.getRequestID() == requestID) {
c.removeConnectionListener(this);
failureReason = new ComponentConnectionException(error.getReason());
latch.countDown();
}
}
/**
Check if the connection succeeded and throw an exception if is has not.
*/
void testSuccess() throws ComponentConnectionException {
if (failureReason != null) {
throw failureReason;
}
}
}
}