diff --git a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java new file mode 100644 index 00000000000..38162116e6e --- /dev/null +++ b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java @@ -0,0 +1,32 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.scheduler; + +import org.spongepowered.api.scheduler.ScheduledTask; + +import java.util.concurrent.Delayed; + +public interface AbstractScheduledTask extends ScheduledTask, Delayed { +} diff --git a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java new file mode 100644 index 00000000000..739f44d0f9e --- /dev/null +++ b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java @@ -0,0 +1,50 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.scheduler; + +import org.spongepowered.api.scheduler.Scheduler; +import org.spongepowered.api.scheduler.Task; + +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + +interface AbstractScheduler extends Scheduler, AutoCloseable { + @Override + AbstractScheduledTask submit(Task task); + + @Override + AbstractScheduledTask submit(Task task, String name); + + + // basic operations + Delayed scheduleAtTick(Runnable command, long ticks); + + Delayed scheduleAtTime(Runnable command, long nanos); + + default Delayed + scheduleAtTime(Runnable command, long time, TimeUnit unit) { + return this.scheduleAtTime(command, unit.convert(time, TimeUnit.NANOSECONDS)); + } +} diff --git a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java index 6ad2165db0d..4d54639d072 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java @@ -26,193 +26,70 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.apache.logging.log4j.Level; -import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.common.SpongeCommon; import org.spongepowered.common.util.PrettyPrinter; -import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -public final class AsyncScheduler extends SpongeScheduler { - - // Locking mechanism - private final Lock lock = new ReentrantLock(); - private final Condition condition = this.lock.newCondition(); - private final AtomicBoolean stateChanged = new AtomicBoolean(false); - // The dynamic thread pooling executor of asynchronous tasks. - private final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder() - .setNameFormat("Sponge-AsyncScheduler-%d") - .build()); - private volatile boolean running = true; - - // Adjustable timeout for pending Tasks - private long minimumTimeout = Long.MAX_VALUE; - private long lastProcessingTimestamp; +public class AsyncScheduler extends SpongeScheduler { + private static final int NCPU = Runtime.getRuntime().availableProcessors(); + private final ScheduledExecutorService scheduler = + Executors.newScheduledThreadPool(NCPU, new ThreadFactoryBuilder() + .setNameFormat("Sponge-AsyncScheduler-%d") + .build()); public AsyncScheduler() { super("A"); - - final Thread thread = new Thread(AsyncScheduler.this::mainLoop); - thread.setName("Sponge Async Scheduler Thread"); - thread.setDaemon(true); - thread.start(); - } - - private void mainLoop() { - this.lastProcessingTimestamp = System.nanoTime(); - while (this.running) { - this.recalibrateMinimumTimeout(); - this.runTick(); - } - } - - private void recalibrateMinimumTimeout() { - this.lock.lock(); - try { - final Set tasks = this.tasks(); - this.minimumTimeout = Long.MAX_VALUE; - final long now = System.nanoTime(); - for (final ScheduledTask tmpTask : tasks) { - final SpongeScheduledTask task = (SpongeScheduledTask) tmpTask; - if (task.state() == SpongeScheduledTask.ScheduledTaskState.EXECUTING) { - // bail out for this task. We'll signal when we complete the task. - continue; - } - // Recalibrate the wait delay for processing tasks before new - // tasks cause the scheduler to process pending tasks. - if (task.task.delay == 0 && task.task.interval == 0) { - this.minimumTimeout = 0; - } - // The time since the task last executed or was added to the map - final long timeSinceLast = now - task.timestamp(); - - if (task.task.delay > 0 && task.state() == SpongeScheduledTask.ScheduledTaskState.WAITING) { - // There is an delay and the task hasn't run yet - this.minimumTimeout = Math.min(task.task.delay - timeSinceLast, this.minimumTimeout); - } - if (task.task.interval > 0 && task.state().isActive) { - // The task repeats and has run after the initial delay - this.minimumTimeout = Math.min(task.task.interval - timeSinceLast, this.minimumTimeout); - } - if (this.minimumTimeout <= 0) { - break; - } - } - if (!tasks.isEmpty()) { - final long latency = System.nanoTime() - this.lastProcessingTimestamp; - this.minimumTimeout -= (latency <= 0) ? 0 : latency; - this.minimumTimeout = (this.minimumTimeout < 0) ? 0 : this.minimumTimeout; - } - } finally { - this.lock.unlock(); - } } @Override - protected void addTask(final SpongeScheduledTask task) { - this.lock.lock(); - try { - super.addTask(task); - this.condition.signalAll(); - } finally { - this.lock.unlock(); - } - } - - @Override - protected void preTick() { - this.lock.lock(); - try { - // If we have something that has indicated it needs to change, - // don't await, just continue. - if (!this.stateChanged.get()) { - this.condition.await(this.minimumTimeout, TimeUnit.NANOSECONDS); - } - // We're processing now. Set to false. - this.stateChanged.set(false); - } catch (final InterruptedException ignored) { - // The taskMap has been modified; there is work to do. - // Continue on without handling the Exception. - } catch (final IllegalMonitorStateException e) { - SpongeCommon.logger().error("The scheduler internal state machine suffered a catastrophic error", e); - } + public ScheduledFuture scheduleAtTick(Runnable command, long ticksAsNanos) { + return this.scheduler.schedule(command, ticksAsNanos, TimeUnit.NANOSECONDS); } @Override - protected void postTick() { - this.lastProcessingTimestamp = System.nanoTime(); - } - - @Override - protected void finallyPostTick() { - this.lock.unlock(); - } - - @Override - protected void onTaskCompletion(final SpongeScheduledTask task) { - if (task.state() == SpongeScheduledTask.ScheduledTaskState.RUNNING) { - this.lock.lock(); - try { - this.stateChanged.set(true); - this.condition.signalAll(); - } finally { - this.lock.unlock(); - } - } - } - - @Override - protected void executeRunnable(final Runnable runnable) { - this.executor.submit(runnable); + public ScheduledFuture scheduleAtTime(Runnable command, long nanos) { + return this.scheduler.schedule(command, nanos, TimeUnit.NANOSECONDS); } public CompletableFuture submit(final Callable callable) { - return this.asyncFailableFuture(callable, this.executor); - } - - private CompletableFuture asyncFailableFuture(Callable call, Executor exec) { final CompletableFuture ret = new CompletableFuture<>(); - exec.execute(() -> { + super.execute(() -> { try { - ret.complete(call.call()); - } catch (Throwable e) { + ret.complete(callable.call()); + } catch (final Throwable e) { ret.completeExceptionally(e); } }); return ret; } - + @Override public void close() { - this.running = false; - // Cancel all tasks - final Set tasks = this.tasks(); - tasks.forEach(ScheduledTask::cancel); - - // Shut down the executor - this.executor.shutdown(); - + final ScheduledExecutorService scheduler = this.scheduler; + if (scheduler.isTerminated()) { + return; + } + scheduler.shutdown(); try { - if (!this.executor.awaitTermination(5, TimeUnit.SECONDS)) { + if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) { new PrettyPrinter() .add("Sponge async scheduler failed to shut down in 5 seconds! Tasks that may have been active:") - .addWithIndices(tasks) + .addWithIndices(super.activeTasks()) .add() .add("We will now attempt immediate shutdown.") .log(SpongeCommon.logger(), Level.WARN); - this.executor.shutdownNow(); + scheduler.shutdownNow(); } } catch (final InterruptedException e) { SpongeCommon.logger().error("The async scheduler was interrupted while awaiting shutdown!"); + + Thread.currentThread().interrupt(); } } + } diff --git a/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java b/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java new file mode 100644 index 00000000000..36ab7938a77 --- /dev/null +++ b/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java @@ -0,0 +1,92 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.scheduler; + +import org.jetbrains.annotations.NotNull; +import org.spongepowered.api.scheduler.Scheduler; +import org.spongepowered.api.scheduler.Task; + +import java.util.UUID; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public class ScheduledTaskEnvelope implements AbstractScheduledTask { + private final AbstractScheduler scheduler; + private final TaskProcedure task; + private final String name; + private final UUID uuid; + private final AtomicBoolean cancelled = new AtomicBoolean(); + volatile Delayed delayed; // init ? + + ScheduledTaskEnvelope(AbstractScheduler scheduler, + TaskProcedure task, + String name, UUID uuid) { + this.scheduler = scheduler; + this.task = task; + this.name = name; + this.uuid = uuid; + } + @Override + public boolean cancel() { + return !this.cancelled.getOpaque() && + this.cancelled.compareAndSet(false, true); + } + + @Override + public boolean isCancelled() { + return this.cancelled.get(); + } + + @Override + public Scheduler scheduler() { + return this.scheduler; + } + + @Override + public Task task() { + return this.task; + } + + @Override + public UUID uniqueId() { + return this.uuid; + } + + @Override + public String name() { + return this.name; + } + + @Override + public long getDelay(@NotNull TimeUnit unit) { + return this.delayed.getDelay(unit); + } + + @Override + public int compareTo(@NotNull Delayed other) { + return this.delayed.compareTo(other); + } +} diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java deleted file mode 100644 index 5a4d1544fe9..00000000000 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.common.scheduler; - -import com.google.common.base.MoreObjects; -import org.spongepowered.api.scheduler.ScheduledTask; -import org.spongepowered.api.scheduler.Scheduler; -import org.spongepowered.api.scheduler.Task; - -import java.util.UUID; - -/** - * An internal representation of a {@link Task} created by a plugin. - */ -public final class SpongeScheduledTask implements ScheduledTask { - - private final SpongeScheduler scheduler; - final SpongeTask task; - private final UUID uniqueId; - private final String name; - - private long timestamp; - private ScheduledTaskState state; - private boolean isCancelled = false; - - SpongeScheduledTask(final SpongeScheduler scheduler, final SpongeTask task, final String name) { - this.scheduler = scheduler; - this.task = task; - this.name = name; - this.uniqueId = UUID.randomUUID(); - // All tasks begin waiting. - this.state = ScheduledTaskState.WAITING; - } - - @Override - public Scheduler scheduler() { - return this.scheduler; - } - - @Override - public Task task() { - return this.task; - } - - @Override - public UUID uniqueId() { - return this.uniqueId; - } - - @Override - public String name() { - return this.name; - } - - @Override - public boolean cancel() { - final boolean success = this.state() == ScheduledTaskState.RUNNING - || this.state() == ScheduledTaskState.EXECUTING; - this.state = ScheduledTaskState.CANCELED; - this.isCancelled = true; - return success; - } - - @Override - public boolean isCancelled() { - return this.isCancelled; - } - - long timestamp() { - return this.timestamp; - } - - void setTimestamp(final long timestamp) { - this.timestamp = timestamp; - } - - /** - * Returns a timestamp after which the next execution will take place. - * Should only be compared to - * {@link SpongeScheduler#timestamp(boolean)}. - * - * @return The next execution timestamp - */ - long nextExecutionTimestamp() { - if (this.state.isActive) { - return this.timestamp + this.task.interval; - } - return this.timestamp + this.task.delay; - } - - ScheduledTaskState state() { - return this.state; - } - - void setState(ScheduledTaskState state) { - this.state = state; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("task", this.task) - .toString(); - } - - // Internal Task state. Not for user-service use. - public enum ScheduledTaskState { - /** - * Never ran before, waiting for the delay to pass. - */ - WAITING(false), - /** - * In the process of switching to the execution state. - */ - SWITCHING(true), - /** - * Is being executed. - */ - EXECUTING(true), - /** - * Has ran, and will continue to unless removed from the task map. - */ - RUNNING(true), - /** - * Task cancelled, scheduled to be removed from the task map. - */ - CANCELED(false); - - public final boolean isActive; - - ScheduledTaskState(boolean active) { - this.isActive = active; - } - } -} diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java index 1c0b8608246..1ccae585ee8 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java @@ -24,295 +24,151 @@ */ package org.spongepowered.common.scheduler; -import com.google.common.collect.Sets; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.scheduler.ScheduledTask; -import org.spongepowered.api.scheduler.Scheduler; import org.spongepowered.api.scheduler.Task; +import org.spongepowered.api.scheduler.TaskExecutorService; import org.spongepowered.common.SpongeCommon; -import org.spongepowered.common.event.tracking.PhaseContext; -import org.spongepowered.common.event.tracking.PhaseTracker; -import org.spongepowered.common.event.tracking.phase.plugin.PluginPhase; import org.spongepowered.common.launch.Launch; import org.spongepowered.plugin.PluginContainer; -import java.util.Iterator; -import java.util.Map; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Matcher; +import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; +import java.util.stream.Collectors; -public abstract class SpongeScheduler implements Scheduler { - - private static final AtomicInteger TASK_CREATED_COUNTER = new AtomicInteger(); - - private static final int TICK_DURATION_MS = 50; - static final long TICK_DURATION_NS = TimeUnit.NANOSECONDS.convert(SpongeScheduler.TICK_DURATION_MS, TimeUnit.MILLISECONDS); +public abstract class SpongeScheduler implements AbstractScheduler { + private static final AtomicLong TASK_CREATED_COUNTER = new AtomicLong(); + static final int TICK_DURATION_MS = 50; + static final long TICK_DURATION_NS = TimeUnit.NANOSECONDS + .convert(TICK_DURATION_MS, TimeUnit.MILLISECONDS); + private final AtomicLong sequenceNumber = new AtomicLong(); private final String tag; + private final ConcurrentMap cachedTasks + = new ConcurrentHashMap<>(); - // The simple queue of all pending (and running) ScheduledTasks - private final Map tasks = new ConcurrentHashMap<>(); - private long sequenceNumber = 0L; - - SpongeScheduler(final String tag) { + protected SpongeScheduler(final String tag) { this.tag = tag; } - /** - * Gets the timestamp to update the timestamp of a task. This method is task - * sensitive to support different timestamp types i.e. real time and ticks. - * - *

Subtracting the result of this method from a previously obtained - * result should become a representation of the time that has passed - * between those calls.

- * - * @param tickBased The task - * @return Timestamp for the task - */ - protected long timestamp(final boolean tickBased) { - return System.nanoTime(); - } - - /** - * Adds the task to the task map, will attempt to process the task on the - * next call to {@link #runTick}. - * - * @param task The task to add - */ - protected void addTask(final SpongeScheduledTask task) { - task.setTimestamp(this.timestamp(task.task.tickBasedDelay)); - this.tasks.put(task.uniqueId(), task); - } - - /** - * Removes the task from the task map. - * - * @param task The task to remove - */ - private void removeTask(final SpongeScheduledTask task) { - this.tasks.remove(task.uniqueId()); - } - @Override public Optional findTask(final UUID id) { Objects.requireNonNull(id, "id"); - return Optional.ofNullable(this.tasks.get(id)); + return Optional.ofNullable(this.cachedTasks.get(id)); } @Override public Set findTasks(final String pattern) { - final Pattern searchPattern = Pattern.compile(Objects.requireNonNull(pattern, "pattern")); - final Set matchingTasks = this.tasks(); - - final Iterator it = matchingTasks.iterator(); - while (it.hasNext()) { - final Matcher matcher = searchPattern.matcher(it.next().name()); - if (!matcher.matches()) { - it.remove(); - } - } + final Pattern searchPattern = Pattern.compile( + Objects.requireNonNull(pattern, "pattern")); - return matchingTasks; + return this.cachedTasks + .values() + .stream() + .filter(x -> searchPattern.matcher(x.name()).matches()) + .collect(Collectors.toSet()); } @Override public Set tasks() { - return Sets.newHashSet(this.tasks.values()); + return new HashSet<>(this.cachedTasks.values()); } @Override public Set tasks(final PluginContainer plugin) { - final String testOwnerId = Objects.requireNonNull(plugin, "plugin").metadata().id(); - - final Set allTasks = this.tasks(); - final Iterator it = allTasks.iterator(); - - while (it.hasNext()) { - final String taskOwnerId = it.next().task().plugin().metadata().id(); - if (!testOwnerId.equals(taskOwnerId)) { - it.remove(); - } - } - - return allTasks; + final String testOwnerId = Objects + .requireNonNull(plugin, "plugin") + .metadata().id(); + return this.cachedTasks + .values() + .stream() + .filter(x -> { + final String taskOwnerId = x.task().plugin().metadata().id(); + return testOwnerId.equals(taskOwnerId); + }).collect(Collectors.toSet()); } - @Override - public SpongeTaskExecutorService executor(final PluginContainer plugin) { + public TaskExecutorService executor(PluginContainer plugin) { Objects.requireNonNull(plugin, "plugin"); return new SpongeTaskExecutorService(() -> Task.builder().plugin(plugin), this); } @Override - public SpongeScheduledTask submit(final Task task) { - Objects.requireNonNull(task, "task"); - - final String name = task.plugin().metadata().id() + "-" + SpongeScheduler.TASK_CREATED_COUNTER.incrementAndGet(); + public AbstractScheduledTask submit(Task task) { + final String name = + task.plugin().metadata().id() + + "-" + + TASK_CREATED_COUNTER.incrementAndGet(); return this.submit(task, name); } - @Override - public SpongeScheduledTask submit(Task task, String name) { - Objects.requireNonNull(task, "task"); - if (Objects.requireNonNull(name, "name").isEmpty()) { - throw new IllegalArgumentException("Task name cannot empty!"); - } - - final SpongeScheduledTask scheduledTask = new SpongeScheduledTask(this, (SpongeTask) task, - name + "-" + this.tag + "-#" + this.sequenceNumber++); - this.addTask(scheduledTask); - return scheduledTask; - } - - /** - * Process all tasks in the map. - */ - final void runTick() { - this.preTick(); - try { - this.tasks.values().forEach(this::processTask); - this.postTick(); - } finally { - this.finallyPostTick(); - } - } - - /** - * Fired when the scheduler begins to tick, before any tasks are processed. - */ - protected void preTick() { - } - - /** - * Fired when the scheduler has processed all tasks. - */ - protected void postTick() { - } - - /** - * Fired after tasks have attempted to be processed, in a finally block to - * guarantee execution regardless of any error when processing a task. - */ - protected void finallyPostTick() { - } - - /** - * Processes the task. - * - * @param task The task to process - */ - private void processTask(final SpongeScheduledTask task) { - // If the task is now slated to be cancelled, we just remove it as if it - // no longer exists. - if (task.state() == SpongeScheduledTask.ScheduledTaskState.CANCELED) { - this.removeTask(task); + public AbstractScheduledTask submit(Task task, String name) { + final SpongeTask st = (SpongeTask) task; + + final long number = this.sequenceNumber.getAndIncrement(); + + final UUID uuid = UUID.randomUUID(); + + final ScheduledTaskEnvelope sched = new ScheduledTaskEnvelope( + this, st, + "%s-%s-#%s".formatted(name, this.tag, number), uuid); + this.cachedTasks.put(sched.uniqueId(), sched); + this.exec(sched, st, true); + return sched; + } + private void exec(final ScheduledTaskEnvelope sched, + final TaskProcedure task, + final boolean first) { + final Time time = first ? task.delayTime() : task.intervalTime(); + final long nanos = time.timeNanos(); + if ((!first && nanos == 0) || sched.isCancelled()) { + this.cachedTasks.remove(sched.uniqueId()); return; } - // If the task is already being processed, we wait for the previous - // occurrence to terminate. - if (task.state() == SpongeScheduledTask.ScheduledTaskState.EXECUTING) { - return; - } - final long threshold; - final boolean tickBased; - // Figure out if we start a delayed Task after threshold ticks or, start - // it after the interval (interval) of the repeating task parameter. - if (task.state() == SpongeScheduledTask.ScheduledTaskState.WAITING) { - threshold = task.task.delay; - tickBased = task.task.tickBasedDelay; - } else if (task.state() == SpongeScheduledTask.ScheduledTaskState.RUNNING) { - threshold = task.task.interval; - tickBased = task.task.tickBasedInterval; - } else { - threshold = Long.MAX_VALUE; - tickBased = false; - } - // This moment is 'now' - final long now = this.timestamp(tickBased); - // So, if the current time minus the timestamp of the task is greater - // than the delay to wait before starting the task, then start the task. - // Repeating tasks get a reset-timestamp each time they are set RUNNING - // If the task has a interval of 0 (zero) this task will not repeat, and - // is removed after we start it. - if (threshold <= (now - task.timestamp())) { - task.setState(SpongeScheduledTask.ScheduledTaskState.SWITCHING); - // It is always interval here because that's the only thing that matters - // at this point. - task.setTimestamp(this.timestamp(task.task.tickBasedInterval)); - this.startTask(task); - // If task is one time shot, remove it from the map. - if (task.task.interval == 0L) { - this.removeTask(task); - } - } - } - - /** - * Begin the execution of a task. Exceptions are caught and logged. - * - * @param task The task to start - */ - private void startTask(final SpongeScheduledTask task) { - this.executeRunnable(() -> { - task.setState(SpongeScheduledTask.ScheduledTaskState.EXECUTING); - try (final @Nullable PhaseContext<@NonNull ?> context = this.createContext(task, task.task().plugin())) { - if (context != null) { - context.buildAndSwitch(); - } - try { - task.task.executor().accept(task); - } catch (final Throwable t) { - SpongeCommon.logger().error("The Scheduler tried to run the task '{}' owned by '{}' but an error occurred.", - task.name(), task.task().plugin().metadata().id(), t); + final Runnable command = () -> { + try { + if (!sched.isCancelled()) { + task.execute(sched); } + } catch (final Exception ex) { + SpongeCommon.logger().error( + "The Scheduler tried to run the task '{}' owned by '{}' but an error occurred.", + sched.name(), task.plugin().metadata().id(), + ex); } finally { - if (!task.isCancelled()) { - task.setState(SpongeScheduledTask.ScheduledTaskState.RUNNING); - } - this.onTaskCompletion(task); + this.exec(sched, task, false); } - }); + }; + sched.delayed = time.tickBased() + ? scheduleAtTick(command, nanos) + : scheduleAtTime(command, nanos); } - - protected @Nullable PhaseContext createContext(final SpongeScheduledTask task, final PluginContainer plugin) { - return PluginPhase.State.SCHEDULED_TASK.createPhaseContext(PhaseTracker.getInstance()) - .source(task) - .container(plugin); - } - - /** - * Run when a task has completed and is switching into - * the {@link SpongeScheduledTask.ScheduledTaskState#RUNNING} state - */ - protected void onTaskCompletion(final SpongeScheduledTask task) { - // no-op for sync methods. + protected Collection activeTasks() { + return Collections.unmodifiableCollection(this.cachedTasks.values()); } - - protected void executeRunnable(final Runnable runnable) { - runnable.run(); - } - public Future execute(final Callable callable) { final FutureTask runnable = new FutureTask<>(callable); - this.submit(new SpongeTask.BuilderImpl().execute(runnable).plugin(Launch.instance().commonPlugin()).build()); + this.submit(new SpongeTask.BuilderImpl() + .execute(runnable) + .plugin(Launch.instance().commonPlugin()) + .build()); return runnable; } public Future execute(final Runnable runnable) { - return this.execute(() -> { - runnable.run(); - return null; - }); + return this.execute(Executors.callable(runnable)); } } diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java index 39e090bfaa1..e18180d18ca 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java @@ -24,65 +24,77 @@ */ package org.spongepowered.common.scheduler; -import com.google.common.base.MoreObjects; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.api.scheduler.Task; import org.spongepowered.api.util.Ticks; +import org.spongepowered.common.event.tracking.PhaseContext; +import org.spongepowered.common.event.tracking.PhaseTracker; +import org.spongepowered.common.event.tracking.phase.plugin.PluginPhase; import org.spongepowered.plugin.PluginContainer; import java.time.Duration; import java.time.temporal.TemporalUnit; import java.util.Objects; +import java.util.StringJoiner; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -public final class SpongeTask implements Task { +public final class SpongeTask implements TaskProcedure { private final PluginContainer plugin; private final Consumer executor; + private final Time delay, interval; - final long delay; // nanos - final long interval; // nanos - final boolean tickBasedDelay; - final boolean tickBasedInterval; - - SpongeTask(final PluginContainer plugin, final Consumer executor, final long delay, - final long interval, final boolean tickBasedDelay, final boolean tickBasedInterval) { + SpongeTask(final PluginContainer plugin, + final Consumer executor, + final Time delay, final Time interval) { this.plugin = plugin; this.executor = executor; this.delay = delay; this.interval = interval; - this.tickBasedDelay = tickBasedDelay; - this.tickBasedInterval = tickBasedInterval; } @Override - public PluginContainer plugin() { - return this.plugin; + public void execute(ScheduledTask scheduledTask) throws ExecutionException { + try (final PhaseContext context = PluginPhase.State.SCHEDULED_TASK + .createPhaseContext(PhaseTracker.getInstance()) + .source(scheduledTask) + .container(this.plugin())) { + context.buildAndSwitch(); + try { + this.executor.accept(scheduledTask); + } catch (final Throwable t) { + throw new ExecutionException(t); + } + } } @Override - public Duration delay() { - return Duration.ofNanos(this.delay); + public PluginContainer plugin() { + return this.plugin; } @Override - public Duration interval() { - return Duration.ofNanos(this.interval); + public Time intervalTime() { + return this.interval; } - public Consumer executor() { - return this.executor; + @Override + public Time delayTime() { + return this.delay; } @Override public String toString() { - return MoreObjects.toStringHelper(this) - .add("plugin", this.plugin.metadata().id()) - .add("delay", this.delay) - .add("interval", this.interval) + return new StringJoiner(", ", + SpongeTask.class.getSimpleName() + "[", "]") + .add("plugin=" + this.plugin.metadata().id()) + .add("delay=" + this.delay().toNanos()) + .add("interval=" + this.interval().toNanos()) .toString(); + } public static final class BuilderImpl implements Task.Builder { @@ -90,10 +102,8 @@ public static final class BuilderImpl implements Task.Builder { private @Nullable Consumer executor; private @Nullable PluginContainer plugin; - private long delay; - private long interval; - private boolean tickBasedDelay; - private boolean tickBasedInterval; + private Time delay = Time.ZERO; + private Time interval = Time.ZERO; @Override public Task.Builder execute(final Consumer executor) { @@ -103,68 +113,67 @@ public Task.Builder execute(final Consumer executor) { @Override public Task.Builder delay(final long delay, final TemporalUnit unit) { - Objects.requireNonNull(unit); + Objects.requireNonNull(unit, "unit"); if (delay < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.delay = Objects.requireNonNull(unit, "unit").getDuration().toNanos() * delay; - this.tickBasedDelay = false; + this.delay = new Time.RealTime(unit.getDuration().toNanos() * delay); return this; } @Override public Task.Builder delay(final long delay, final TimeUnit unit) { - Objects.requireNonNull(unit); + Objects.requireNonNull(unit, "unit"); if (delay < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.delay = Objects.requireNonNull(unit, "unit").toNanos(delay); - this.tickBasedDelay = false; + this.delay = new Time.RealTime(unit.toNanos(delay)); return this; } @Override public Task.Builder delay(final Ticks delay) { - Objects.requireNonNull(delay); + Objects.requireNonNull(delay, "delay"); if (delay.ticks() < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.delay = delay.ticks() * SpongeScheduler.TICK_DURATION_NS; - this.tickBasedDelay = true; + this.delay = new Time.TickTime( + delay.ticks() * SpongeScheduler.TICK_DURATION_NS); return this; } @Override public Task.Builder delay(final Duration delay) { - this.delay = Objects.requireNonNull(delay, "delay").toNanos(); - this.tickBasedDelay = false; + Objects.requireNonNull(delay, "delay"); + this.delay = new Time.RealTime(delay.toNanos()); return this; } @Override public Task.Builder interval(final Duration interval) { - this.interval = Objects.requireNonNull(interval, "interval").toNanos(); - this.tickBasedInterval = false; + Objects.requireNonNull(interval, "interval"); + this.interval = new Time.RealTime(interval.toNanos()); return this; } @Override public Task.Builder interval(final long delay, final TemporalUnit unit) { + Objects.requireNonNull(unit, "unit"); if (delay < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.interval = Objects.requireNonNull(unit, "unit").getDuration().toNanos() * delay; - this.tickBasedInterval = false; + this.interval = new Time.RealTime( + unit.getDuration().toNanos() * delay); return this; } @Override public Task.Builder interval(final long interval, final TimeUnit unit) { + Objects.requireNonNull(unit, "unit"); if (interval < 0) { throw new IllegalArgumentException("Interval must be equal to or greater than zero!"); } - this.interval = Objects.requireNonNull(unit, "unit").toNanos(interval); - this.tickBasedInterval = false; + this.interval = new Time.RealTime(unit.toNanos(interval)); return this; } @@ -174,8 +183,8 @@ public Task.Builder interval(final Ticks interval) { if (interval.ticks() < 0) { throw new IllegalArgumentException("Interval must be equal to or greater than zero!"); } - this.interval = interval.ticks() * SpongeScheduler.TICK_DURATION_NS; - this.tickBasedInterval = true; + this.interval = new Time.TickTime( + interval.ticks() * SpongeScheduler.TICK_DURATION_NS); return this; } @@ -188,13 +197,12 @@ public Task.Builder plugin(final PluginContainer plugin) { @Override public Task.Builder from(final Task value) { - final SpongeTask task = (SpongeTask) Objects.requireNonNull(value, "value"); + final SpongeTask task = (SpongeTask) + Objects.requireNonNull(value, "value"); this.executor = task.executor; this.plugin = task.plugin(); this.interval = task.interval; this.delay = task.delay; - this.tickBasedDelay = task.tickBasedDelay; - this.tickBasedInterval = task.tickBasedInterval; return this; } @@ -202,10 +210,8 @@ public Task.Builder from(final Task value) { public Task.Builder reset() { this.executor = null; this.plugin = null; - this.interval = 0; - this.delay = 0; - this.tickBasedDelay = false; - this.tickBasedInterval = false; + this.interval = Time.ZERO; + this.delay = Time.ZERO; return this; } @@ -214,7 +220,7 @@ public Task build() { Objects.requireNonNull(this.executor, "executor"); Objects.requireNonNull(this.plugin, "plugin"); - return new SpongeTask(this.plugin, this.executor, this.delay, this.interval, this.tickBasedDelay, this.tickBasedInterval); + return new SpongeTask(this.plugin, this.executor, this.delay, this.interval); } } } diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java b/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java index 434e8f616c1..a3d40f24c79 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java @@ -24,7 +24,6 @@ */ package org.spongepowered.common.scheduler; -import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableList; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.scheduler.ScheduledTask; @@ -33,7 +32,6 @@ import org.spongepowered.api.scheduler.TaskExecutorService; import org.spongepowered.api.scheduler.TaskFuture; -import java.time.Duration; import java.time.temporal.TemporalUnit; import java.util.List; import java.util.concurrent.AbstractExecutorService; @@ -102,7 +100,7 @@ public TaskFuture submit(final Runnable command, final @Nullable T result final FutureTask runnable = new FutureTask<>(command, result); final Task task = this.createTask(runnable) .build(); - return new SpongeScheduledFuture<>(runnable, this.submitTask(task), this.scheduler); + return new SpongeScheduledFuture<>(runnable, this.submitTask(task)); } @Override @@ -110,7 +108,7 @@ public TaskFuture submit(final Callable command) { final FutureTask runnable = new FutureTask<>(command); final Task task = this.createTask(runnable) .build(); - return new SpongeScheduledFuture<>(runnable, this.submitTask(task), this.scheduler); + return new SpongeScheduledFuture<>(runnable, this.submitTask(task)); } @Override @@ -119,7 +117,7 @@ public ScheduledTaskFuture schedule(final Runnable command, final long delay, final Task task = this.createTask(runnable) .delay(delay, unit) .build(); - return new SpongeScheduledFuture<>(runnable, this.submitTask(task), this.scheduler); + return new SpongeScheduledFuture<>(runnable, this.submitTask(task)); } @Override @@ -128,7 +126,7 @@ public ScheduledTaskFuture schedule(final Runnable command, final long delay, final Task task = this.createTask(runnable) .delay(delay, unit) .build(); - return new SpongeScheduledFuture<>(runnable, this.submitTask(task), this.scheduler); + return new SpongeScheduledFuture<>(runnable, this.submitTask(task)); } @Override @@ -137,7 +135,7 @@ public ScheduledTaskFuture schedule(final Callable callable, final lon final Task task = this.createTask(runnable) .delay(delay, unit) .build(); - return new SpongeScheduledFuture<>(runnable, this.submitTask(task), this.scheduler); + return new SpongeScheduledFuture<>(runnable, this.submitTask(task)); } @Override @@ -146,7 +144,7 @@ public ScheduledTaskFuture schedule(final Callable callable, final lon final Task task = this.createTask(runnable) .delay(delay, unit) .build(); - return new SpongeScheduledFuture<>(runnable, this.submitTask(task), this.scheduler); + return new SpongeScheduledFuture<>(runnable, this.submitTask(task)); } @Override @@ -156,10 +154,10 @@ public ScheduledTaskFuture scheduleAtFixedRate(final Runnable command, final .delay(initialDelay, unit) .interval(period, unit) .build(); - final SpongeScheduledTask scheduledTask = this.submitTask(task); + final AbstractScheduledTask scheduledTask = this.submitTask(task); // A repeatable task needs to be able to cancel itself runnable.setTask(scheduledTask); - return new SpongeScheduledFuture<>(runnable, scheduledTask, this.scheduler); + return new SpongeScheduledFuture<>(runnable, scheduledTask); } @Override @@ -169,10 +167,10 @@ public ScheduledTaskFuture scheduleAtFixedRate(final Runnable command, final .delay(initialDelay, unit) .interval(period, unit) .build(); - final SpongeScheduledTask scheduledTask = this.submitTask(task); + final AbstractScheduledTask scheduledTask = this.submitTask(task); // A repeatable task needs to be able to cancel itself runnable.setTask(scheduledTask); - return new SpongeScheduledFuture<>(runnable, scheduledTask, this.scheduler); + return new SpongeScheduledFuture<>(runnable, scheduledTask); } @Override @@ -191,20 +189,19 @@ private Task.Builder createTask(final Runnable command) { return this.taskBuilderProvider.get().execute(command); } - private SpongeScheduledTask submitTask(final Task task) { + private AbstractScheduledTask submitTask(final Task task) { return this.scheduler.submit(task); } private static class SpongeScheduledFuture implements org.spongepowered.api.scheduler.ScheduledTaskFuture { private final FutureTask runnable; - private final SpongeScheduledTask task; - private final SpongeScheduler scheduler; + private final AbstractScheduledTask task; - SpongeScheduledFuture(final FutureTask runnable, final SpongeScheduledTask task, final SpongeScheduler scheduler) { + SpongeScheduledFuture(final FutureTask runnable, + final AbstractScheduledTask task) { this.runnable = runnable; this.task = task; - this.scheduler = scheduler; } @Override @@ -214,38 +211,17 @@ public ScheduledTask task() { @Override public boolean isPeriodic() { - final Duration interval = this.task.task.interval(); - return interval.toMillis() > 0; + return !this.task.task().interval().isZero(); } @Override public long getDelay(final TimeUnit unit) { - // Since these tasks are scheduled through - // SchedulerExecutionService, they are - // always nanotime-based, not tick-based. - return unit.convert(this.task.nextExecutionTimestamp() - this.scheduler.timestamp(false), TimeUnit.NANOSECONDS); + return this.task.getDelay(unit); } - @SuppressWarnings("rawtypes") @Override public int compareTo(final Delayed other) { - // Since delay may return different values for each call, - // this check is required to correctly implement Comparable - if (other == this) { - return 0; - } - - // If we are considering other sponge tasks, we can order by - // their internal tasks - if (other instanceof SpongeScheduledFuture) { - final SpongeScheduledTask otherTask = ((SpongeScheduledFuture) other).task; - return ComparisonChain.start() - .compare(this.task.nextExecutionTimestamp(), otherTask.nextExecutionTimestamp()) - .compare(this.task.uniqueId(), otherTask.uniqueId()) - .result(); - } - - return Long.compare(this.getDelay(TimeUnit.NANOSECONDS), other.getDelay(TimeUnit.NANOSECONDS)); + return this.task.compareTo(other); } @Override @@ -264,7 +240,7 @@ public boolean isCancelled() { // It might be externally cancelled, the runnable would never // run in that case return this.runnable.isCancelled() || - (this.task.state() == SpongeScheduledTask.ScheduledTaskState.CANCELED && !this.runnable.isDone()); + (this.task.isCancelled() && !this.runnable.isDone()); } @Override diff --git a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java index 6d42c6c7ad9..e1b2f99951b 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java @@ -24,30 +24,92 @@ */ package org.spongepowered.common.scheduler; -public abstract class SyncScheduler extends SpongeScheduler { +import org.jetbrains.annotations.NotNull; - // The number of ticks elapsed since this scheduler began. - private long counter = 0L; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; - SyncScheduler(final String tag) { +public class SyncScheduler extends SpongeScheduler { + private final BlockingQueue + ticksQueue = new DelayQueue<>(); + private final BlockingQueue + timedQueue = new DelayQueue<>(); + private volatile long timestamp; + + private final Time ticked = new Time() { + @Override + public boolean tickBased() { + return true; + } + + @Override + public long timeNanos() { + return SyncScheduler.this.timestamp; + } + }; + + protected SyncScheduler(final String tag) { super(tag); } - /** - * The hook to update the Ticks known by the SyncScheduler. - */ + @Override + public Delayed scheduleAtTick(Runnable command, long ticksAsNanos) { + final Time clock = this.ticked; + final SchedFutureTask f = new SchedFutureTask( + command, clock, + ticksAsNanos + clock.timeNanos()); + this.ticksQueue.add(f); + return f; + } + + @Override + public Delayed scheduleAtTime(Runnable command, long nanos) { + final Time clock = Time.REAL_TIME; + final SchedFutureTask f = new SchedFutureTask( + command, clock, + nanos + clock.timeNanos()); + this.timedQueue.add(f); + return f; + } + public void tick() { - this.counter++; - this.runTick(); + this.timestamp += TICK_DURATION_NS; + for (Runnable task; + (task = this.ticksQueue.poll()) != null; + task.run()); + for (Runnable task; + (task = this.timedQueue.poll()) != null; + task.run()); } @Override - protected long timestamp(final boolean tickBased) { - // The task is based on minecraft ticks, so we generate - // a timestamp based on the elapsed ticks - if (tickBased) { - return this.counter * SpongeScheduler.TICK_DURATION_NS; + public void close() throws Exception { + throw new UnsupportedOperationException(); + } + + private record SchedFutureTask( + Runnable command, + Time clock, + long timeStamp + ) implements Runnable, Delayed { + @Override + public void run() { + this.command.run(); + } + + @Override + public long getDelay(@NotNull TimeUnit unit) { + return unit.convert( + this.timeStamp - this.clock.timeNanos(), + TimeUnit.NANOSECONDS); + } + @Override + public int compareTo(@NotNull Delayed other) { + return other == this ? 0 : Long.compare( + this.getDelay(TimeUnit.NANOSECONDS), + other.getDelay(TimeUnit.NANOSECONDS)); } - return super.timestamp(false); } } diff --git a/src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java b/src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java new file mode 100644 index 00000000000..397463a0957 --- /dev/null +++ b/src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java @@ -0,0 +1,49 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.scheduler; + +import org.spongepowered.api.scheduler.ScheduledTask; +import org.spongepowered.api.scheduler.Task; + +import java.time.Duration; + +interface TaskProcedure extends Task { + + void execute(ScheduledTask task) throws Exception; + + Time intervalTime(); + + Time delayTime(); + + @Override + default Duration delay() { + return this.delayTime().toDuration(); + } + + @Override + default Duration interval() { + return this.intervalTime().toDuration(); + } +} diff --git a/src/main/java/org/spongepowered/common/scheduler/Time.java b/src/main/java/org/spongepowered/common/scheduler/Time.java new file mode 100644 index 00000000000..4c55fbc59ff --- /dev/null +++ b/src/main/java/org/spongepowered/common/scheduler/Time.java @@ -0,0 +1,73 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.scheduler; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +public interface Time { + + Time ZERO = new RealTime(0); + Time REAL_TIME = new Time() { + @Override + public boolean tickBased() { + return false; + } + @Override + public long timeNanos() { + return System.nanoTime(); + } + }; + + boolean tickBased(); + + + long timeNanos(); + + + default long convert(TimeUnit unit) { + return unit.convert(timeNanos(), TimeUnit.NANOSECONDS); + } + + default Duration toDuration() { + return Duration.ofNanos(timeNanos()); + } + + + record RealTime(long timeNanos) implements Time { + + @Override + public boolean tickBased() { + return false; + } + } + record TickTime(long timeNanos) implements Time { + + @Override + public boolean tickBased() { + return true; + } + } +}