Skip to content

Commit 4020847

Browse files
BlackBaronesshuanmeng-qwqRollczi
authored
GH-437 Improve schedulers (#486)
* build: update Minecraft/Velocity versions in examples * improve bukkit scheduler * improve bungee scheduler * rework core scheduler and use it for bungee * implement Sponge scheduler * Revert "build: update Minecraft/Velocity versions in examples" This reverts commit 47496d5. * revert disabling examples (i didnt want to commit that) * fix compilation * fix bugs * Support Fabric Scheduler (#1) * add fabric server scheduler * fix server, update ExampleCommand * del mixin * add FabricClientScheduler * fix later * fix later, update ClientCommands * fix tests * Don't use Throwables * Wrap using the default implementation (#2) * add fabric server scheduler * fix server, update ExampleCommand * del mixin * add FabricClientScheduler * fix later * fix later, update ClientCommands * extend SchedulerExecutorPoolImpl * Revert SchedulerExecutorPoolImpl * Create SchedulerMainThreadBased to simplify implementations * Make fabric implementation more simpler. * Revert unit tests changes. * Revert unit tests changes. * SpongeScheduler code style changes. * Implement important notes from BlackBaroness to the old scheduler. * Rename AbstractMainThreadBasedScheduler. Remove unused scheduler. * Fix runAsynchronous * Fix runAsynchronous * improve comments * improve comments * improve Sponge scheduler * remove empty lines * improve Sponge scheduler * Improve the language of an error * Update litecommands-sponge/src/dev/rollczi/litecommands/sponge/SpongeScheduler.java Co-authored-by: Norbert Dejlich <[email protected]> --------- Co-authored-by: huanmeng_qwq <[email protected]> Co-authored-by: Rollczi <[email protected]>
1 parent 5cc1cc8 commit 4020847

File tree

17 files changed

+330
-80
lines changed

17 files changed

+330
-80
lines changed

buildSrc/src/main/kotlin/Versions.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ object Versions {
3636
const val FABRIC_LOADER = "0.16.9"
3737
const val FABRIC_COMMAND_API_V2 = "2.2.37+7feeb7331c"
3838
const val FABRIC_COMMAND_API_V1 = "1.2.56+f71b366f73"
39+
const val FABRIC_LIFECYCLE_EVENTS_V1 = "2.3.12+6c1df36019"
3940

4041
// ChatGPT
4142
const val GSON = "2.11.0"

examples/fabric/src/main/java/dev/rollczi/example/fabric/client/command/ClientCommands.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.rollczi.example.fabric.client.command;
22

33
import dev.rollczi.litecommands.annotations.argument.Arg;
4+
import dev.rollczi.litecommands.annotations.async.Async;
45
import dev.rollczi.litecommands.annotations.command.Command;
56
import dev.rollczi.litecommands.annotations.context.Sender;
67
import dev.rollczi.litecommands.annotations.execute.Execute;
@@ -36,4 +37,14 @@ String health(@Arg ClientPlayerEntity player) {
3637
return String.valueOf(player.getHealth());
3738
}
3839

40+
@Execute(name = "thread1")
41+
String thread1() {
42+
return Thread.currentThread().getName();
43+
}
44+
45+
@Execute(name = "thread2")
46+
@Async
47+
String thread2() {
48+
return Thread.currentThread().getName();
49+
}
3950
}

examples/fabric/src/main/java/dev/rollczi/example/fabric/server/command/ExampleCommand.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.rollczi.example.fabric.server.command;
22

33
import dev.rollczi.litecommands.annotations.argument.Arg;
4+
import dev.rollczi.litecommands.annotations.async.Async;
45
import dev.rollczi.litecommands.annotations.command.Command;
56
import dev.rollczi.litecommands.annotations.execute.Execute;
67
import dev.rollczi.litecommands.annotations.join.Join;
@@ -19,4 +20,15 @@ void sendMessage(@Arg("player") ServerPlayerEntity player, @Join("reason") Strin
1920
Text sendMessage(@Quoted @Arg String message) {
2021
return Text.of("You saied: " + message);
2122
}
23+
24+
@Execute(name = "thread1")
25+
String thread1() {
26+
return Thread.currentThread().getName();
27+
}
28+
29+
@Execute(name = "thread2")
30+
@Async
31+
String thread2() {
32+
return Thread.currentThread().getName();
33+
}
2234
}

litecommands-annotations/test/dev/rollczi/litecommands/annotations/async/AsyncCommandTest.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package dev.rollczi.litecommands.annotations.async;
22

3-
import dev.rollczi.litecommands.unit.annotations.LiteTestSpec;
43
import dev.rollczi.litecommands.annotations.argument.Arg;
54
import dev.rollczi.litecommands.annotations.command.Command;
65
import dev.rollczi.litecommands.annotations.context.Context;
@@ -13,7 +12,7 @@
1312
import dev.rollczi.litecommands.scheduler.SchedulerExecutorPoolImpl;
1413
import dev.rollczi.litecommands.unit.AssertExecute;
1514
import dev.rollczi.litecommands.unit.TestSender;
16-
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
15+
import dev.rollczi.litecommands.unit.annotations.LiteTestSpec;
1716
import org.junit.jupiter.api.Test;
1817

1918
import java.util.Date;
@@ -79,7 +78,7 @@ public String testAsyncArgs(@Context Date date, @Arg String first, @Async @Arg S
7978

8079
@Async
8180
@Execute(name = "async-args-and-method")
82-
public String testAsyncArgs2(@Context Date date, @Async @Arg String first, @Arg String second) {
81+
public String testAsyncArgs2(@Context Date date, @Async @Arg String first, @Arg String second) {
8382
return Thread.currentThread().getName() + " args [first=" + first + ", second=" + second + "]";
8483
}
8584

litecommands-annotations/test/dev/rollczi/litecommands/annotations/async/ParallelAsyncCommandTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,4 @@ void testAsyncArgsAndMethod() {
7474
}
7575
}
7676

77-
7877
}

litecommands-bukkit/src/dev/rollczi/litecommands/bukkit/BukkitSchedulerImpl.java

Lines changed: 18 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
package dev.rollczi.litecommands.bukkit;
22

3-
import dev.rollczi.litecommands.scheduler.Scheduler;
4-
import dev.rollczi.litecommands.scheduler.SchedulerPoll;
5-
import dev.rollczi.litecommands.shared.ThrowingSupplier;
3+
import dev.rollczi.litecommands.scheduler.AbstractMainThreadBasedScheduler;
4+
import java.util.logging.Level;
65
import org.bukkit.Bukkit;
76
import org.bukkit.plugin.Plugin;
87
import org.bukkit.scheduler.BukkitScheduler;
98

109
import java.time.Duration;
11-
import java.util.concurrent.CompletableFuture;
1210

13-
class BukkitSchedulerImpl implements Scheduler {
11+
class BukkitSchedulerImpl extends AbstractMainThreadBasedScheduler {
1412

1513
private final BukkitScheduler bukkitScheduler;
1614
private final Plugin plugin;
@@ -20,68 +18,36 @@ class BukkitSchedulerImpl implements Scheduler {
2018
this.plugin = plugin;
2119
}
2220

23-
@Override
24-
public <T> CompletableFuture<T> supplyLater(SchedulerPoll type, Duration delay, ThrowingSupplier<T, Throwable> supplier) {
25-
SchedulerPoll resolved = type.resolve(SchedulerPoll.MAIN, SchedulerPoll.ASYNCHRONOUS);
26-
27-
if (resolved.equals(SchedulerPoll.MAIN)) {
28-
return supplySync(type, supplier, delay);
29-
}
30-
31-
if (resolved.equals(SchedulerPoll.ASYNCHRONOUS)) {
32-
return supplyAsync(type, supplier, delay);
33-
}
34-
35-
throw new IllegalArgumentException("Unknown scheduler poll type: " + type);
36-
}
37-
3821
@Override
3922
public void shutdown() {
4023
}
4124

42-
private <T> CompletableFuture<T> supplySync(SchedulerPoll type, ThrowingSupplier<T, Throwable> supplier, Duration delay) {
43-
CompletableFuture<T> future = new CompletableFuture<>();
44-
25+
@Override
26+
protected void runSynchronous(Runnable task, Duration delay) {
4527
if (Bukkit.isPrimaryThread() && delay.isZero()) {
46-
return tryRun(type, future, supplier);
28+
task.run();
29+
return;
4730
}
4831

4932
if (delay.isZero()) {
50-
bukkitScheduler.runTask(plugin, () -> tryRun(type, future, supplier));
51-
}
52-
else {
53-
bukkitScheduler.runTaskLater(plugin, () -> tryRun(type, future, supplier), toTicks(delay));
33+
bukkitScheduler.runTask(plugin, task);
34+
} else {
35+
bukkitScheduler.runTaskLater(plugin, task, toTicks(delay));
5436
}
55-
56-
return future;
5737
}
5838

59-
private <T> CompletableFuture<T> supplyAsync(SchedulerPoll type, ThrowingSupplier<T, Throwable> supplier, Duration delay) {
60-
CompletableFuture<T> future = new CompletableFuture<>();
61-
39+
@Override
40+
protected void runAsynchronous(Runnable task, Duration delay) {
6241
if (delay.isZero()) {
63-
bukkitScheduler.runTaskAsynchronously(plugin, () -> tryRun(type, future, supplier));
42+
bukkitScheduler.runTaskAsynchronously(plugin, task);
43+
} else {
44+
bukkitScheduler.runTaskLaterAsynchronously(plugin, task, toTicks(delay));
6445
}
65-
else {
66-
bukkitScheduler.runTaskLaterAsynchronously(plugin, () -> tryRun(type, future, supplier), toTicks(delay));
67-
}
68-
69-
return future;
7046
}
7147

72-
private <T> CompletableFuture<T> tryRun(SchedulerPoll type, CompletableFuture<T> future, ThrowingSupplier<T, Throwable> supplier) {
73-
try {
74-
future.complete(supplier.get());
75-
}
76-
catch (Throwable throwable) {
77-
future.completeExceptionally(throwable);
78-
79-
if (type.isLogging()) {
80-
throwable.printStackTrace();
81-
}
82-
}
83-
84-
return future;
48+
@Override
49+
protected void log(Throwable throwable) {
50+
this.plugin.getLogger().log(Level.SEVERE, "An error occurred while executing a task", throwable);
8551
}
8652

8753
private long toTicks(Duration duration) {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package dev.rollczi.litecommands.scheduler;
2+
3+
import dev.rollczi.litecommands.shared.ThrowingSupplier;
4+
import java.time.Duration;
5+
import java.util.concurrent.CompletableFuture;
6+
7+
public abstract class AbstractMainThreadBasedScheduler implements Scheduler {
8+
9+
@Override
10+
public final <T> CompletableFuture<T> supplyLater(SchedulerPoll type, Duration delay, ThrowingSupplier<T, Throwable> supplier) {
11+
SchedulerPoll resolved = type.resolve(SchedulerPoll.MAIN, SchedulerPoll.ASYNCHRONOUS);
12+
CompletableFuture<T> future = new CompletableFuture<>();
13+
14+
if (resolved.equals(SchedulerPoll.MAIN)) {
15+
runSynchronous(() -> tryRun(type, future, supplier), delay);
16+
return future;
17+
}
18+
19+
if (resolved.equals(SchedulerPoll.ASYNCHRONOUS)) {
20+
runAsynchronous(() -> tryRun(type, future, supplier), delay);
21+
return future;
22+
}
23+
24+
throw new IllegalArgumentException("Unknown scheduler poll type: " + type);
25+
}
26+
27+
abstract protected void runSynchronous(Runnable task, Duration delay);
28+
29+
protected abstract void runAsynchronous(Runnable task, Duration delay);
30+
31+
private <T> void tryRun(SchedulerPoll type, CompletableFuture<T> future, ThrowingSupplier<T, Throwable> supplier) {
32+
try {
33+
future.complete(supplier.get());
34+
}
35+
catch (Throwable throwable) {
36+
future.completeExceptionally(throwable);
37+
38+
if (type.isLogging()) {
39+
this.log(throwable);
40+
}
41+
}
42+
}
43+
44+
protected void log(Throwable throwable) {}
45+
46+
}

litecommands-core/src/dev/rollczi/litecommands/scheduler/SchedulerExecutorPoolImpl.java

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,34 @@
66
import java.util.concurrent.CompletableFuture;
77
import java.util.concurrent.ExecutorService;
88
import java.util.concurrent.Executors;
9+
import java.util.concurrent.LinkedBlockingQueue;
910
import java.util.concurrent.ScheduledExecutorService;
1011
import java.util.concurrent.ThreadFactory;
12+
import java.util.concurrent.ThreadPoolExecutor;
1113
import java.util.concurrent.TimeUnit;
1214
import java.util.concurrent.atomic.AtomicInteger;
1315

1416
public class SchedulerExecutorPoolImpl implements Scheduler {
1517

18+
public static final int CACHED_POOL = -1;
19+
private static final int MAIN_POOL_SIZE = 1;
20+
1621
private static final String MAIN_THREAD_NAME_FORMAT = "scheduler-%s-main";
1722
private static final String ASYNC_THREAD_NAME_FORMAT = "scheduler-%s-async-%d";
1823

19-
private final ThreadLocal<Boolean> isMainThread = ThreadLocal.withInitial(() -> false);
24+
protected final ThreadLocal<Boolean> isMainThread = ThreadLocal.withInitial(() -> false);
2025

2126
private final ExecutorService mainExecutor;
2227
private final ExecutorService asyncExecutor;
2328
private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
2429

2530
public SchedulerExecutorPoolImpl(String name) {
26-
this(name, -1);
31+
this(name, CACHED_POOL);
2732
}
2833

2934
public SchedulerExecutorPoolImpl(String name, int pool) {
30-
this.mainExecutor = Executors.newSingleThreadScheduledExecutor(runnable -> {
31-
Thread thread = new Thread(runnable);
32-
thread.setName(String.format(MAIN_THREAD_NAME_FORMAT, name));
33-
34-
return thread;
35-
});
36-
37-
this.mainExecutor.submit(() -> isMainThread.set(true));
38-
39-
AtomicInteger asyncCount = new AtomicInteger();
40-
ThreadFactory factory = runnable -> {
41-
Thread thread = new Thread(runnable);
42-
thread.setName(String.format(ASYNC_THREAD_NAME_FORMAT, name, asyncCount.getAndIncrement()));
43-
44-
return thread;
45-
};
46-
47-
this.asyncExecutor = pool < 0 ? Executors.newCachedThreadPool(factory) : Executors.newFixedThreadPool(pool, factory);
35+
this.mainExecutor = createMainExecutor(name);
36+
this.asyncExecutor = createAsyncExecutor(name, pool);
4837
}
4938

5039
@Override
@@ -88,4 +77,54 @@ public void shutdown() {
8877
isMainThread.remove();
8978
}
9079

80+
/**
81+
* Create the main executor.
82+
* In some cases, the main executor might never be used. We don't want to have to create a useless thread
83+
*
84+
* This may look like a premature optimization, but a typical server has 40+ plugins
85+
* As the framework spreads, more and more of them can use LiteCommands,
86+
* so we better handle this situation, there are practically no losses anyway
87+
*
88+
* To improve performance executor set {@link SchedulerExecutorPoolImpl#isMainThread} to true.
89+
*
90+
* @author BlackBaroness
91+
* @return the main executor. (Should have one thread)
92+
*/
93+
protected ExecutorService createMainExecutor(String name) {
94+
ThreadFactory factory = runnable -> new Thread(() -> {
95+
isMainThread.set(true);
96+
runnable.run();
97+
}, String.format(MAIN_THREAD_NAME_FORMAT, name));
98+
99+
ThreadPoolExecutor mainExecutor = new ThreadPoolExecutor(MAIN_POOL_SIZE, MAIN_POOL_SIZE,
100+
1, TimeUnit.HOURS,
101+
new LinkedBlockingQueue<>(),
102+
factory
103+
);
104+
105+
mainExecutor.allowCoreThreadTimeOut(true);
106+
return mainExecutor;
107+
}
108+
109+
/**
110+
* Create async executor.
111+
*
112+
* @author BlackBaroness
113+
* @return async executor.
114+
*/
115+
protected ExecutorService createAsyncExecutor(String name, int pool) {
116+
AtomicInteger asyncThreadCount = new AtomicInteger();
117+
ThreadFactory factory = runnable -> new Thread(runnable, String.format(ASYNC_THREAD_NAME_FORMAT, name, asyncThreadCount.getAndIncrement()));
118+
119+
if (pool < 0) {
120+
return Executors.newCachedThreadPool(factory);
121+
}
122+
123+
return new ThreadPoolExecutor(pool, pool,
124+
3, TimeUnit.MINUTES,
125+
new LinkedBlockingQueue<>(),
126+
factory
127+
);
128+
}
129+
91130
}

litecommands-core/src/dev/rollczi/litecommands/shared/ThrowingRunnable.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@
44
public interface ThrowingRunnable<E extends Throwable> {
55

66
void run() throws E;
7-
87
}

litecommands-fabric/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ dependencies {
2525
modImplementation("net.fabricmc:fabric-loader:${Versions.FABRIC_LOADER}")
2626
modImplementation("net.fabricmc.fabric-api:fabric-command-api-v2:${Versions.FABRIC_COMMAND_API_V2}")
2727
modImplementation("net.fabricmc.fabric-api:fabric-command-api-v1:${Versions.FABRIC_COMMAND_API_V1}")
28+
modImplementation("net.fabricmc.fabric-api:fabric-lifecycle-events-v1:${Versions.FABRIC_LIFECYCLE_EVENTS_V1}")
2829
}
2930

3031
litecommandsPublish {

0 commit comments

Comments
 (0)