package com.jenjinstudios.client.net;
import com.jenjinstudios.core.Connection;
import com.jenjinstudios.core.MessageIO;
import com.jenjinstudios.core.io.Message;
import com.jenjinstudios.core.io.MessageRegistry;
import java.io.IOException;
import java.security.KeyPair;
import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The base class for any client. This class uses a similar system to the JGSA.
*
* @author Caleb Brinkman
*/
public class Client extends Connection
{
private static final int UPDATES_PER_SECOND = 60;
/** The list of tasks that this client will execute each update cycle. */
private final List<Runnable> repeatedTasks;
/** The timer that manages the update loop. */
private Timer sendMessagesTimer;
private final ClientLoop clientLoop = new ClientLoop(this);
/**
* Construct a new client and attempt to connect to the server over the specified port.
*
* @param messageIO The MessageIO used to send and receive messages.
*/
protected Client(MessageIO messageIO) {
super(messageIO);
repeatedTasks = new LinkedList<>();
}
/**
* Generate a PingRequest message.
*
* @return The generated message.
*/
private static Message generatePingRequest() {
Message pingRequest = MessageRegistry.getInstance().createMessage("PingRequest");
pingRequest.setArgument("requestTimeMillis", System.currentTimeMillis());
return pingRequest;
}
/**
* Add a task to the repeated queue of this client. Should be called to extend client functionality.
*
* @param r The task to be performed.
*/
public void addRepeatedTask(Runnable r) {
synchronized (repeatedTasks)
{
repeatedTasks.add(r);
}
}
/** Tell the client threads to stop running. */
@Override
public void shutdown() {
super.shutdown();
if (sendMessagesTimer != null)
{
sendMessagesTimer.cancel();
}
}
@Override
public void start() {
KeyPair rsaKeyPair = generateRSAKeyPair();
setRSAKeyPair(rsaKeyPair);
// Finally, send a ping request to establish latency.
getMessageIO().queueOutgoingMessage(generatePingRequest());
sendMessagesTimer = new Timer("Client Update Loop", false);
int period = 1000 / UPDATES_PER_SECOND;
sendMessagesTimer.scheduleAtFixedRate(clientLoop, 0, period);
super.start();
}
/** Run the repeated synchronized tasks. */
protected void runRepeatedTasks() {
synchronized (repeatedTasks)
{
for (Runnable r : repeatedTasks)
r.run();
}
}
/**
* Get the average number of updates per second that this client is executing.
*
* @return The average number of updates per second that this client is executing.
*/
public double getAverageUPS() { return 1.0d / clientLoop.getAverageRunTime(); }
/**
* The ClientLoop class is essentially what amounts to the output thread.
*
* @author Caleb Brinkman
*/
private static class ClientLoop extends TimerTask
{
private static final int MAX_STORED_UPDATE_TIMES = 50;
private static final int NANOS_TO_SECOND = 1000000000;
private static final Logger LOGGER = Logger.getLogger(ClientLoop.class.getName());
/** The Client for this loop. */
private final Client client;
private int updateCount;
private long lastStart = System.nanoTime();
private final long[] updateTimesNanos = new long[MAX_STORED_UPDATE_TIMES];
/**
* Construct a ClientLoop for the given client.
*
* @param client The client for this ClientLoop
*/
ClientLoop(Client client) {
this.client = client;
}
@Override
public void run() {
updateCount++;
saveUpdateTime();
client.runRepeatedTasks();
client.getExecutableMessageQueue().runQueuedExecutableMessages();
try
{
client.getMessageIO().writeAllMessages();
} catch (IOException e)
{
LOGGER.log(Level.INFO, "Shutting down because of IOException", e);
client.shutdown();
}
}
private void saveUpdateTime() {
long newStart = System.nanoTime();
long timeElapsed = newStart - lastStart;
lastStart = newStart;
updateTimesNanos[updateCount % updateTimesNanos.length] = timeElapsed;
}
public double getAverageRunTime() {
double maxIndex = Math.min(updateCount, updateTimesNanos.length);
double total = 0;
for (int i = 0; i < maxIndex; i++)
{
total += updateTimesNanos[i];
}
return total / maxIndex / NANOS_TO_SECOND;
}
}
}