package jaci.openrio.toast.core.thread;
import jaci.openrio.toast.lib.log.Logger;
import jaci.openrio.toast.lib.state.ConcurrentVector;
/**
* The 'Heartbeat' is a class that keeps a constant ticking routine. The timing between these routines is the same for each
* tick, (100ms/50Hz), meaning tasks that require a scheduled timer may find this useful. Additionally, the Heartbeat will keep track
* of the time required for each 'beat'. If a beat takes longer than the Heart Rate (100ms), the heartbeat will 'skip', and will
* pass an argument to all the listeners that contains the number of beat(s) that were skipped. The difference in time between
* each beat is therefore guaranteed to be a modulus of the Heart Rate (100ms).
*
* Additionally, the Heartbeat Thread will only be alive if there are Listeners in the array. This means the Heartbeat will
* not tick if there is nothing to execute, making sure not to waste system resources.
*
* @author Jaci
*/
public class Heartbeat implements Runnable {
static ConcurrentVector<HeartbeatListener> aorta = new ConcurrentVector<>();
static long heart_rate = 100;
static Logger nervous_system;
int skipped_beats;
static boolean running;
int consecutive;
/**
* Run the Heartbeat thread. This only occurs when there are actual Listeners registered to ensure we don't waste resources
*/
@Override
public void run() {
log().info("Heartbeat started!");
while (true) {
try {
running = true;
long start = System.currentTimeMillis();
aorta.tick();
for (HeartbeatListener artery : aorta)
artery.onHeartbeat(skipped_beats);
long end = System.currentTimeMillis();
long tick_time = end - start;
skipped_beats = 0;
if (tick_time < heart_rate) {
Thread.sleep(heart_rate - tick_time);
consecutive = 0;
} else if (tick_time > heart_rate) {
skipped_beats = (int)(tick_time / heart_rate);
consecutive++;
if (consecutive < 3) {
log().warn(String.format("Heartbeat skipped %s beats, (took %sms of max %sms)", skipped_beats, tick_time, heart_rate));
} else if (consecutive == 3) {
log().warn("Too many consecutive skipped Heartbeats, suppressing log.");
}
Thread.sleep(tick_time % heart_rate);
}
if (aorta.size() == 0) {
running = false;
log().info("No listeners in the queue, we've lost 'er, doc...");
return;
}
} catch (Exception e) {}
}
}
/**
* Get the logger for the Heartbeat
*/
public static Logger log() {
if (nervous_system == null)
nervous_system = new Logger("Heartbeat", Logger.ATTR_TIME | Logger.ATTR_COLOR);
return nervous_system;
}
/**
* Add a listener to the Heartbeat. This can be done at any time
*/
public static void add(HeartbeatListener artery) {
aorta.addConcurrent(artery);
checkActive();
}
/**
* Remove a listener from the Heartbeat. This can be done at any time.
*/
public static void remove(HeartbeatListener artery) {
aorta.removeConcurrent(artery);
}
static Thread heart;
/**
* Check if the heartbeat is active, and start it if it is not.
*/
static void checkActive() {
if (!running) {
heart = new Thread(new Heartbeat());
heart.setName("Heartbeat");
heart.start();
}
}
}