2021-12-16 00:15:55 +01:00
|
|
|
package net.minestom.server.timer;
|
|
|
|
|
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
|
2024-03-21 22:42:24 +01:00
|
|
|
import net.minestom.server.MinecraftServer;
|
2021-12-27 12:28:02 +01:00
|
|
|
import org.jctools.queues.MpscUnboundedArrayQueue;
|
2021-12-16 00:15:55 +01:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
|
|
|
|
import java.time.Duration;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
2021-12-16 14:23:25 +01:00
|
|
|
import java.util.concurrent.Executors;
|
|
|
|
import java.util.concurrent.ForkJoinPool;
|
|
|
|
import java.util.concurrent.ScheduledExecutorService;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
2021-12-16 00:15:55 +01:00
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
import java.util.function.Supplier;
|
|
|
|
|
|
|
|
final class SchedulerImpl implements Scheduler {
|
|
|
|
private static final AtomicInteger TASK_COUNTER = new AtomicInteger();
|
|
|
|
private static final ScheduledExecutorService SCHEDULER = Executors.newSingleThreadScheduledExecutor(r -> {
|
|
|
|
Thread thread = new Thread(r);
|
|
|
|
thread.setDaemon(true);
|
|
|
|
return thread;
|
|
|
|
});
|
|
|
|
private static final ForkJoinPool EXECUTOR = ForkJoinPool.commonPool();
|
|
|
|
|
2024-03-31 05:51:08 +02:00
|
|
|
private final MpscUnboundedArrayQueue<TaskImpl> tasksToExecute = new MpscUnboundedArrayQueue<>(64);
|
|
|
|
private final MpscUnboundedArrayQueue<TaskImpl> tickEndTasksToExecute = new MpscUnboundedArrayQueue<>(64);
|
|
|
|
// Tasks scheduled on a certain tick/tick end
|
|
|
|
private final Int2ObjectAVLTreeMap<List<TaskImpl>> tickStartTaskQueue = new Int2ObjectAVLTreeMap<>();
|
|
|
|
private final Int2ObjectAVLTreeMap<List<TaskImpl>> tickEndTaskQueue = new Int2ObjectAVLTreeMap<>();
|
2021-12-16 00:15:55 +01:00
|
|
|
|
|
|
|
private int tickState;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void process() {
|
|
|
|
processTick(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void processTick() {
|
|
|
|
processTick(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void processTick(int tickDelta) {
|
2024-03-31 05:51:08 +02:00
|
|
|
processTickTasks(tickStartTaskQueue, tasksToExecute, tickDelta);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void processTickEnd() {
|
|
|
|
processTickTasks(tickEndTaskQueue, tickEndTasksToExecute, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void processTickTasks(Int2ObjectAVLTreeMap<List<TaskImpl>> targetTaskQueue, MpscUnboundedArrayQueue<TaskImpl> targetTasksToExecute, int tickDelta) {
|
2021-12-16 00:15:55 +01:00
|
|
|
synchronized (this) {
|
|
|
|
this.tickState += tickDelta;
|
|
|
|
int tickToProcess;
|
2024-03-31 05:51:08 +02:00
|
|
|
while (!targetTaskQueue.isEmpty() && (tickToProcess = targetTaskQueue.firstIntKey()) <= tickState) {
|
|
|
|
final List<TaskImpl> tickScheduledTasks = targetTaskQueue.remove(tickToProcess);
|
|
|
|
if (tickScheduledTasks != null) tickScheduledTasks.forEach(targetTasksToExecute::relaxedOffer);
|
2021-12-16 00:15:55 +01:00
|
|
|
}
|
|
|
|
}
|
2024-03-31 05:51:08 +02:00
|
|
|
runTasks(targetTasksToExecute);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void runTasks(MpscUnboundedArrayQueue<TaskImpl> targetQueue) {
|
2021-12-16 00:15:55 +01:00
|
|
|
// Run all tasks lock-free, either in the current thread or pool
|
2024-03-31 05:51:08 +02:00
|
|
|
if (!targetQueue.isEmpty()) {
|
|
|
|
targetQueue.drain(task -> {
|
2021-12-16 16:07:07 +01:00
|
|
|
if (!task.isAlive()) return;
|
|
|
|
switch (task.executionType()) {
|
2024-03-31 05:51:08 +02:00
|
|
|
case TICK_START, TICK_END, SYNC -> handleTask(task);
|
2021-12-16 16:07:07 +01:00
|
|
|
case ASYNC -> EXECUTOR.submit(() -> handleTask(task));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2021-12-16 00:15:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @NotNull Task submitTask(@NotNull Supplier<TaskSchedule> task,
|
|
|
|
@NotNull ExecutionType executionType) {
|
2022-03-10 16:57:36 +01:00
|
|
|
final TaskImpl taskRef = new TaskImpl(TASK_COUNTER.getAndIncrement(), task,
|
|
|
|
executionType, this);
|
2021-12-16 00:15:55 +01:00
|
|
|
handleTask(taskRef);
|
|
|
|
return taskRef;
|
|
|
|
}
|
|
|
|
|
2022-03-10 16:57:36 +01:00
|
|
|
void unparkTask(TaskImpl task) {
|
|
|
|
if (task.tryUnpark())
|
2024-03-31 05:51:08 +02:00
|
|
|
this.tasksToExecute.relaxedOffer(task);
|
2021-12-16 00:15:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private void safeExecute(TaskImpl task) {
|
|
|
|
// Prevent the task from being executed in the current thread
|
|
|
|
// By either adding the task to the execution queue or submitting it to the pool
|
|
|
|
switch (task.executionType()) {
|
2024-03-31 05:51:08 +02:00
|
|
|
case TICK_START, SYNC -> tasksToExecute.offer(task);
|
|
|
|
case TICK_END -> tickEndTasksToExecute.offer(task);
|
2022-04-11 20:26:59 +02:00
|
|
|
case ASYNC -> EXECUTOR.submit(() -> {
|
|
|
|
if (!task.isAlive()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
handleTask(task);
|
|
|
|
});
|
2021-12-16 00:15:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void handleTask(TaskImpl task) {
|
2024-03-21 22:42:24 +01:00
|
|
|
TaskSchedule schedule;
|
|
|
|
try {
|
|
|
|
schedule = task.task().get();
|
|
|
|
} catch (Throwable t) {
|
|
|
|
MinecraftServer.getExceptionManager().handleException(new RuntimeException("Exception in scheduled task", t));
|
|
|
|
schedule = TaskSchedule.stop();
|
|
|
|
}
|
|
|
|
|
2021-12-16 00:15:55 +01:00
|
|
|
if (schedule instanceof TaskScheduleImpl.DurationSchedule durationSchedule) {
|
|
|
|
final Duration duration = durationSchedule.duration();
|
|
|
|
SCHEDULER.schedule(() -> safeExecute(task), duration.toMillis(), TimeUnit.MILLISECONDS);
|
|
|
|
} else if (schedule instanceof TaskScheduleImpl.TickSchedule tickSchedule) {
|
|
|
|
synchronized (this) {
|
|
|
|
final int target = tickState + tickSchedule.tick();
|
2024-03-31 05:51:08 +02:00
|
|
|
var targetTaskQueue = switch (task.executionType()) {
|
|
|
|
case TICK_START, SYNC, ASYNC -> tickStartTaskQueue;
|
|
|
|
case TICK_END -> tickEndTaskQueue;
|
|
|
|
};
|
|
|
|
targetTaskQueue.computeIfAbsent(target, i -> new ArrayList<>()).add(task);
|
2021-12-16 00:15:55 +01:00
|
|
|
}
|
|
|
|
} else if (schedule instanceof TaskScheduleImpl.FutureSchedule futureSchedule) {
|
|
|
|
futureSchedule.future().thenRun(() -> safeExecute(task));
|
|
|
|
} else if (schedule instanceof TaskScheduleImpl.Park) {
|
2022-03-10 16:57:36 +01:00
|
|
|
task.parked = true;
|
2021-12-16 00:15:55 +01:00
|
|
|
} else if (schedule instanceof TaskScheduleImpl.Stop) {
|
2022-03-10 16:57:36 +01:00
|
|
|
task.cancel();
|
2021-12-16 00:15:55 +01:00
|
|
|
} else if (schedule instanceof TaskScheduleImpl.Immediate) {
|
2024-03-31 05:51:08 +02:00
|
|
|
if (task.executionType() == ExecutionType.TICK_END) {
|
|
|
|
tickEndTasksToExecute.relaxedOffer(task);
|
|
|
|
}
|
|
|
|
else tasksToExecute.relaxedOffer(task);
|
2021-12-16 00:15:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|