-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from das-kaesebrot/feature/autopause-detection
Implements new feature: tick drift detection
- Loading branch information
Showing
8 changed files
with
227 additions
and
143 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
src/main/java/eu/kaesebrot/dev/classes/ScheduledMessageTask.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package eu.kaesebrot.dev.classes; | ||
|
||
import org.bukkit.scheduler.BukkitTask; | ||
|
||
public record ScheduledMessageTask(BukkitTask task, long absoluteEndTicks) { | ||
|
||
public void cancelIfEndTicksHavePassed(long absoluteTicksNow) { | ||
if (absoluteTicksNow > absoluteEndTicks && !task.isCancelled()) { | ||
task.cancel(); | ||
} | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
src/main/java/eu/kaesebrot/dev/classes/TickReferencePoint.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package eu.kaesebrot.dev.classes; | ||
|
||
import java.time.ZonedDateTime; | ||
|
||
public class TickReferencePoint { | ||
private long ticks; | ||
private ZonedDateTime dateTime; | ||
|
||
public TickReferencePoint() { | ||
} | ||
|
||
public TickReferencePoint(long ticks, ZonedDateTime dateTime) { | ||
this.ticks = ticks; | ||
this.dateTime = dateTime; | ||
} | ||
|
||
public long getTicks() { | ||
return ticks; | ||
} | ||
|
||
public ZonedDateTime getDateTime() { | ||
return dateTime; | ||
} | ||
|
||
public void setTicks(long ticks) { | ||
this.ticks = ticks; | ||
} | ||
|
||
public void setDateTime(ZonedDateTime dateTime) { | ||
this.dateTime = dateTime; | ||
} | ||
} |
100 changes: 0 additions & 100 deletions
100
src/main/java/eu/kaesebrot/dev/tasks/ScheduledMessageTaskQueuer.java
This file was deleted.
Oops, something went wrong.
155 changes: 155 additions & 0 deletions
155
src/main/java/eu/kaesebrot/dev/tasks/ScheduledMessageTaskScheduler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package eu.kaesebrot.dev.tasks; | ||
|
||
import eu.kaesebrot.dev.classes.ScheduledMessage; | ||
import eu.kaesebrot.dev.classes.ScheduledMessageTask; | ||
import eu.kaesebrot.dev.classes.TickReferencePoint; | ||
import eu.kaesebrot.dev.utils.TickConverter; | ||
import org.bukkit.plugin.java.JavaPlugin; | ||
import org.bukkit.scheduler.BukkitRunnable; | ||
|
||
import java.time.Duration; | ||
import java.time.ZonedDateTime; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class ScheduledMessageTaskScheduler extends BukkitRunnable | ||
{ | ||
private final JavaPlugin plugin; | ||
private final Map<String, ScheduledMessage> scheduledMessages; | ||
private final Duration durationAhead; | ||
private final List<ScheduledMessageTask> activeSubTasks = new ArrayList<>(); | ||
private final TickConverter tickConverter = new TickConverter(); | ||
private TickReferencePoint referencePoint; | ||
private ZonedDateTime lastScheduleAheadUntil; | ||
|
||
public ScheduledMessageTaskScheduler(JavaPlugin plugin, Map<String, ScheduledMessage> scheduledMessages, Duration durationAhead) { | ||
this.plugin = plugin; | ||
this.scheduledMessages = scheduledMessages; | ||
this.durationAhead = durationAhead; | ||
lastScheduleAheadUntil = ZonedDateTime.now(); | ||
|
||
updateReference(); | ||
} | ||
|
||
@Override | ||
public void run() | ||
{ | ||
cleanUpPastRunningSubtasks(); | ||
|
||
var now = ZonedDateTime.now(); | ||
|
||
if (!ticksAreSync()) { | ||
cleanUpAllRunningSubTasks(); | ||
|
||
// reset last scheduling end timeframe to ensure we re-queue all tasks again with the correct timestamps | ||
lastScheduleAheadUntil = now; | ||
} | ||
|
||
// use lastScheduleAheadUntil if it's in the future, use now if last schedule was in the past | ||
// if it's in the past, we're probably in the first ever run | ||
var searchDateStart = (Duration.between(now, lastScheduleAheadUntil).isNegative() ? now : lastScheduleAheadUntil); | ||
var searchDateEnd = now.plus(durationAhead); | ||
|
||
for (var scheduledMessage: scheduledMessages.entrySet()) { | ||
var message = scheduledMessage.getValue(); | ||
|
||
var nextRunTicksForMessage = tickConverter.getNextRunTicksUntil(scheduledMessage.getValue().getSchedule(), | ||
searchDateStart, searchDateEnd); | ||
|
||
if (nextRunTicksForMessage.isEmpty()) | ||
break; | ||
|
||
plugin.getLogger().info(String.format("Scheduling new messages for %s=%s in time slot %s to %s", | ||
scheduledMessage.getKey(), scheduledMessage.getValue().toString(), searchDateStart, searchDateEnd)); | ||
|
||
// guess if we can use a simple repeatable timer | ||
// if present, we can, otherwise we have to queue single tasks for every iteration | ||
var ticksRepeatableInterval = tickConverter.ticksRepeatableInterval(nextRunTicksForMessage); | ||
|
||
if (ticksRepeatableInterval.isPresent()) { | ||
var subTask = getRunnableForMessage(message).runTaskTimer(plugin, nextRunTicksForMessage.get(0), ticksRepeatableInterval.get()); | ||
|
||
activeSubTasks.add(new ScheduledMessageTask(subTask, getAbsoluteTicks() + nextRunTicksForMessage.get(nextRunTicksForMessage.size() - 1))); | ||
|
||
plugin.getLogger().info(String.format("Scheduled repeatable message %s=%s running %s times every %s ticks, first run %s ticks from now", | ||
scheduledMessage.getKey(), message, nextRunTicksForMessage.size(), ticksRepeatableInterval.get(), nextRunTicksForMessage.get(0))); | ||
} | ||
else | ||
{ | ||
for (long nextRunTick: nextRunTicksForMessage) | ||
{ | ||
var subTask = getRunnableForMessage(message).runTaskLater(plugin, nextRunTick); | ||
|
||
var nextRunDateTime = tickConverter.ticksToDateTimeFromNow(nextRunTick); | ||
|
||
activeSubTasks.add(new ScheduledMessageTask(subTask, getAbsoluteTicks() + nextRunTick)); | ||
|
||
plugin.getLogger().info(String.format("Scheduled single-run message %s=%s at %s (%s ticks from now)", | ||
scheduledMessage.getKey(), message, nextRunDateTime.toString(), nextRunTick)); | ||
} | ||
} | ||
} | ||
|
||
lastScheduleAheadUntil = searchDateEnd; | ||
updateReference(); | ||
} | ||
|
||
private void cleanUpAllRunningSubTasks() { | ||
for (ScheduledMessageTask task: activeSubTasks) | ||
{ | ||
task.task().cancel(); | ||
} | ||
|
||
activeSubTasks.clear(); | ||
} | ||
|
||
private void cleanUpPastRunningSubtasks() { | ||
for (ScheduledMessageTask task: activeSubTasks) | ||
{ | ||
task.cancelIfEndTicksHavePassed(getAbsoluteTicks() + TickConverter.getTicksPerSecond()); | ||
} | ||
|
||
activeSubTasks.removeIf(s -> s.task().isCancelled()); | ||
} | ||
|
||
private boolean ticksAreSync() { | ||
var areSync = tickConverter.ticksAreSync( | ||
referencePoint.getTicks(), referencePoint.getDateTime(), | ||
getAbsoluteTicks(), ZonedDateTime.now()); | ||
|
||
if (!areSync) { | ||
plugin.getLogger().warning("Detected sync loss between ticks and real time!"); | ||
updateReference(); | ||
} | ||
|
||
return areSync; | ||
} | ||
|
||
private TickReferencePoint getReferencePointForNow() { | ||
return new TickReferencePoint(getAbsoluteTicks(), ZonedDateTime.now()); | ||
} | ||
|
||
// Update our reference point for checking if we're still synced to real time | ||
private void updateReference() { | ||
if (referencePoint == null) { | ||
referencePoint = getReferencePointForNow(); | ||
return; | ||
} | ||
|
||
referencePoint.setTicks(getAbsoluteTicks()); | ||
referencePoint.setDateTime(ZonedDateTime.now()); | ||
} | ||
|
||
private BukkitRunnable getRunnableForMessage(ScheduledMessage message) { | ||
return switch (message.getType()) { | ||
case TITLE -> new TitleTask(plugin, message.getText()); | ||
case BROADCAST -> new BroadcastTask(plugin, message.getText()); | ||
default -> throw new IllegalArgumentException("Illegal message type provided"); | ||
}; | ||
} | ||
|
||
private long getAbsoluteTicks() { | ||
return plugin.getServer().getWorlds().get(0).getFullTime(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.