aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java
blob: 139ac05e8e9a4f0a113c0733906ae83acd1cc37a (plain)
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
package de.hysky.skyblocker.utils.scheduler;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.mojang.brigadier.Command;
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.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 {
    private 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");
        }
    }

    public static Command<FabricClientCommandSource> queueOpenScreenCommand(Supplier<Screen> screenSupplier) {
        return context -> INSTANCE.queueOpenScreen(screenSupplier);
    }

    /**
     * Schedules 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
     * @see #queueOpenScreenCommand(Supplier)
     */
    public int queueOpenScreen(Supplier<Screen> screenSupplier) {
        MinecraftClient.getInstance().send(() -> MinecraftClient.getInstance().setScreen(screenSupplier.get()));
        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);
        }
    }
}