From 4b3f9771c4394ad066b3ed9bcd008ce975d735c8 Mon Sep 17 00:00:00 2001 From: phinner <62483793+phinner@users.noreply.github.com> Date: Fri, 22 Mar 2024 00:18:32 +0100 Subject: [PATCH] feat(core): Added distributor 3 scheduler (partial) --- .../core/DistributorCorePlugin.java | 2 +- .../core/annotation/TaskHandler.java | 60 +++++++ .../core/scheduler/Cancellable.java | 30 ++++ .../core/scheduler/MindustryTimeUnit.java | 109 ++++++++++++ .../core/scheduler/PluginScheduler.java | 35 ++++ .../core/scheduler/PluginSchedulerImpl.java | 118 +++++++++++++ .../core/scheduler/PluginTask.java | 35 ++++ .../core/scheduler/PluginTaskBuilder.java | 82 +++++++++ .../core/scheduler/PluginTaskImpl.java | 163 ++++++++++++++++++ .../core/scheduler/TimeSource.java | 44 +++++ .../core/scheduler/package-info.java | 4 + 11 files changed, 681 insertions(+), 1 deletion(-) create mode 100644 distributor-core/src/main/java/com/xpdustry/distributor/core/annotation/TaskHandler.java create mode 100644 distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/Cancellable.java create mode 100644 distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/MindustryTimeUnit.java create mode 100644 distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginScheduler.java create mode 100644 distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginSchedulerImpl.java create mode 100644 distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginTask.java create mode 100644 distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginTaskBuilder.java create mode 100644 distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginTaskImpl.java create mode 100644 distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/TimeSource.java create mode 100644 distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/package-info.java diff --git a/distributor-core/src/main/java/com/xpdustry/distributor/core/DistributorCorePlugin.java b/distributor-core/src/main/java/com/xpdustry/distributor/core/DistributorCorePlugin.java index 9f190bf0..b6029bd3 100644 --- a/distributor-core/src/main/java/com/xpdustry/distributor/core/DistributorCorePlugin.java +++ b/distributor-core/src/main/java/com/xpdustry/distributor/core/DistributorCorePlugin.java @@ -30,9 +30,9 @@ public final class DistributorCorePlugin extends AbstractMindustryPlugin implements Distributor { private final ServiceManager services = ServiceManager.simple(); + private final MultiLocalizationSource source = MultiLocalizationSource.create(); private CommandFacade.@Nullable Factory factory = null; private @Nullable PermissionManager permissions = null; - private final MultiLocalizationSource source = MultiLocalizationSource.create(); @Override public ServiceManager getServiceManager() { diff --git a/distributor-core/src/main/java/com/xpdustry/distributor/core/annotation/TaskHandler.java b/distributor-core/src/main/java/com/xpdustry/distributor/core/annotation/TaskHandler.java new file mode 100644 index 00000000..506c1243 --- /dev/null +++ b/distributor-core/src/main/java/com/xpdustry/distributor/core/annotation/TaskHandler.java @@ -0,0 +1,60 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.core.annotation; + +import com.xpdustry.distributor.core.scheduler.Cancellable; +import com.xpdustry.distributor.core.scheduler.MindustryTimeUnit; +import com.xpdustry.distributor.core.scheduler.PluginScheduler; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method as a task handler, meaning it will be registered and called as a scheduled task in the + * {@link PluginScheduler}. + *
+ * The annotated method can have one {@link Cancellable} parameter to allow the task to cancel itself. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface TaskHandler { + + /** + * The interval between each execution of the task. + * The task will be executed once if the interval is set to a value below -1. + */ + long interval() default -1; + + /** + * The initial delay before the first execution of the task. + * The task will be executed immediately if the delay is set to a value below -1. + */ + long delay() default -1; + + /** + * The time unit of the interval and initial delay. + */ + MindustryTimeUnit unit() default MindustryTimeUnit.SECONDS; + + /** + * Whether the task should be executed asynchronously. + */ + boolean async() default false; +} diff --git a/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/Cancellable.java b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/Cancellable.java new file mode 100644 index 00000000..8504b606 --- /dev/null +++ b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/Cancellable.java @@ -0,0 +1,30 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.core.scheduler; + +/** + * A {@code Cancellable} is used to cancel a task. + */ +public interface Cancellable { + + /** + * Cancels the task bound to this {@code Cancellable}. + */ + void cancel(); +} diff --git a/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/MindustryTimeUnit.java b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/MindustryTimeUnit.java new file mode 100644 index 00000000..6a9897f9 --- /dev/null +++ b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/MindustryTimeUnit.java @@ -0,0 +1,109 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.core.scheduler; + +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import org.jspecify.annotations.Nullable; + +/** + * Time units used by the {@link PluginTaskBuilder} and {@link PluginTask} classes to represent time. + */ +public enum MindustryTimeUnit { + + /** + * Time unit representing one thousandth of a second. + */ + MILLISECONDS(TimeUnit.MILLISECONDS), + + /** + * Time unit representing one game loop, which is 60 times per second. + */ + TICKS(null), + + /** + * Time unit representing one thousandth of a millisecond. + */ + SECONDS(TimeUnit.SECONDS), + + /** + * Time unit representing sixty seconds. + */ + MINUTES(TimeUnit.MINUTES), + + /** + * Time unit representing sixty minutes. + */ + HOURS(TimeUnit.HOURS), + + /** + * Time unit representing twenty-four hours. + */ + DAYS(TimeUnit.DAYS); + + private final @Nullable TimeUnit unit; + + MindustryTimeUnit(final @Nullable TimeUnit unit) { + this.unit = unit; + } + + /** + * Converts the given duration in the given time unit to this time unit. + *

+ * Since this method is equivalent to {@link TimeUnit#convert(long, TimeUnit)}: + *

    + *
  • If it overflows, the result will be {@link Long#MAX_VALUE} if the duration is positive, + * or {@link Long#MIN_VALUE} if it is negative.
  • + *
  • Conversions are floored so converting 999 milliseconds to seconds results in 0.
  • + *
+ * + * @param sourceDuration the duration to convert + * @param sourceUnit the time unit of the duration + * @return the converted duration + * @see TimeUnit#convert(long, TimeUnit) + */ + public long convert(final long sourceDuration, final MindustryTimeUnit sourceUnit) { + if (this == sourceUnit) { + return sourceDuration; + } + final var sourceJavaUnit = sourceUnit.getJavaTimeUnit(); + final var targetJavaUnit = this.getJavaTimeUnit(); + + if (sourceJavaUnit.isPresent() && targetJavaUnit.isPresent()) { + return targetJavaUnit.get().convert(sourceDuration, sourceJavaUnit.get()); + } else if (sourceJavaUnit.isEmpty()) { + return targetJavaUnit + .orElseThrow() + .convert((long) Math.nextUp(sourceDuration * (1000F / 60F)), TimeUnit.MILLISECONDS); + } else { + final var millis = TimeUnit.MILLISECONDS.convert(sourceDuration, sourceJavaUnit.orElseThrow()); + if (millis == Long.MAX_VALUE || millis == Long.MIN_VALUE) { + return millis; + } + return (long) (millis * (60F / 1000L)); + } + } + + /** + * Returns the Java time unit associated with this Mindustry time unit, if any. + */ + public Optional getJavaTimeUnit() { + return Optional.ofNullable(this.unit); + } +} diff --git a/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginScheduler.java b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginScheduler.java new file mode 100644 index 00000000..d093b830 --- /dev/null +++ b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginScheduler.java @@ -0,0 +1,35 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.core.scheduler; + +import com.xpdustry.distributor.core.plugin.MindustryPlugin; + +/** + * A {@code PluginScheduler} is used to schedule tasks for a plugin. A better alternative to {@link arc.util.Timer}. + */ +public interface PluginScheduler { + + /** + * Returns a new {@link PluginTaskBuilder} instance scheduling a task. + * + * @param plugin the plugin to schedule the task for. + * @return a new {@link PluginTaskBuilder} instance. + */ + PluginTaskBuilder schedule(final MindustryPlugin plugin); +} diff --git a/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginSchedulerImpl.java b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginSchedulerImpl.java new file mode 100644 index 00000000..69844baf --- /dev/null +++ b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginSchedulerImpl.java @@ -0,0 +1,118 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.core.scheduler; + +import com.xpdustry.distributor.core.plugin.MindustryPlugin; +import com.xpdustry.distributor.core.plugin.PluginListener; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Queue; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class PluginSchedulerImpl implements PluginScheduler, PluginListener { + + static final String DISTRIBUTOR_WORKER_BASE_NAME = "distributor-worker-"; + private static final Logger logger = LoggerFactory.getLogger("PluginScheduler"); + + private final Queue> tasks = + new PriorityBlockingQueue<>(16, Comparator.comparing(PluginTaskImpl::getNextExecutionTime)); + private final ForkJoinPool pool; + private final Executor syncExecutor; + private final TimeSource source; + + public PluginSchedulerImpl(final TimeSource source, final Executor syncExecutor, final int parallelism) { + this.pool = new ForkJoinPool(parallelism, new PluginSchedulerWorkerThreadFactory(), null, false); + this.syncExecutor = syncExecutor; + this.source = source; + } + + @Override + public PluginTaskBuilder schedule(final MindustryPlugin plugin) { + return new PluginTaskImpl.Builder(this, plugin); + } + + @Override + public void onPluginUpdate() { + while (!this.tasks.isEmpty()) { + final var task = this.tasks.peek(); + if (task.isCancelled()) { + this.tasks.remove(); + } else if (task.getNextExecutionTime() < this.source.getCurrentTicks()) { + this.tasks.remove(); + final Executor executor = task.isAsync() ? this.pool : this.syncExecutor; + executor.execute(task); + } else { + break; + } + } + } + + @Override + public void onPluginExit() { + logger.info("Shutdown scheduler."); + this.pool.shutdown(); + try { + if (!this.pool.awaitTermination(20, TimeUnit.SECONDS)) { + logger.error("Timed out waiting for the scheduler to terminate properly"); + Thread.getAllStackTraces().forEach((thread, stack) -> { + if (thread.getName().startsWith(DISTRIBUTOR_WORKER_BASE_NAME)) { + logger.error( + "Worker thread {} may be blocked, possibly the reason for the slow shutdown:\n{}", + thread.getName(), + Arrays.stream(stack).map(e -> " " + e).collect(Collectors.joining("\n"))); + } + }); + } + } catch (final InterruptedException e) { + logger.error("The plugin scheduler shutdown have been interrupted.", e); + } + } + + void schedule(final PluginTaskImpl task) { + this.tasks.add(task); + } + + TimeSource getTimeSource() { + return this.source; + } + + boolean isShutdown() { + return this.pool.isShutdown(); + } + + private static final class PluginSchedulerWorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { + + private static final AtomicInteger COUNT = new AtomicInteger(0); + + @Override + public ForkJoinWorkerThread newThread(final ForkJoinPool pool) { + final var thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); + thread.setName(DISTRIBUTOR_WORKER_BASE_NAME + COUNT.getAndIncrement()); + return thread; + } + } +} diff --git a/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginTask.java b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginTask.java new file mode 100644 index 00000000..4d7b2b48 --- /dev/null +++ b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginTask.java @@ -0,0 +1,35 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.core.scheduler; + +import com.xpdustry.distributor.core.plugin.PluginAware; +import java.util.concurrent.Future; + +/** + * A {@code PluginTask} is a future used by a {@link PluginScheduler}. + * + * @param the type of the value returned by this task. + */ +public interface PluginTask extends Future, PluginAware { + + /** + * Returns whether this future is executed asynchronously. + */ + boolean isAsync(); +} diff --git a/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginTaskBuilder.java b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginTaskBuilder.java new file mode 100644 index 00000000..21d899a0 --- /dev/null +++ b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginTaskBuilder.java @@ -0,0 +1,82 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.core.scheduler; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * A helper object for building and scheduling a {@link PluginTask}. + * + *
 {@code
+ *      final PluginScheduler scheduler = DistributorProvider.get().getPluginScheduler();
+ *      final MindustryPlugin plugin = ...;
+ *      // Warn the players the server is close in 5 minutes.
+ *      Groups.player.each(p -> p.sendMessage("The server will restart in 5 minutes."));
+ *      // Now schedule the closing task.
+ *      scheduler.scheduleSync(plugin).delay(5L, MindustryTimeUnit.MINUTES).execute(() -> Core.app.exit());
+ * } 
+ */ +public interface PluginTaskBuilder { + + PluginTaskBuilder async(final boolean async); + + /** + * Run the task after a delay. + * + * @param delay the delay. + * @param unit the time unit of the delay. + * @return this builder. + */ + PluginTaskBuilder delay(final long delay, final MindustryTimeUnit unit); + + /** + * Run the task periodically with a fixed interval. + * Stops the periodic execution if an exception is thrown. + * + * @param interval the interval between the end of the last execution and the start of the next. + * @param unit the time unit of the interval. + * @return this builder. + */ + PluginTaskBuilder repeat(final long interval, final MindustryTimeUnit unit); + + /** + * Build and schedule the task with the given task. + * + * @param runnable the task to run. + * @return a new plugin task. + */ + PluginTask execute(final Runnable runnable); + + /** + * Build and schedule the task with the given task. + * + * @param consumer the task to run, with a cancellable object to stop the task if it's periodic. + * @return a new plugin task. + */ + PluginTask execute(final Consumer consumer); + + /** + * Build and schedule the task with the given task. + * + * @param supplier the task to run, with an output value. Won't output any result value if the task is periodic. + * @return a new plugin task. + */ + PluginTask execute(final Supplier supplier); +} diff --git a/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginTaskImpl.java b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginTaskImpl.java new file mode 100644 index 00000000..0cc03ff9 --- /dev/null +++ b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/PluginTaskImpl.java @@ -0,0 +1,163 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.core.scheduler; + +import com.xpdustry.distributor.core.plugin.MindustryPlugin; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; +import java.util.function.Consumer; +import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + +final class PluginTaskImpl extends FutureTask implements PluginTask { + + private final MindustryPlugin plugin; + private final boolean async; + private final long period; + private final PluginSchedulerImpl scheduler; + private long nextRun; + + private PluginTaskImpl( + final MindustryPlugin plugin, + final Callable callable, + final boolean async, + final long period, + final PluginSchedulerImpl scheduler) { + super(callable); + this.plugin = plugin; + this.async = async; + this.period = period; + this.scheduler = scheduler; + } + + @Override + public void run() { + if (this.scheduler.isShutdown() + && (this.period == 0 + || this.nextRun - this.scheduler.getTimeSource().getCurrentTicks() > 0)) { + this.cancel(false); + } else if (this.period == 0) { + super.run(); + } else if (super.runAndReset()) { + this.nextRun = this.scheduler.getTimeSource().getCurrentTicks() + this.period; + this.scheduler.schedule(this); + } + } + + @Override + public boolean isAsync() { + return this.async; + } + + @Override + public MindustryPlugin getPlugin() { + return this.plugin; + } + + @Override + protected void setException(final Throwable throwable) { + super.setException(throwable); + this.plugin + .getLogger() + .error( + "An error occurred in thread {} of the plugin scheduler.", + Thread.currentThread().getName(), + throwable); + } + + public long getNextExecutionTime() { + return this.nextRun; + } + + static final class Builder implements PluginTaskBuilder { + + private final PluginSchedulerImpl scheduler; + private final MindustryPlugin plugin; + private boolean async; + private long delay = 0; + private long repeat = 0; + + public Builder(final PluginSchedulerImpl scheduler, final MindustryPlugin plugin) { + this.scheduler = scheduler; + this.plugin = plugin; + } + + @Override + public PluginTaskBuilder async(final boolean async) { + this.async = async; + return this; + } + + @Override + public PluginTaskBuilder delay(final long delay, final MindustryTimeUnit unit) { + this.delay = MindustryTimeUnit.TICKS.convert(delay, unit); + return this; + } + + @Override + public PluginTaskBuilder repeat(final long interval, final MindustryTimeUnit unit) { + this.repeat = MindustryTimeUnit.TICKS.convert(interval, unit); + return this; + } + + @Override + public PluginTask execute(final Runnable runnable) { + final var task = new PluginTaskImpl( + this.plugin, Executors.callable(runnable, null), this.async, this.repeat, this.scheduler); + return this.schedule(task); + } + + @Override + public PluginTask execute(final Consumer consumer) { + final var cancellable = new PluginTaskCancellable(); + final var task = new PluginTaskImpl( + this.plugin, + Executors.callable(() -> consumer.accept(cancellable), null), + this.async, + this.repeat, + this.scheduler); + cancellable.task = task; + return this.schedule(task); + } + + @Override + public PluginTask execute(final Supplier supplier) { + final var task = new PluginTaskImpl<>(this.plugin, supplier::get, this.async, this.repeat, this.scheduler); + return this.schedule(task); + } + + private PluginTaskImpl schedule(final PluginTaskImpl task) { + task.nextRun = this.scheduler.getTimeSource().getCurrentTicks() + this.delay; + this.scheduler.schedule(task); + return task; + } + } + + private static final class PluginTaskCancellable implements Cancellable { + + private @Nullable PluginTask task = null; + + @Override + public void cancel() { + Objects.requireNonNull(this.task).cancel(false); + } + } +} diff --git a/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/TimeSource.java b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/TimeSource.java new file mode 100644 index 00000000..6852fa6f --- /dev/null +++ b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/TimeSource.java @@ -0,0 +1,44 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.core.scheduler; + +import arc.util.Time; + +/** + * A {@code PluginTimeSource} provides the current time in milliseconds. + */ +@FunctionalInterface +public interface TimeSource { + + /** + * Returns a {@code PluginTimeSource} using {@link Time#globalTime} to provide the current time. + */ + static TimeSource arc() { + return () -> (long) Time.globalTime; + } + + /** + * Returns a {@code PluginTimeSource} using {@link System#currentTimeMillis()} to provide the current time. + */ + static TimeSource standard() { + return () -> System.currentTimeMillis() / 16L; + } + + long getCurrentTicks(); +} diff --git a/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/package-info.java b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/package-info.java new file mode 100644 index 00000000..0c035531 --- /dev/null +++ b/distributor-core/src/main/java/com/xpdustry/distributor/core/scheduler/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package com.xpdustry.distributor.core.scheduler; + +import org.jspecify.annotations.NullMarked;