Skip to content

Commit

Permalink
use server-side timers; closes #930; closes #1065
Browse files Browse the repository at this point in the history
  • Loading branch information
jflamy committed Jul 23, 2024
1 parent 35301fb commit be00625
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import app.owlcms.publicresults.TimerReceiverServlet;
import app.owlcms.publicresults.UpdateReceiverServlet;
import app.owlcms.uievents.BreakTimerEvent;
import app.owlcms.uievents.BreakTimerEvent.BreakStart;
import app.owlcms.uievents.BreakType;
import app.owlcms.uievents.DecisionEvent;
import app.owlcms.uievents.DecisionEventType;
Expand Down Expand Up @@ -480,14 +479,16 @@ public void slaveUpdateEvent(UpdateEvent e) {
logger.debug("### not in a group");
doDone(e.getFullName());
this.needReset = true;
} else if ("BREAK".equals(fopState)) {
logger.debug("### in a break {}", e.getBreakType());
// also trigger a break timer event to make sure we are in sync with owlcms
BreakStart breakStart = new BreakStart(e.getBreakRemaining(), e.isIndefinite());
breakStart.setFopName(e.getFopName());
TimerReceiverServlet.getEventBus().post(breakStart);
this.needReset = true;
} else if (!this.needReset) {
}
// else if ("BREAK".equals(fopState)) {
// logger.debug("### in a break {}", e.getBreakType());
// // also trigger a break timer event to make sure we are in sync with owlcms
// BreakStart breakStart = new BreakStart(e.getBreakRemaining(), e.isIndefinite());
// breakStart.setFopName(e.getFopName());
// TimerReceiverServlet.getEventBus().post(breakStart);
// this.needReset = true;
// }
else if (!this.needReset) {
// logger.debug("no reset");
} else {
logger.debug("### resetting becase of ranking update");
Expand Down Expand Up @@ -518,14 +519,16 @@ protected void onAttach(AttachEvent attachEvent) {
getEventObserver().setTitle(fopName2);

UpdateEvent initEvent = UpdateReceiverServlet.sync(fopName2);
//FIXME: set timers based on last received timer event.
if (initEvent != null) {
slaveUpdateEvent(initEvent);
this.timer.slaveOrderUpdated(initEvent);
} else {
getElement().setProperty("fulName", Translator.translate("WaitingForSite"));
getElement().setProperty("groupInfo", "");
}

TimerReceiverServlet.syncAthleteTimer(fopName2, this.timer);
TimerReceiverServlet.syncBreakTimer(fopName2, this.breakTimer);
}

/**
Expand Down
74 changes: 74 additions & 0 deletions publicresults/src/main/java/app/owlcms/prutils/CountdownTimer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package app.owlcms.prutils;

import java.util.Timer;
import java.util.TimerTask;

public class CountdownTimer {
private Timer timer;
private long startTime;
private long duration;
private TimerTask task;
private boolean running;

public CountdownTimer(long duration) {
this.duration = duration;
this.timer = new Timer();
this.running = false;
this.task = new TimerTask() {
@Override
public void run() {
// Task to be scheduled
}
};
}

public void start() {
this.running = true;
this.startTime = System.currentTimeMillis();
this.timer.schedule(task, this.duration);
}

public void set(long duration) {
stop();
this.duration = duration;
}

public void stop() {
this.running = false;
this.timer.cancel();
this.timer.purge();
}

public void restart() {
this.timer = new Timer();
this.task = new TimerTask() {
@Override
public void run() {
// Task to be scheduled
}
};
start();
}

public void restartAtValue(long duration) {
stop();
this.duration = duration;
restart();
}

public long getTimeRemaining() {
if (!running) {
return this.duration;
}
long elapsedTime = System.currentTimeMillis() - this.startTime;
return this.duration - elapsedTime;
}

public boolean isRunning() {
return running;
}

public boolean isIndefinite() {
return duration < 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@

import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.slf4j.LoggerFactory;

import com.google.common.eventbus.EventBus;

import app.owlcms.components.elements.AthleteTimerElementPR;
import app.owlcms.components.elements.BreakTimerElementPR;
import app.owlcms.prutils.CountdownTimer;
import app.owlcms.uievents.BreakTimerEvent;
import app.owlcms.uievents.TimerEvent;
import app.owlcms.utils.LoggerUtils;
Expand All @@ -31,15 +36,13 @@
public class TimerReceiverServlet extends HttpServlet implements Traceable {

private static String defaultFopName;
// static EventBus eventBus = new AsyncEventBus(TimerReceiverServlet.class.getSimpleName(),
// Executors.newCachedThreadPool());
private static Map<String, CountdownTimer> athleteTimerCache = new HashMap<>();
private static Map<String, CountdownTimer> breakTimerCache = new HashMap<>();

public static EventBus getEventBus() {
return UpdateReceiverServlet.getEventBus();
}

private Logger logger = (Logger) LoggerFactory.getLogger(TimerReceiverServlet.class);

private String secret = StartupUtils.getStringParam("updateKey");

/**
Expand All @@ -61,7 +64,6 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

try {
resp.setCharacterEncoding("UTF-8");
if (StartupUtils.isTraceSetting()) {
Expand All @@ -79,7 +81,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S
return;
}

String fopName = TimerReceiverServlet.processTimerReq(req, resp, getLogger());
String fopName = processTimerReq(req, resp, getLogger());

if (defaultFopName == null) {
defaultFopName = fopName;
Expand All @@ -96,6 +98,8 @@ public static String processTimerReq(HttpServletRequest req, HttpServletResponse
String athleteTimerEventTypeString = req.getParameter("athleteTimerEventType");
String breakTimerEventTypeString = req.getParameter("breakTimerEventType");
String fopName = req.getParameter("fopName");

logger.debug("processing timer request {} {} {}", fopName, athleteTimerEventTypeString, breakTimerEventTypeString);

int athleteMillis = computeAthleteTargetDuration(req);
int breakMillis = computeBreakTargetDuration(req);
Expand All @@ -106,11 +110,15 @@ public static String processTimerReq(HttpServletRequest req, HttpServletResponse
boolean silent = silentString != null ? Boolean.valueOf(silentString) : false;

if (athleteTimerEventTypeString != null) {
CountdownTimer athleteTimer = getAthleteTimerFromCache(fopName);
if (athleteTimerEventTypeString.equals("SetTime")) {
athleteTimer.set(athleteMillis);
timerEvent = new TimerEvent.SetTime(athleteMillis);
} else if (athleteTimerEventTypeString.equals("StopTime")) {
athleteTimer.set(athleteMillis);
timerEvent = new TimerEvent.StopTime(athleteMillis);
} else if (athleteTimerEventTypeString.equals("StartTime")) {
athleteTimer.restartAtValue(athleteMillis);
timerEvent = new TimerEvent.StartTime(athleteMillis, silent);
} else {
String message = MessageFormat.format("**** unknown athlete timer event type {0}", athleteTimerEventTypeString);
Expand All @@ -121,13 +129,18 @@ public static String processTimerReq(HttpServletRequest req, HttpServletResponse
}
}
if (breakTimerEventTypeString != null) {
CountdownTimer breakTimer = getBreakTimerFromCache(fopName);
if (breakTimerEventTypeString.equals("BreakPaused")) {
breakTimer.stop();
breakTimerEvent = new BreakTimerEvent.BreakPaused(breakMillis);
} else if (breakTimerEventTypeString.equals("BreakStarted")) {
breakTimer.restartAtValue(indefinite ? -1 : breakMillis);
breakTimerEvent = new BreakTimerEvent.BreakStart(breakMillis, indefinite);
} else if (breakTimerEventTypeString.equals("BreakDone")) {
breakTimer.stop();
breakTimerEvent = new BreakTimerEvent.BreakDone(null);
} else if (breakTimerEventTypeString.equals("BreakSetTime")) {
breakTimer.set(indefinite ? -1 : athleteMillis);
breakTimerEvent = new BreakTimerEvent.BreakSetTime(breakMillis, indefinite);
} else {
String message = MessageFormat.format("**** unknown break timer event type {0}", breakTimerEventTypeString);
Expand All @@ -150,6 +163,24 @@ public static String processTimerReq(HttpServletRequest req, HttpServletResponse
return fopName;
}

private static CountdownTimer getAthleteTimerFromCache(String fopName) {
CountdownTimer t = athleteTimerCache.get(fopName);
if (t == null) {
t = new CountdownTimer(-1);
athleteTimerCache.put(fopName, t);
}
return t;
}

private static CountdownTimer getBreakTimerFromCache(String fopName) {
CountdownTimer t = breakTimerCache.get(fopName);
if (t == null) {
t = new CountdownTimer(0);
breakTimerCache.put(fopName, t);
}
return t;
}

private static int computeAthleteTargetDuration(HttpServletRequest req) {
String startTimeMillisString = req.getParameter("athleteStartTimeMillis");
String secondsString = req.getParameter("athleteMillisRemaining");
Expand Down Expand Up @@ -179,11 +210,45 @@ private static int computeBreakTargetDuration(HttpServletRequest req) {

@Override
public Logger getLogger() {
return logger;
return (Logger) LoggerFactory.getLogger(TimerReceiverServlet.class);
}

public static void syncAthleteTimer(String fopName2, AthleteTimerElementPR timer) {
CountdownTimer t = getAthleteTimerFromCache(fopName2);
long athleteMillis = t.getTimeRemaining();
TimerEvent timerEvent = null;
if (athleteMillis >= 0) {
if (t.isRunning()) {
timerEvent = new TimerEvent.StartTime((int) athleteMillis, true);

} else {
timerEvent = new TimerEvent.SetTime((int) athleteMillis);
}
}
if (timerEvent != null) {
timerEvent.setFopName(fopName2);
getEventBus().post(timerEvent);
}
}

void setLogger(Logger logger) {
this.logger = logger;
public static void syncBreakTimer(String fopName2, BreakTimerElementPR breakTimer) {
CountdownTimer t = getBreakTimerFromCache(fopName2);
var logger = (Logger) LoggerFactory.getLogger(TimerReceiverServlet.class);
//logger.debug("getBreakTimerFromCache {} {}",System.identityHashCode(t),t.getTimeRemaining());
long athleteMillis = t.getTimeRemaining();
BreakTimerEvent timerEvent = null;
if (athleteMillis >= 0) {
//logger.debug("t.isRunning() {}",t.isRunning());
if (t.isRunning()) {
timerEvent = new BreakTimerEvent.BreakStart((int) athleteMillis, t.isIndefinite());
} else {
timerEvent = new BreakTimerEvent.BreakSetTime((int) athleteMillis, t.isIndefinite());
}
}
if (timerEvent != null) {
timerEvent.setFopName(fopName2);
getEventBus().post(timerEvent);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.vaadin.flow.server.VaadinSession;

import app.owlcms.uievents.BreakType;
import app.owlcms.uievents.UpdateEvent;
Expand Down Expand Up @@ -91,7 +90,6 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
logger.debug("updatereceiverservlet vaadin session {}",VaadinSession.getCurrent());
try {
String updateKey = req.getParameter("updateKey");
if (updateKey == null || !updateKey.equals(this.secret)) {
Expand Down Expand Up @@ -161,7 +159,12 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
String mode = req.getParameter("mode");
updateEvent.setMode(mode);

TimerReceiverServlet.processTimerReq(req, null, getLogger());
String breakTimerEventTypeString = req.getParameter("breakTimerEventType");
// we only process the break timer events. athlete timers wait until next FOP events.
if (breakTimerEventTypeString != null) {
logger.debug("processing break keepalive");
TimerReceiverServlet.processTimerReq(req, null, getLogger());
}

String breakTypeString = req.getParameter("breakType");
updateEvent.setBreak("true".equalsIgnoreCase(req.getParameter("break")));
Expand Down

0 comments on commit be00625

Please sign in to comment.