pluginList = new ArrayList<>();
18 |
19 | @Override
20 | public void onLoad() {
21 | this.pluginList.add(new LinearProcessingPlugin());
22 | this.pluginList.add(new LoopedProcessingPlugin());
23 | }
24 |
25 | @Override
26 | public void onEnable() {
27 | this.pluginList.forEach(plugin -> plugin.onEnable(this));
28 | }
29 |
30 | @Override
31 | public void onDisable() {
32 | this.pluginList.forEach(plugin -> plugin.onDisable(this));
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/additions/AdeptScheduledWorkloadRunnable.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.additions;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledWorkload;
4 |
5 | import java.util.LinkedList;
6 |
7 | /**
8 | * Read this only after understanding the "looped" package.
9 | *
10 | * This is a more sophisticated version of the ScheduledWorkloadRunnable.
11 | * The trick here is to call removeIf on a LinkedList. Usually the runtime
12 | * complexity of remove while iterating for Lists is O(n) which is quite bad.
13 | * Here the LinkedList shines with O(1) scaling.
14 | *
15 | * Each ScheduledWorkload will be ticked once every tick and removed if shouldBeRescheduled() returns false.
16 | *
17 | * Benefits: Really easy to implement. Very good performance.
18 | * Downside: There is no time limiting factor anymore. Every workload gets ticked once every tick.
19 | *
20 | * This runnable should be used for tasks that don't need any time limiting.
21 | * Like sending every player on the server his action bar every 20 ticks.
22 | * Or doing other tasks that have an internal tick counter and only run every N calls of
23 | * compute.
24 | */
25 | public class AdeptScheduledWorkloadRunnable implements Runnable {
26 |
27 | private final LinkedList workloads = new LinkedList<>();
28 |
29 | public void addWorkload(ScheduledWorkload workload) {
30 | this.workloads.add(workload);
31 | }
32 |
33 | @Override
34 | public void run() {
35 | this.workloads.removeIf(this::runAndCheck);
36 | }
37 |
38 | private boolean runAndCheck(ScheduledWorkload workload) {
39 | workload.compute();
40 | return workload.shouldBeRescheduled();
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/evendistribution/EvenDistributionPlugin.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.evendistribution;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.DummyPlugin;
4 | import org.bukkit.plugin.java.JavaPlugin;
5 |
6 | /**
7 | * TODO: Work in progress.
8 | */
9 | public class EvenDistributionPlugin implements DummyPlugin {
10 |
11 | @Override
12 | public void onEnable(JavaPlugin host) {
13 |
14 | }
15 |
16 | @Override
17 | public void onDisable(JavaPlugin host) {
18 |
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/looped/LoopedProcessingPlugin.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.looped;
2 |
3 | import co.aikar.commands.BukkitCommandManager;
4 | import com.gestankbratwurst.scheduling.workloaddistribution.DummyPlugin;
5 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledWorkloadRunnable;
6 | import com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.implementations.distributed.WorkloadRunnable;
7 | import org.bukkit.Bukkit;
8 | import org.bukkit.Particle;
9 | import org.bukkit.plugin.java.JavaPlugin;
10 |
11 | import java.util.Arrays;
12 | import java.util.List;
13 |
14 | /**
15 | * This is how your JavaPlugin class could look like.
16 | * You have a ScheduledWorkloadRunnable singleton to which you
17 | * pass your ScheduledWorkloads. The command is for you to test
18 | * out the two implementations and can be ignored. The
19 | * WorkloadRunnable is only used for demonstrating purposes and
20 | * is part of the simple example package.
21 | */
22 | public class LoopedProcessingPlugin implements DummyPlugin {
23 |
24 | private final WorkloadRunnable workloadRunnable = new WorkloadRunnable();
25 | private final ScheduledWorkloadRunnable scheduledWorkloadRunnable = new ScheduledWorkloadRunnable();
26 |
27 | @Override
28 | public void onEnable(JavaPlugin host) {
29 | Bukkit.getScheduler().runTaskTimer(host, this.workloadRunnable, 1, 1);
30 | Bukkit.getScheduler().runTaskTimer(host, this.scheduledWorkloadRunnable, 1, 1);
31 | this.registerCommand(host);
32 | }
33 |
34 | @Override
35 | public void onDisable(JavaPlugin host) {
36 |
37 | }
38 |
39 | private void registerCommand(JavaPlugin host) {
40 | BukkitCommandManager commandManager = new BukkitCommandManager(host);
41 | List particles = Arrays.stream(Particle.values()).map(Enum::toString).toList();
42 | commandManager.getCommandCompletions().registerStaticCompletion("Particle", particles);
43 | commandManager.registerCommand(new ParticlesCommand(this.scheduledWorkloadRunnable, this.workloadRunnable));
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/looped/ParticlesCommand.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.looped;
2 |
3 | import co.aikar.commands.BaseCommand;
4 | import co.aikar.commands.annotation.CommandAlias;
5 | import co.aikar.commands.annotation.CommandCompletion;
6 | import co.aikar.commands.annotation.Subcommand;
7 | import co.aikar.commands.annotation.Values;
8 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.CuboidLocationFetcher;
9 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.distributed.ParticleSpawnPoint;
10 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledParticleSpawnPoint;
11 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledWorkloadRunnable;
12 | import com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.implementations.distributed.WorkloadRunnable;
13 | import lombok.Data;
14 | import lombok.RequiredArgsConstructor;
15 | import org.bukkit.Location;
16 | import org.bukkit.Particle;
17 | import org.bukkit.entity.Player;
18 | import org.bukkit.entity.Slime;
19 |
20 | import java.util.WeakHashMap;
21 |
22 | /**
23 | * Example command. Don't worry about it.
24 | * Simply used, so you can test out the different implementations.
25 | */
26 | @CommandAlias("fillparticles")
27 | @RequiredArgsConstructor
28 | public class ParticlesCommand extends BaseCommand {
29 |
30 |
31 | private final WeakHashMap selectedLocationsMap = new WeakHashMap<>();
32 | private final ScheduledWorkloadRunnable scheduledWorkloadRunnable;
33 | private final WorkloadRunnable workloadRunnable;
34 |
35 | @Subcommand("posA")
36 | public void onPosA(Player sender) {
37 | sender.getWorld().spawn(sender.getLocation(), Slime.class, slime -> {
38 | slime.setCustomName("Bob");
39 | slime.setCustomNameVisible(true);
40 | slime.setInvisible(true);
41 | });
42 | this.selectedLocationsMap.computeIfAbsent(sender, key -> new LocationPair()).setLocA(sender.getLocation());
43 | sender.sendMessage("§eLocation A was set.");
44 | }
45 |
46 | @Subcommand("posB")
47 | public void onPosB(Player sender) {
48 | this.selectedLocationsMap.computeIfAbsent(sender, key -> new LocationPair()).setLocB(sender.getLocation());
49 | sender.sendMessage("§eLocation A was set.");
50 | }
51 |
52 | @Subcommand("once")
53 | @CommandCompletion("@Particle")
54 | public void onInstantFill(Player sender, @Values("@Particle") Particle particle) {
55 | LocationPair locationPair = this.selectedLocationsMap.computeIfAbsent(sender, key -> new LocationPair());
56 | if (locationPair.isInvalid()) {
57 | sender.sendMessage("§cYou need to select two locations which have to be in the same world first.");
58 | return;
59 | }
60 | CuboidLocationFetcher.fetch(locationPair.locA, locationPair.locB).forEach(location -> {
61 | ParticleSpawnPoint particleSpawnPoint = new ParticleSpawnPoint(location, particle, 1, 0.01);
62 | this.workloadRunnable.addWorkload(particleSpawnPoint);
63 | });
64 | }
65 |
66 | @Subcommand("repeated")
67 | @CommandCompletion("@Particle")
68 | public void onDistributedFill(Player sender, @Values("@Particle") Particle particle) {
69 | LocationPair locationPair = this.selectedLocationsMap.computeIfAbsent(sender, key -> new LocationPair());
70 | if (locationPair.isInvalid()) {
71 | sender.sendMessage("§cYou need to select two locations which have to be in the same world first.");
72 | return;
73 | }
74 | CuboidLocationFetcher.fetch(locationPair.locA, locationPair.locB).forEach(location -> {
75 | ScheduledParticleSpawnPoint particleSpawnPoint = new ScheduledParticleSpawnPoint(location, particle, 1, 0.01);
76 | this.scheduledWorkloadRunnable.addWorkload(particleSpawnPoint);
77 | });
78 | sender.sendMessage("§cCaution! This wont stop until the server restarts.");
79 | }
80 |
81 | @Data
82 | private static class LocationPair {
83 | private Location locA;
84 | private Location locB;
85 |
86 | boolean isInvalid() {
87 | return this.locA == null || this.locB == null || this.locA.getWorld() != this.locB.getWorld();
88 | }
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/looped/problem/implementation/CuboidLocationFetcher.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation;
2 |
3 | import com.google.common.base.Preconditions;
4 | import org.bukkit.Location;
5 | import org.bukkit.World;
6 | import org.bukkit.util.BoundingBox;
7 | import org.bukkit.util.Vector;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | /**
13 | * Simple utility class to fetch locations between two corners.
14 | */
15 | public class CuboidLocationFetcher {
16 |
17 | public static List fetch(Location cornerA, Location cornerB) {
18 | Preconditions.checkArgument(cornerA.getWorld() == cornerB.getWorld() && cornerA.getWorld() != null);
19 | BoundingBox box = BoundingBox.of(cornerA.getBlock(), cornerB.getBlock());
20 | Vector max = box.getMax();
21 | Vector min = box.getMin();
22 |
23 | World world = cornerA.getWorld();
24 | List fetchedList = new ArrayList<>();
25 |
26 | for (int x = min.getBlockX(); x <= max.getBlockX(); x++) {
27 | for (int y = min.getBlockY(); y <= max.getBlockY(); y++) {
28 | for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) {
29 | Location location = new Location(world, x + 0.5, y + 0.5, z + 0.5);
30 | fetchedList.add(location);
31 | }
32 | }
33 | }
34 |
35 | return fetchedList;
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/looped/problem/implementation/distributed/ParticleSpawnPoint.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.distributed;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.implementations.distributed.Workload;
4 | import org.bukkit.Location;
5 | import org.bukkit.Particle;
6 | import org.bukkit.World;
7 |
8 | import java.util.concurrent.ThreadLocalRandom;
9 |
10 | public class ParticleSpawnPoint implements Workload {
11 |
12 | private final Location location;
13 | private final Particle particle;
14 | private final int amount;
15 | private final double randomOffset;
16 | private final ThreadLocalRandom random;
17 |
18 | public ParticleSpawnPoint(Location location, Particle particle, int amount, double offset) {
19 | this.location = location;
20 | this.particle = particle;
21 | this.amount = amount;
22 | this.randomOffset = offset;
23 | this.random = ThreadLocalRandom.current();
24 | }
25 |
26 | @Override
27 | public void compute() {
28 | double randX = this.random.nextDouble(-this.randomOffset, this.randomOffset);
29 | double randY = this.random.nextDouble(-this.randomOffset, this.randomOffset);
30 | double randZ = this.random.nextDouble(-this.randomOffset, this.randomOffset);
31 | World world = this.location.getWorld();
32 | if (world == null) {
33 | return;
34 | }
35 | world.spawnParticle(this.particle, this.location, this.amount, randX, randY, randZ);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/looped/problem/implementation/scheduled/ScheduledParticleSpawnPoint.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled;
2 |
3 | import org.bukkit.Location;
4 | import org.bukkit.Particle;
5 | import org.bukkit.World;
6 |
7 | import java.util.concurrent.ThreadLocalRandom;
8 |
9 | public class ScheduledParticleSpawnPoint implements ScheduledWorkload {
10 |
11 | private final Location location;
12 | private final Particle particle;
13 | private final int amount;
14 | private final double randomOffset;
15 | private final ThreadLocalRandom random;
16 |
17 | public ScheduledParticleSpawnPoint(Location location, Particle particle, int amount, double offset) {
18 | this.location = location;
19 | this.particle = particle;
20 | this.amount = amount;
21 | this.randomOffset = offset;
22 | this.random = ThreadLocalRandom.current();
23 | }
24 |
25 | @Override
26 | public void compute() {
27 | double randX = this.random.nextDouble(-this.randomOffset, this.randomOffset);
28 | double randY = this.random.nextDouble(-this.randomOffset, this.randomOffset);
29 | double randZ = this.random.nextDouble(-this.randomOffset, this.randomOffset);
30 | World world = this.location.getWorld();
31 | if (world == null) {
32 | return;
33 | }
34 | world.spawnParticle(this.particle, this.location, this.amount, randX, randY, randZ);
35 | }
36 |
37 | @Override
38 | public boolean shouldBeRescheduled() {
39 | return true;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/looped/problem/implementation/scheduled/ScheduledWorkload.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled;
2 |
3 | public interface ScheduledWorkload {
4 |
5 | void compute();
6 |
7 | default boolean shouldBeRescheduled() {
8 | return false;
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/looped/problem/implementation/scheduled/ScheduledWorkloadRunnable.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled;
2 |
3 | import java.util.ArrayDeque;
4 | import java.util.Deque;
5 |
6 | /**
7 | * This is the basic implementation of a WorkloadRunnable.
8 | * It processes as many Workloads every tick as the given
9 | * field MAX_MILLIS_PER_TICK allows.
10 | */
11 | public class ScheduledWorkloadRunnable implements Runnable {
12 |
13 | private static final double MAX_MILLIS_PER_TICK = 2.5;
14 | private static final int MAX_NANOS_PER_TICK = (int) (MAX_MILLIS_PER_TICK * 1E6);
15 |
16 | private final Deque workloadDeque = new ArrayDeque<>();
17 |
18 | public void addWorkload(ScheduledWorkload workload) {
19 | this.workloadDeque.add(workload);
20 | }
21 |
22 | @Override
23 | public void run() {
24 | long stopTime = System.nanoTime() + MAX_NANOS_PER_TICK;
25 |
26 | ScheduledWorkload lastElement = this.workloadDeque.peekLast();
27 | ScheduledWorkload nextLoad = null;
28 |
29 | // Compute all loads until the time is run out or the queue is empty, or we did one full cycle
30 | // The lastElement is here, so we don't cycle through the queue several times
31 | while (System.nanoTime() <= stopTime && !this.workloadDeque.isEmpty() && nextLoad != lastElement) {
32 | nextLoad = this.workloadDeque.poll();
33 | nextLoad.compute();
34 | if (nextLoad.shouldBeRescheduled()) {
35 | this.addWorkload(nextLoad);
36 | }
37 | }
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/selflimiting/chunkloaded/ChunkWorkload.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.selflimiting.chunkloaded;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledWorkload;
4 | import org.bukkit.Bukkit;
5 | import org.bukkit.Chunk;
6 | import org.bukkit.World;
7 |
8 | import java.util.UUID;
9 | import java.util.function.Consumer;
10 |
11 | /**
12 | * Recommendation: Look at FixedCountRunnable and PlayerWorkload first.
13 | *
14 | * This workload runs an action every 20 compute calls (about once a second)
15 | * and stops itself as soon as the chunk or the world is unloaded.
16 | */
17 | public class ChunkWorkload implements ScheduledWorkload {
18 |
19 | private final UUID worldID;
20 | private final int x;
21 | private final int z;
22 | private final Consumer action;
23 | private boolean chunkUnLoaded;
24 | private int ticksAlive;
25 |
26 | public ChunkWorkload(Chunk chunk, Consumer action) {
27 | this.worldID = chunk.getWorld().getUID();
28 | this.x = chunk.getX();
29 | this.z = chunk.getZ();
30 | this.action = action;
31 | }
32 |
33 | @Override
34 | public void compute() {
35 | World world = Bukkit.getWorld(this.worldID);
36 | if (world == null) {
37 | this.chunkUnLoaded = true;
38 | return;
39 | }
40 | if (world.isChunkLoaded(this.x, this.z)) {
41 | this.chunkUnLoaded = true;
42 | return;
43 | }
44 | if (this.ticksAlive++ % 20 == 0) {
45 | return;
46 | }
47 | this.action.accept(world.getChunkAt(this.x, this.z));
48 | }
49 |
50 | @Override
51 | public boolean shouldBeRescheduled() {
52 | return this.chunkUnLoaded;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/selflimiting/chunkloaded/ExampleChunkListener.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.selflimiting.chunkloaded;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledWorkloadRunnable;
4 | import lombok.AllArgsConstructor;
5 | import org.bukkit.Chunk;
6 | import org.bukkit.block.BlockState;
7 | import org.bukkit.event.EventHandler;
8 | import org.bukkit.event.Listener;
9 | import org.bukkit.event.world.ChunkLoadEvent;
10 |
11 | /**
12 | * As soon as a chunk is loaded, every BlockState in it is
13 | * ticked every second.
14 | */
15 | @AllArgsConstructor
16 | public class ExampleChunkListener implements Listener {
17 |
18 | private final ScheduledWorkloadRunnable workloadRunnable;
19 |
20 | @EventHandler
21 | public void onChunkLoad(ChunkLoadEvent event) {
22 | ChunkWorkload workload = new ChunkWorkload(event.getChunk(), this::tickChunk);
23 | this.workloadRunnable.addWorkload(workload);
24 | }
25 |
26 | private void tickChunk(Chunk chunk) {
27 | for (BlockState blockState : chunk.getTileEntities()) {
28 | this.checkBlockState(blockState);
29 | }
30 | }
31 |
32 | private void checkBlockState(BlockState blockState) {
33 | // Compute something with each BlockState
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/selflimiting/complexloads/abstraction/AbstractWorkload.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.selflimiting.complexloads.abstraction;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledWorkload;
4 |
5 | /**
6 | * This is a bit of a stretch of the whole concept and can safely be ignored.
7 | * Just a demonstration on how volatile you could implement workloads.
8 | */
9 | public abstract class AbstractWorkload implements ScheduledWorkload {
10 |
11 | private boolean invalidElement;
12 |
13 | @Override
14 | public void compute() {
15 | T element = this.getElement();
16 | if (this.isInvalid(element)) {
17 | this.invalidElement = true;
18 | return;
19 | }
20 | this.apply(element);
21 | }
22 |
23 | @Override
24 | public boolean shouldBeRescheduled() {
25 | return !this.invalidElement;
26 | }
27 |
28 | protected abstract T getElement();
29 |
30 | protected abstract boolean isInvalid(T element);
31 |
32 | protected abstract void apply(T element);
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/selflimiting/complexloads/abstraction/ExampleAbstractListener.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.selflimiting.complexloads.abstraction;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledWorkloadRunnable;
4 | import lombok.AllArgsConstructor;
5 | import org.bukkit.Material;
6 | import org.bukkit.event.EventHandler;
7 | import org.bukkit.event.Listener;
8 | import org.bukkit.event.block.BlockBreakEvent;
9 |
10 | /**
11 | * When a player breaks a REDSTONE_ORE block -> add 0.1 HP to him each tick
12 | * for 20 ticks or until he is full health.
13 | */
14 | @AllArgsConstructor
15 | public class ExampleAbstractListener implements Listener {
16 |
17 | private final ScheduledWorkloadRunnable workloadRunnable;
18 |
19 | @EventHandler(ignoreCancelled = true)
20 | public void onDamage(BlockBreakEvent event) {
21 | if (event.getBlock().getType() != Material.REDSTONE_ORE) {
22 | return;
23 | }
24 | PlayerHealWorkload workload = new PlayerHealWorkload(event.getPlayer().getUniqueId(), 0.1, 20);
25 | this.workloadRunnable.addWorkload(workload);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/selflimiting/complexloads/abstraction/PlayerHealWorkload.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.selflimiting.complexloads.abstraction;
2 |
3 | import lombok.RequiredArgsConstructor;
4 | import org.bukkit.Bukkit;
5 | import org.bukkit.attribute.Attribute;
6 | import org.bukkit.entity.Player;
7 |
8 | import java.util.UUID;
9 |
10 | /**
11 | * This heals a player "times" times every tick for "healAmount"
12 | */
13 | @RequiredArgsConstructor
14 | public class PlayerHealWorkload extends AbstractWorkload {
15 |
16 | private final UUID playerID;
17 | private final double healAmount;
18 | private final int times;
19 | private int ticksAlive;
20 |
21 | @Override
22 | protected Player getElement() {
23 | return Bukkit.getPlayer(this.playerID);
24 | }
25 |
26 | @Override
27 | protected boolean isInvalid(Player element) {
28 | if (this.ticksAlive++ == this.times) {
29 | return true;
30 | }
31 | return element == null || element.getHealth() == element.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue();
32 | }
33 |
34 | @Override
35 | protected void apply(Player element) {
36 | double current = element.getHealth();
37 | double max = element.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue();
38 | element.setHealth(Math.min(current + this.healAmount, max));
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/selflimiting/complexloads/composition/CompositionWorkload.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.selflimiting.complexloads.composition;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledWorkload;
4 | import lombok.RequiredArgsConstructor;
5 |
6 | import java.util.function.Consumer;
7 | import java.util.function.Predicate;
8 | import java.util.function.Supplier;
9 |
10 | /**
11 | * This is a bit of a stretch of the whole concept and can safely be ignored.
12 | * Just a demonstration on how volatile you could implement workloads.
13 | */
14 | @RequiredArgsConstructor
15 | public class CompositionWorkload implements ScheduledWorkload {
16 |
17 | private final Supplier valueSupplier;
18 | private final Predicate breakupCondition;
19 | private final Consumer valueConsumer;
20 | private boolean conditionFailed;
21 |
22 | @Override
23 | public void compute() {
24 | T element = this.valueSupplier.get();
25 | if (this.breakupCondition.test(element)) {
26 | this.conditionFailed = true;
27 | return;
28 | }
29 | this.valueConsumer.accept(element);
30 | }
31 |
32 | @Override
33 | public boolean shouldBeRescheduled() {
34 | return !this.conditionFailed;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/selflimiting/complexloads/composition/ExampleCompositionListener.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.selflimiting.complexloads.composition;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledWorkloadRunnable;
4 | import lombok.AllArgsConstructor;
5 | import org.bukkit.Bukkit;
6 | import org.bukkit.Location;
7 | import org.bukkit.Particle;
8 | import org.bukkit.entity.Entity;
9 | import org.bukkit.event.EventHandler;
10 | import org.bukkit.event.Listener;
11 | import org.bukkit.event.entity.EntityDamageEvent;
12 |
13 | import java.util.UUID;
14 | import java.util.function.Consumer;
15 | import java.util.function.Predicate;
16 | import java.util.function.Supplier;
17 |
18 | /**
19 | * This will play flame particles on an entities Location when it gets damaged
20 | * until it is dead.
21 | */
22 | @AllArgsConstructor
23 | public class ExampleCompositionListener implements Listener {
24 |
25 | private final ScheduledWorkloadRunnable workloadRunnable;
26 |
27 | @EventHandler
28 | public void onDamage(EntityDamageEvent event) {
29 | UUID entityID = event.getEntity().getUniqueId();
30 | Supplier supplier = () -> Bukkit.getEntity(entityID);
31 | Predicate condition = this::isInvalidEntity;
32 | Consumer action = this::tickEntity;
33 | CompositionWorkload entityRunnable = new CompositionWorkload<>(supplier, condition, action);
34 | this.workloadRunnable.addWorkload(entityRunnable);
35 | }
36 |
37 |
38 | private boolean isInvalidEntity(Entity entity) {
39 | return entity == null || entity.isDead();
40 | }
41 |
42 | private void tickEntity(Entity entity) {
43 | Location location = entity.getLocation();
44 | location.getWorld().spawnParticle(Particle.FLAME, location, 1);
45 | }
46 |
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/selflimiting/fixedcount/ExampleFixedCountListener.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.selflimiting.fixedcount;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledWorkloadRunnable;
4 | import lombok.AllArgsConstructor;
5 | import org.bukkit.Material;
6 | import org.bukkit.event.EventHandler;
7 | import org.bukkit.event.Listener;
8 | import org.bukkit.event.block.BlockBreakEvent;
9 |
10 | @AllArgsConstructor
11 | public class ExampleFixedCountListener implements Listener {
12 |
13 | private final ScheduledWorkloadRunnable workloadRunnable;
14 |
15 | /**
16 | * This just prints out the material in the console 3 times after
17 | * 0.0s delay
18 | * 0.5s delay
19 | * 1.0s delay
20 | * Then it just gets discarded.
21 | */
22 | @EventHandler
23 | public void onDamage(BlockBreakEvent event) {
24 | Material material = event.getBlock().getType();
25 | FixedCountRunnable runnable = new FixedCountRunnable(10, 30, () -> System.out.println(material));
26 | this.workloadRunnable.addWorkload(runnable);
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/selflimiting/fixedcount/FixedCountRunnable.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.selflimiting.fixedcount;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledWorkload;
4 | import lombok.RequiredArgsConstructor;
5 |
6 | /**
7 | * This workload will run "maxTicks" times and only executes
8 | * the internal runnable every "delay" ticks.
9 | * So FixedCountRunnable(5, 60, () -> System.out.println("Hi"))
10 | * will print out "Hi" 20 times while skipping every 4th compute call.
11 | * Comparable to BukkitScheduler#runTaskTimer()
12 | */
13 | @RequiredArgsConstructor
14 | public class FixedCountRunnable implements ScheduledWorkload {
15 |
16 | private final int delay;
17 | private final int maxTicks;
18 | private final Runnable runnable;
19 | private int ticksAlive = 0;
20 |
21 | @Override
22 | public void compute() {
23 | if (this.ticksAlive++ % this.delay == 0) {
24 | this.runnable.run();
25 | }
26 | }
27 |
28 | @Override
29 | public boolean shouldBeRescheduled() {
30 | return this.ticksAlive < this.maxTicks;
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/selflimiting/playeronline/ExamplePlayerListener.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.selflimiting.playeronline;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledWorkloadRunnable;
4 | import lombok.AllArgsConstructor;
5 | import org.bukkit.event.EventHandler;
6 | import org.bukkit.event.Listener;
7 | import org.bukkit.event.player.PlayerJoinEvent;
8 |
9 | @AllArgsConstructor
10 | public class ExamplePlayerListener implements Listener {
11 |
12 | private final ScheduledWorkloadRunnable workloadRunnable;
13 |
14 | /**
15 | * This will literally spam the player every single tick with Hi.
16 | * The good part is that we don't need to remove the Player from
17 | * the ScheduledWorkloadRunnable as the PlayerWorkload is self controlled
18 | * and will just not be rescheduled if the Player is offline.
19 | */
20 | @EventHandler
21 | public void onJoin(PlayerJoinEvent event) {
22 | PlayerWorkload workload = new PlayerWorkload(event.getPlayer().getUniqueId(), player -> player.sendMessage("Hi"));
23 | this.workloadRunnable.addWorkload(workload);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/selflimiting/playeronline/PlayerWorkload.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.selflimiting.playeronline;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.problem.implementation.scheduled.ScheduledWorkload;
4 | import lombok.RequiredArgsConstructor;
5 | import org.bukkit.Bukkit;
6 | import org.bukkit.entity.Player;
7 | import org.bukkit.util.Consumer;
8 |
9 | import java.util.UUID;
10 |
11 | /**
12 | * This runnable will apply an action on a Player as long as he is online.
13 | */
14 | @RequiredArgsConstructor
15 | public class PlayerWorkload implements ScheduledWorkload {
16 |
17 | private final UUID playerID;
18 | private final Consumer action;
19 | private boolean playerOffline;
20 |
21 | @Override
22 | public void compute() {
23 | Player player = Bukkit.getPlayer(this.playerID);
24 | if (player == null) {
25 | this.playerOffline = true;
26 | return;
27 | }
28 | this.action.accept(player);
29 | }
30 |
31 | @Override
32 | public boolean shouldBeRescheduled() {
33 | return !this.playerOffline;
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/simple/FillCommand.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.simple;
2 |
3 | import co.aikar.commands.BaseCommand;
4 | import co.aikar.commands.annotation.CommandAlias;
5 | import co.aikar.commands.annotation.CommandCompletion;
6 | import co.aikar.commands.annotation.Subcommand;
7 | import co.aikar.commands.annotation.Values;
8 | import com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.implementations.distributed.DistributedFiller;
9 | import com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.implementations.distributed.WorkloadRunnable;
10 | import com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.implementations.primitive.FullVolumeFiller;
11 | import lombok.Data;
12 | import lombok.RequiredArgsConstructor;
13 | import org.bukkit.Location;
14 | import org.bukkit.Material;
15 | import org.bukkit.entity.Player;
16 |
17 | import java.util.WeakHashMap;
18 |
19 | /**
20 | * Example command. Don't worry about it.
21 | * Simply used, so you can test out the different implementations.
22 | */
23 | @CommandAlias("fill")
24 | @RequiredArgsConstructor
25 | public class FillCommand extends BaseCommand {
26 |
27 | private final WeakHashMap selectedLocationsMap = new WeakHashMap<>();
28 | private final WorkloadRunnable workloadRunnable;
29 |
30 | @Subcommand("posA")
31 | public void onPosA(Player sender) {
32 | this.selectedLocationsMap.computeIfAbsent(sender, key -> new LocationPair()).setLocA(sender.getLocation());
33 | sender.sendMessage("§eLocation A was set.");
34 | }
35 |
36 | @Subcommand("posB")
37 | public void onPosB(Player sender) {
38 | this.selectedLocationsMap.computeIfAbsent(sender, key -> new LocationPair()).setLocB(sender.getLocation());
39 | sender.sendMessage("§eLocation A was set.");
40 | }
41 |
42 | @Subcommand("instant")
43 | @CommandCompletion("@Material")
44 | public void onInstantFill(Player sender, @Values("@Material") Material material) {
45 | LocationPair locationPair = this.selectedLocationsMap.computeIfAbsent(sender, key -> new LocationPair());
46 | if (locationPair.isInvalid()) {
47 | sender.sendMessage("§cYou need to select two locations which have to be in the same world first.");
48 | return;
49 | }
50 | new FullVolumeFiller().fill(locationPair.locA, locationPair.locB, material);
51 | }
52 |
53 | @Subcommand("distributed")
54 | @CommandCompletion("@Material")
55 | public void onDistributedFill(Player sender, @Values("@Material") Material material) {
56 | LocationPair locationPair = this.selectedLocationsMap.computeIfAbsent(sender, key -> new LocationPair());
57 | if (locationPair.isInvalid()) {
58 | sender.sendMessage("§cYou need to select two locations which have to be in the same world first.");
59 | return;
60 | }
61 | new DistributedFiller(this.workloadRunnable).fill(locationPair.locA, locationPair.locB, material);
62 | }
63 |
64 | @Data
65 | private static class LocationPair {
66 | private Location locA;
67 | private Location locB;
68 |
69 | boolean isInvalid() {
70 | return this.locA == null || this.locB == null || this.locA.getWorld() != this.locB.getWorld();
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/simple/LinearProcessingPlugin.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.simple;
2 |
3 | import co.aikar.commands.BukkitCommandManager;
4 | import com.gestankbratwurst.scheduling.workloaddistribution.DummyPlugin;
5 | import com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.implementations.distributed.WorkloadRunnable;
6 | import org.bukkit.Bukkit;
7 | import org.bukkit.Material;
8 | import org.bukkit.plugin.java.JavaPlugin;
9 |
10 | import java.util.Arrays;
11 | import java.util.List;
12 |
13 | /**
14 | * This is how your JavaPlugin class could look like.
15 | * You have a WorkloadRunnable singleton to which you
16 | * pass your workloads. The command is for you to test
17 | * out the two implementations and can be ignored.
18 | */
19 | public class LinearProcessingPlugin implements DummyPlugin {
20 |
21 | private final WorkloadRunnable workloadRunnable = new WorkloadRunnable();
22 |
23 | @Override
24 | public void onEnable(JavaPlugin host) {
25 | Bukkit.getScheduler().runTaskTimer(host, this.workloadRunnable, 1, 1);
26 | this.registerCommand(host);
27 | }
28 |
29 | @Override
30 | public void onDisable(JavaPlugin host) {
31 |
32 | }
33 |
34 | private void registerCommand(JavaPlugin host) {
35 | BukkitCommandManager commandManager = new BukkitCommandManager(host);
36 | List materials = Arrays.stream(Material.values()).filter(Material::isBlock).map(Enum::toString).toList();
37 | commandManager.getCommandCompletions().registerStaticCompletion("Material", materials);
38 | commandManager.registerCommand(new FillCommand(this.workloadRunnable));
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/simple/problem/description/VolumeFiller.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.description;
2 |
3 | import org.bukkit.Location;
4 | import org.bukkit.Material;
5 |
6 | /**
7 | * We are defining out problem with an interface.
8 | * Our task is it to fill a volume defined by two corners with blocks.
9 | */
10 | public interface VolumeFiller {
11 |
12 | void fill(Location cornerA, Location cornerB, Material material);
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/simple/problem/implementations/distributed/DistributedFiller.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.implementations.distributed;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.description.VolumeFiller;
4 | import com.google.common.base.Preconditions;
5 | import lombok.AllArgsConstructor;
6 | import org.bukkit.Location;
7 | import org.bukkit.Material;
8 | import org.bukkit.World;
9 | import org.bukkit.util.BoundingBox;
10 | import org.bukkit.util.Vector;
11 |
12 | /**
13 | * This implementation does not change a Block instantly.
14 | * It defines a Workload (PlacableBlock in this case) and simply passes it
15 | * to the WorkloadRunnable. We then forget about it because the runnable
16 | * will decide when there is enough CPU time left for this Workload to be
17 | * computed.
18 | */
19 | @AllArgsConstructor
20 | public class DistributedFiller implements VolumeFiller {
21 |
22 | private final WorkloadRunnable workloadRunnable;
23 |
24 | @Override
25 | public void fill(Location cornerA, Location cornerB, Material material) {
26 | Preconditions.checkArgument(cornerA.getWorld() == cornerB.getWorld() && cornerA.getWorld() != null);
27 | BoundingBox box = BoundingBox.of(cornerA.getBlock(), cornerB.getBlock());
28 | Vector max = box.getMax();
29 | Vector min = box.getMin();
30 |
31 | World world = cornerA.getWorld();
32 |
33 | for (int x = min.getBlockX(); x <= max.getBlockX(); x++) {
34 | for (int y = min.getBlockY(); y <= max.getBlockY(); y++) {
35 | for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) {
36 | PlacableBlock placableBlock = new PlacableBlock(world.getUID(), x, y, z, material);
37 | this.workloadRunnable.addWorkload(placableBlock);
38 | }
39 | }
40 | }
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/simple/problem/implementations/distributed/PlacableBlock.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.implementations.distributed;
2 |
3 | import com.google.common.base.Preconditions;
4 | import lombok.AllArgsConstructor;
5 | import org.bukkit.Bukkit;
6 | import org.bukkit.Material;
7 | import org.bukkit.World;
8 |
9 | import java.util.UUID;
10 |
11 | /**
12 | * An arbitrary implementation for Workload that changes
13 | * a single Block to a given Material.
14 | */
15 | @AllArgsConstructor
16 | public class PlacableBlock implements Workload {
17 |
18 | private final UUID worldID;
19 | private final int blockX;
20 | private final int blockY;
21 | private final int blockZ;
22 | private final Material material;
23 |
24 | @Override
25 | public void compute() {
26 | World world = Bukkit.getWorld(this.worldID);
27 | Preconditions.checkState(world != null);
28 | world.getBlockAt(this.blockX, this.blockY, this.blockZ).setType(this.material);
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/simple/problem/implementations/distributed/Workload.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.implementations.distributed;
2 |
3 | public interface Workload {
4 |
5 | void compute();
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/simple/problem/implementations/distributed/WorkloadRunnable.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.implementations.distributed;
2 |
3 | import java.util.ArrayDeque;
4 | import java.util.Deque;
5 |
6 | /**
7 | * This is the basic implementation of a WorkloadRunnable.
8 | * It processes as many Workloads every tick as the given
9 | * field MAX_MILLIS_PER_TICK allows.
10 | */
11 | public class WorkloadRunnable implements Runnable {
12 |
13 | private static final double MAX_MILLIS_PER_TICK = 2.5;
14 | private static final int MAX_NANOS_PER_TICK = (int) (MAX_MILLIS_PER_TICK * 1E6);
15 |
16 | private final Deque workloadDeque = new ArrayDeque<>();
17 |
18 | public void addWorkload(Workload workload) {
19 | this.workloadDeque.add(workload);
20 | }
21 |
22 | @Override
23 | public void run() {
24 | long stopTime = System.nanoTime() + MAX_NANOS_PER_TICK;
25 |
26 | Workload nextLoad;
27 |
28 | // Note: Don't permute the conditions because sometimes the time will be over but the queue will still be polled then.
29 | while (System.nanoTime() <= stopTime && (nextLoad = this.workloadDeque.poll()) != null) {
30 | nextLoad.compute();
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/simple/problem/implementations/primitive/FullVolumeFiller.java:
--------------------------------------------------------------------------------
1 | package com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.implementations.primitive;
2 |
3 | import com.gestankbratwurst.scheduling.workloaddistribution.simple.problem.description.VolumeFiller;
4 | import com.google.common.base.Preconditions;
5 | import org.bukkit.Location;
6 | import org.bukkit.Material;
7 | import org.bukkit.World;
8 | import org.bukkit.util.BoundingBox;
9 | import org.bukkit.util.Vector;
10 |
11 | /**
12 | * This implementation will instantly fill a volume with blocks in a single tick.
13 | */
14 | public class FullVolumeFiller implements VolumeFiller {
15 |
16 | @Override
17 | public void fill(Location cornerA, Location cornerB, Material material) {
18 | Preconditions.checkArgument(cornerA.getWorld() == cornerB.getWorld() && cornerA.getWorld() != null);
19 | BoundingBox box = BoundingBox.of(cornerA.getBlock(), cornerB.getBlock());
20 | Vector max = box.getMax();
21 | Vector min = box.getMin();
22 |
23 | World world = cornerA.getWorld();
24 |
25 | for (int x = min.getBlockX(); x <= max.getBlockX(); x++) {
26 | for (int y = min.getBlockY(); y <= max.getBlockY(); y++) {
27 | for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) {
28 | world.setType(x, y, z, material);
29 | }
30 | }
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/resources/plugin.yml:
--------------------------------------------------------------------------------
1 | name: WorkloadDistribution
2 | version: '${project.version}'
3 | main: com.gestankbratwurst.scheduling.workloaddistribution.WorkloadDistribution
4 | api-version: 1.18
5 | authors: [ Gestankbratwurst ]
6 | description: Example plugin for workload distribution
7 |
--------------------------------------------------------------------------------