package rescuecore2.components;
import rescuecore2.connection.Connection;
import rescuecore2.connection.ConnectionListener;
import rescuecore2.connection.ConnectionException;
import rescuecore2.messages.Message;
import rescuecore2.messages.control.KVTimestep;
import rescuecore2.messages.control.VKConnect;
import rescuecore2.messages.control.VKAcknowledge;
import rescuecore2.messages.control.KVConnectOK;
import rescuecore2.messages.control.KVConnectError;
import rescuecore2.worldmodel.Entity;
import rescuecore2.worldmodel.ChangeSet;
import rescuecore2.worldmodel.WorldModel;
import rescuecore2.config.Config;
import rescuecore2.log.Logger;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
/**
Abstract base class for viewer implementations.
@param <T> The subclass of WorldModel that this viewer understands.
*/
public abstract class AbstractViewer<T extends WorldModel<? extends Entity>> extends AbstractComponent<T> implements Viewer {
/**
The ID of this viewer.
*/
protected int viewerID;
private int lastUpdateTime;
/**
Create a new AbstractViewer.
*/
protected AbstractViewer() {
}
/**
Get this viewer's ID.
@return The viewer ID.
*/
public final int getViewerID() {
return viewerID;
}
@Override
public void postConnect(Connection c, int id, Collection<Entity> entities, Config kernelConfig) {
this.viewerID = id;
lastUpdateTime = 0;
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();
VKConnect connect = new VKConnect(requestID, 1, getName());
CountDownLatch latch = new CountDownLatch(1);
ViewerConnectionListener l = new ViewerConnectionListener(requestID, latch);
connection.addConnectionListener(l);
connection.sendMessage(connect);
// Wait for a reply
latch.await();
l.testSuccess();
}
/**
Handle a KVTimestep object from the server. The default implementation just updates the world model.
@param timestep The KVTimestep object.
*/
protected void handleTimestep(KVTimestep timestep) {
ChangeSet changes = timestep.getChangeSet();
int time = timestep.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);
}
@Override
protected void processMessage(Message msg) {
if (msg instanceof KVTimestep) {
KVTimestep t = (KVTimestep)msg;
if (t.getTargetID() == viewerID) {
handleTimestep(t);
}
}
else {
super.processMessage(msg);
}
}
private class ViewerConnectionListener implements ConnectionListener {
private int requestID;
private CountDownLatch latch;
private ComponentConnectionException failureReason;
public ViewerConnectionListener(int requestID, CountDownLatch latch) {
this.requestID = requestID;
this.latch = latch;
failureReason = null;
}
@Override
public void messageReceived(Connection c, Message msg) {
if (msg instanceof KVConnectOK) {
handleConnectOK(c, (KVConnectOK)msg);
}
if (msg instanceof KVConnectError) {
handleConnectError(c, (KVConnectError)msg);
}
}
private void handleConnectOK(Connection c, KVConnectOK ok) {
if (ok.getRequestID() == requestID) {
c.removeConnectionListener(this);
postConnect(c, ok.getViewerID(), ok.getEntities(), ok.getConfig());
try {
c.sendMessage(new VKAcknowledge(requestID, ok.getViewerID()));
}
catch (ConnectionException e) {
failureReason = new ComponentConnectionException(e);
}
latch.countDown();
}
}
private void handleConnectError(Connection c, KVConnectError 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;
}
}
}
}