├── .gitignore ├── README.md ├── pom.xml ├── readmesrc ├── dist_fill.gif └── instant_fill.gif └── src └── main ├── java └── com │ └── gestankbratwurst │ └── scheduling │ └── workloaddistribution │ ├── DummyPlugin.java │ ├── WorkloadDistribution.java │ ├── additions │ └── AdeptScheduledWorkloadRunnable.java │ ├── evendistribution │ └── EvenDistributionPlugin.java │ ├── looped │ ├── LoopedProcessingPlugin.java │ ├── ParticlesCommand.java │ └── problem │ │ └── implementation │ │ ├── CuboidLocationFetcher.java │ │ ├── distributed │ │ └── ParticleSpawnPoint.java │ │ └── scheduled │ │ ├── ScheduledParticleSpawnPoint.java │ │ ├── ScheduledWorkload.java │ │ └── ScheduledWorkloadRunnable.java │ ├── selflimiting │ ├── chunkloaded │ │ ├── ChunkWorkload.java │ │ └── ExampleChunkListener.java │ ├── complexloads │ │ ├── abstraction │ │ │ ├── AbstractWorkload.java │ │ │ ├── ExampleAbstractListener.java │ │ │ └── PlayerHealWorkload.java │ │ └── composition │ │ │ ├── CompositionWorkload.java │ │ │ └── ExampleCompositionListener.java │ ├── fixedcount │ │ ├── ExampleFixedCountListener.java │ │ └── FixedCountRunnable.java │ └── playeronline │ │ ├── ExamplePlayerListener.java │ │ └── PlayerWorkload.java │ └── simple │ ├── FillCommand.java │ ├── LinearProcessingPlugin.java │ └── problem │ ├── description │ └── VolumeFiller.java │ └── implementations │ ├── distributed │ ├── DistributedFiller.java │ ├── PlacableBlock.java │ ├── Workload.java │ └── WorkloadRunnable.java │ └── primitive │ └── FullVolumeFiller.java └── resources └── plugin.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | 11 | # Compiled class file 12 | *.class 13 | 14 | # Log file 15 | *.log 16 | 17 | # BlueJ files 18 | *.ctxt 19 | 20 | # Package Files # 21 | *.jar 22 | *.war 23 | *.nar 24 | *.ear 25 | *.zip 26 | *.tar.gz 27 | *.rar 28 | 29 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 30 | hs_err_pid* 31 | 32 | *~ 33 | 34 | # temporary files which can be created if a process still has a handle open of a deleted file 35 | .fuse_hidden* 36 | 37 | # KDE directory preferences 38 | .directory 39 | 40 | # Linux trash folder which might appear on any partition or disk 41 | .Trash-* 42 | 43 | # .nfs files are created when an open file is removed but is still being accessed 44 | .nfs* 45 | 46 | # General 47 | .DS_Store 48 | .AppleDouble 49 | .LSOverride 50 | 51 | # Icon must end with two \r 52 | Icon 53 | 54 | # Thumbnails 55 | ._* 56 | 57 | # Files that might appear in the root of a volume 58 | .DocumentRevisions-V100 59 | .fseventsd 60 | .Spotlight-V100 61 | .TemporaryItems 62 | .Trashes 63 | .VolumeIcon.icns 64 | .com.apple.timemachine.donotpresent 65 | 66 | # Directories potentially created on remote AFP share 67 | .AppleDB 68 | .AppleDesktop 69 | Network Trash Folder 70 | Temporary Items 71 | .apdisk 72 | 73 | # Windows thumbnail cache files 74 | Thumbs.db 75 | Thumbs.db:encryptable 76 | ehthumbs.db 77 | ehthumbs_vista.db 78 | 79 | # Dump file 80 | *.stackdump 81 | 82 | # Folder config file 83 | [Dd]esktop.ini 84 | 85 | # Recycle Bin used on file shares 86 | $RECYCLE.BIN/ 87 | 88 | # Windows Installer files 89 | *.cab 90 | *.msi 91 | *.msix 92 | *.msm 93 | *.msp 94 | 95 | # Windows shortcuts 96 | *.lnk 97 | 98 | target/ 99 | 100 | pom.xml.tag 101 | pom.xml.releaseBackup 102 | pom.xml.versionsBackup 103 | pom.xml.next 104 | 105 | release.properties 106 | dependency-reduced-pom.xml 107 | buildNumber.properties 108 | .mvn/timing.properties 109 | .mvn/wrapper/maven-wrapper.jar 110 | .flattened-pom.xml 111 | 112 | # Common working directory 113 | run/ 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WorkloadDistribution 2 | 3 | This repository provides extensiv example implementations for distributed workloads in Spigot. 4 | More can be read in the forum thread: 5 | https://www.spigotmc.org/threads/guide-on-workload-distribution-or-how-to-handle-heavy-splittable-tasks.409003/ 6 | 7 | Order for the packages should be 8 | * simple 9 | * looped 10 | * selflimiting 11 | * evendistribution 12 | 13 | ## From an expensive single blocking operation 14 | ![](https://github.com/Flo0/WorkloadDistribution/blob/master/readmesrc/instant_fill.gif) 15 | 16 | 17 | ## To a distributed async workload 18 | ![](https://github.com/Flo0/WorkloadDistribution/blob/master/readmesrc/dist_fill.gif) 19 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.gestankbratwurst.scheduling 8 | WorkloadDistribution 9 | 1.0.0-SNAPSHOT 10 | jar 11 | 12 | WorkloadDistribution 13 | 14 | Example plugin for workload distribution 15 | 16 | 17 17 | UTF-8 18 | 19 | 20 | 21 | 22 | 23 | org.apache.maven.plugins 24 | maven-compiler-plugin 25 | 3.8.1 26 | 27 | ${java.version} 28 | ${java.version} 29 | 30 | 31 | 32 | org.apache.maven.plugins 33 | maven-shade-plugin 34 | 3.2.4 35 | 36 | 37 | package 38 | 39 | shade 40 | 41 | 42 | false 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | src/main/resources 51 | true 52 | 53 | 54 | 55 | 56 | 57 | 58 | spigotmc-repo 59 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 60 | 61 | 62 | sonatype 63 | https://oss.sonatype.org/content/groups/public/ 64 | 65 | 66 | aikar 67 | https://repo.aikar.co/content/groups/aikar/ 68 | 69 | 70 | 71 | 72 | 73 | org.spigotmc 74 | spigot-api 75 | 1.18.1-R0.1-SNAPSHOT 76 | provided 77 | 78 | 79 | org.projectlombok 80 | lombok 81 | 1.18.22 82 | provided 83 | 84 | 85 | co.aikar 86 | acf-bukkit 87 | 0.5.1-SNAPSHOT 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /readmesrc/dist_fill.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flo0/WorkloadDistribution/d972e3e2cf1fb7e51aad16777e9701422e4fa7cf/readmesrc/dist_fill.gif -------------------------------------------------------------------------------- /readmesrc/instant_fill.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flo0/WorkloadDistribution/d972e3e2cf1fb7e51aad16777e9701422e4fa7cf/readmesrc/instant_fill.gif -------------------------------------------------------------------------------- /src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/DummyPlugin.java: -------------------------------------------------------------------------------- 1 | package com.gestankbratwurst.scheduling.workloaddistribution; 2 | 3 | import org.bukkit.plugin.java.JavaPlugin; 4 | 5 | /** 6 | * Interface to mimic a JavaPlugin. Look into each 7 | * package for an implementation which represents 8 | * a real JavaPlugin. 9 | */ 10 | public interface DummyPlugin { 11 | 12 | void onEnable(JavaPlugin host); 13 | 14 | void onDisable(JavaPlugin host); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/gestankbratwurst/scheduling/workloaddistribution/WorkloadDistribution.java: -------------------------------------------------------------------------------- 1 | package com.gestankbratwurst.scheduling.workloaddistribution; 2 | 3 | import com.gestankbratwurst.scheduling.workloaddistribution.looped.LoopedProcessingPlugin; 4 | import com.gestankbratwurst.scheduling.workloaddistribution.simple.LinearProcessingPlugin; 5 | import org.bukkit.plugin.java.JavaPlugin; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Host JavaPlugin. Look at each package to see implementations 12 | * of DummyPlugin which each represents its own plugin. 13 | * This class can safely be ignored. 14 | */ 15 | public final class WorkloadDistribution extends JavaPlugin { 16 | 17 | private final List 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 | --------------------------------------------------------------------------------