1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
package de.hysky.skyblocker.utils.scheduler;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import it.unimi.dsi.fastutil.ints.AbstractInt2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* A scheduler for running tasks at a later time. Tasks will be run synchronously on the main client thread. Use the instance stored in {@link #INSTANCE}. Do not instantiate this class.
*/
public class Scheduler {
protected static final Logger LOGGER = LoggerFactory.getLogger(Scheduler.class);
public static final Scheduler INSTANCE = new Scheduler();
private int currentTick = 0;
private final AbstractInt2ObjectMap<List<ScheduledTask>> tasks = new Int2ObjectOpenHashMap<>();
private final ExecutorService executors = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("Skyblocker-Scheduler-%d").build());
protected Scheduler() {
}
/**
* @see #schedule(Runnable, int, boolean)
*/
public void schedule(Runnable task, int delay) {
schedule(task, delay, false);
}
/**
* @see #scheduleCyclic(Runnable, int, boolean)
*/
public void scheduleCyclic(Runnable task, int period) {
scheduleCyclic(task, period, false);
}
/**
* Schedules a task to run after a delay.
*
* @param task the task to run
* @param delay the delay in ticks
* @param multithreaded whether to run the task on the schedulers dedicated thread pool
*/
public void schedule(Runnable task, int delay, boolean multithreaded) {
if (delay >= 0) {
addTask(new ScheduledTask(task, multithreaded), currentTick + delay);
} else {
LOGGER.warn("Scheduled a task with negative delay");
}
}
/**
* Schedules a task to run every period ticks.
*
* @param task the task to run
* @param period the period in ticks
* @param multithreaded whether to run the task on the schedulers dedicated thread pool
*/
public void scheduleCyclic(Runnable task, int period, boolean multithreaded) {
if (period > 0) {
addTask(new ScheduledTask(task, period, true, multithreaded), currentTick);
} else {
LOGGER.error("Attempted to schedule a cyclic task with period lower than 1");
}
}
/**
* Creates a command that queues a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed.
*
* @param screenFactory the factory of the screen to open
* @return the command
*/
public static Command<FabricClientCommandSource> queueOpenScreenFactoryCommand(Function<CommandContext<FabricClientCommandSource>, Screen> screenFactory) {
return context -> queueOpenScreen(screenFactory.apply(context));
}
/**
* Creates a command that queues a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed.
*
* @param screenSupplier the supplier of the screen to open
* @return the command
*/
public static Command<FabricClientCommandSource> queueOpenScreenCommand(Supplier<Screen> screenSupplier) {
return context -> queueOpenScreen(screenSupplier.get());
}
/**
* Creates a command that queues a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed.
*
* @param screen the screen to open
* @return the command
*/
public static Command<FabricClientCommandSource> queueOpenScreenCommand(Screen screen) {
return context -> queueOpenScreen(screen);
}
/**
* Schedules a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed.
*
* @deprecated Use {@link #queueOpenScreen(Screen)} instead
* @param screenSupplier the supplier of the screen to open
* @see #queueOpenScreenCommand(Supplier)
*/
@Deprecated(forRemoval = true)
public static int queueOpenScreen(Supplier<Screen> screenSupplier) {
return queueOpenScreen(screenSupplier.get());
}
/**
* Schedules a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed.
*
* @param screen the screen to open
* @see #queueOpenScreenFactoryCommand(Function)
*/
public static int queueOpenScreen(Screen screen) {
MinecraftClient.getInstance().send(() -> MinecraftClient.getInstance().setScreen(screen));
return Command.SINGLE_SUCCESS;
}
public void tick() {
if (tasks.containsKey(currentTick)) {
List<ScheduledTask> currentTickTasks = tasks.get(currentTick);
//noinspection ForLoopReplaceableByForEach (or else we get a ConcurrentModificationException)
for (int i = 0; i < currentTickTasks.size(); i++) {
ScheduledTask task = currentTickTasks.get(i);
if (!runTask(task, task.multithreaded)) {
tasks.computeIfAbsent(currentTick + 1, key -> new ArrayList<>()).add(task);
}
}
tasks.remove(currentTick);
}
currentTick += 1;
}
/**
* Runs the task if able.
*
* @param task the task to run
* @return {@code true} if the task is run, and {@link false} if task is not run.
*/
protected boolean runTask(Runnable task, boolean multithreaded) {
if (multithreaded) {
executors.execute(task);
} else {
task.run();
}
return true;
}
private void addTask(ScheduledTask scheduledTask, int schedule) {
if (tasks.containsKey(schedule)) {
tasks.get(schedule).add(scheduledTask);
} else {
List<ScheduledTask> list = new ArrayList<>();
list.add(scheduledTask);
tasks.put(schedule, list);
}
}
/**
* A task that that is scheduled to execute once after the {@code interval}, or that is run every {@code interval} ticks.
*/
protected record ScheduledTask(Runnable task, int interval, boolean cyclic, boolean multithreaded) implements Runnable {
private ScheduledTask(Runnable task, boolean multithreaded) {
this(task, -1, false, multithreaded);
}
@Override
public void run() {
task.run();
if (cyclic) INSTANCE.addTask(this, INSTANCE.currentTick + interval);
}
}
}
|