diff --git a/build.gradle b/build.gradle index a6ccd0b..2960f59 100644 --- a/build.gradle +++ b/build.gradle @@ -35,11 +35,11 @@ repositories { dependencies { testImplementation 'junit:junit:4.13.2' //compileOnly 'com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT' - compileOnly 'dev.folia:folia-api:1.19.4-R0.1-SNAPSHOT' + compileOnly 'dev.folia:folia-api:1.20.4-R0.1-SNAPSHOT' compileOnly 'me.clip:placeholderapi:2.10.9' implementation 'org.jooq:joor-java-8:0.9.14' implementation 'com.github.froobynooby:nab-configuration:master-SNAPSHOT' //'com.froobworld:nab-configuration:1.0.2' - implementation 'org.bstats:bstats-bukkit:2.2.1' + implementation 'org.bstats:bstats-bukkit:3.0.2' } processResources { diff --git a/src/main/java/com/froobworld/farmcontrol/FarmControl.java b/src/main/java/com/froobworld/farmcontrol/FarmControl.java index dd3449c..23a4dad 100644 --- a/src/main/java/com/froobworld/farmcontrol/FarmControl.java +++ b/src/main/java/com/froobworld/farmcontrol/FarmControl.java @@ -58,7 +58,7 @@ public void onEnable() { registerCommands(); new FcMetrics(this, 9692); - hookManager.getSchedulerHook().runRepeatingTask(RemoveRandomMovementAction::cleanUp, 1200, 1200); // Hack to fix leaking entities + hookManager.getSchedulerHook().runRepeatingTask(() -> RemoveRandomMovementAction.cleanUp(this), 1200, 1200); // Hack to fix leaking entities } public void reload() throws Exception { @@ -77,7 +77,7 @@ public void onDisable() { farmController.unRegister(); farmController.unload(); } - RemoveRandomMovementAction.cleanUp(); + RemoveRandomMovementAction.cleanUp(this); } public FcConfig getFcConfig() { diff --git a/src/main/java/com/froobworld/farmcontrol/HookManager.java b/src/main/java/com/froobworld/farmcontrol/HookManager.java index 8a5b42b..7881c59 100644 --- a/src/main/java/com/froobworld/farmcontrol/HookManager.java +++ b/src/main/java/com/froobworld/farmcontrol/HookManager.java @@ -1,5 +1,8 @@ package com.froobworld.farmcontrol; +import com.froobworld.farmcontrol.hook.entitygetter.BukkitEntityGetterHook; +import com.froobworld.farmcontrol.hook.entitygetter.EntityGetterHook; +import com.froobworld.farmcontrol.hook.entitygetter.RegionisedEntityGetterHook; import com.froobworld.farmcontrol.hook.scheduler.BukkitSchedulerHook; import com.froobworld.farmcontrol.hook.scheduler.RegionisedSchedulerHook; import com.froobworld.farmcontrol.hook.scheduler.SchedulerHook; @@ -12,6 +15,7 @@ public class HookManager { private final FarmControl farmControl; private final TickHook tickHook; private final SchedulerHook schedulerHook; + private final EntityGetterHook entityGetterHook; private MsptTracker msptTracker; public HookManager(FarmControl farmControl) { @@ -28,6 +32,11 @@ public HookManager(FarmControl farmControl) { } else { tickHook = new BukkitTickHook(schedulerHook); } + if (RegionisedEntityGetterHook.isCompatible()) { + entityGetterHook = new RegionisedEntityGetterHook(farmControl); + } else { + entityGetterHook = new BukkitEntityGetterHook(); + } if (tickHook != null) { tickHook.register(farmControl); } @@ -62,4 +71,7 @@ public SchedulerHook getSchedulerHook() { return schedulerHook; } + public EntityGetterHook getEntityGetterHook() { + return entityGetterHook; + } } diff --git a/src/main/java/com/froobworld/farmcontrol/command/StatusCommand.java b/src/main/java/com/froobworld/farmcontrol/command/StatusCommand.java index 82b3780..49a42ab 100644 --- a/src/main/java/com/froobworld/farmcontrol/command/StatusCommand.java +++ b/src/main/java/com/froobworld/farmcontrol/command/StatusCommand.java @@ -2,8 +2,8 @@ import com.froobworld.farmcontrol.FarmControl; import com.froobworld.farmcontrol.controller.FarmController; +import com.froobworld.farmcontrol.controller.entity.SnapshotEntity; import com.froobworld.farmcontrol.data.FcData; -import com.froobworld.farmcontrol.hook.scheduler.ScheduledTask; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.World; @@ -47,27 +47,20 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command AtomicInteger affectedCount = new AtomicInteger(0); Map actionCount = new HashMap<>(); - CompletableFuture completableFuture = CompletableFuture.completedFuture(null); - for (Entity entity : world.getEntitiesByClasses(FarmController.ENTITY_CLASSES)) { - entityCount.incrementAndGet(); - CompletableFuture entityFuture = new CompletableFuture(); - ScheduledTask scheduledTask = farmControl.getHookManager().getSchedulerHook().runEntityTaskAsap(() -> { - try { - FcData fcData = FcData.get(entity); - if (fcData != null) { - affectedCount.incrementAndGet(); - for (String action : fcData.getActions()) { - actionCount.computeIfAbsent(action, a -> new AtomicInteger(0)).incrementAndGet(); - } + final CompletableFuture completableFuture = new CompletableFuture<>(); + farmControl.getHookManager().getEntityGetterHook().getSnapshotEntities(world, FarmController.ENTITY_CLASSES).thenAccept(entities -> { + entityCount.set(entities.size()); + for (SnapshotEntity entity : entities) { + FcData fcData = entity.getFcData(); + if (fcData != null && !fcData.getActions().isEmpty()) { + affectedCount.incrementAndGet(); + for (String action : fcData.getActions()) { + actionCount.computeIfAbsent(action, a -> new AtomicInteger(0)).incrementAndGet(); } - } finally { - entityFuture.complete(null); } - }, () -> entityFuture.complete(null), entity); - if (scheduledTask != null) { - completableFuture = completableFuture.thenCompose(v -> entityFuture); } - } + completableFuture.complete(null); + }); completableFuture.thenRunAsync(() -> { sender.sendMessage(ChatColor.GRAY + "Status for world '" + ChatColor.RED + world.getName() + ChatColor.GRAY + "'"); sender.sendMessage(""); diff --git a/src/main/java/com/froobworld/farmcontrol/controller/FarmController.java b/src/main/java/com/froobworld/farmcontrol/controller/FarmController.java index e006485..ecfbafd 100644 --- a/src/main/java/com/froobworld/farmcontrol/controller/FarmController.java +++ b/src/main/java/com/froobworld/farmcontrol/controller/FarmController.java @@ -13,11 +13,8 @@ import org.bukkit.World; import org.bukkit.entity.*; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.List; +import java.util.*; +import java.util.concurrent.CompletableFuture; public class FarmController { public static final Class[] ENTITY_CLASSES = List.of(Mob.class, Vehicle.class, Projectile.class, Item.class).toArray(new Class[0]); @@ -88,11 +85,13 @@ public void addWorld(World world) { public void removeWorld(World world) { worldTriggerProfilesMap.remove(world); - for (Entity entity : world.getEntities()) { - farmControl.getHookManager().getSchedulerHook().runEntityTaskAsap( - () -> Actioner.undoAllActions(entity, farmControl), - null, entity); - } + farmControl.getHookManager().getEntityGetterHook().getEntities(world).thenAccept(entities -> { + for (Entity entity : entities) { + farmControl.getHookManager().getSchedulerHook().runEntityTaskAsap( + () -> Actioner.undoAllActions(entity, farmControl), + null, entity); + } + }); } public void register() { diff --git a/src/main/java/com/froobworld/farmcontrol/controller/action/RemoveRandomMovementAction.java b/src/main/java/com/froobworld/farmcontrol/controller/action/RemoveRandomMovementAction.java index db50cd2..a422e26 100644 --- a/src/main/java/com/froobworld/farmcontrol/controller/action/RemoveRandomMovementAction.java +++ b/src/main/java/com/froobworld/farmcontrol/controller/action/RemoveRandomMovementAction.java @@ -1,5 +1,6 @@ package com.froobworld.farmcontrol.controller.action; +import com.froobworld.farmcontrol.FarmControl; import com.froobworld.farmcontrol.utils.NmsUtils; import com.google.common.collect.MapMaker; import com.google.common.collect.Sets; @@ -25,8 +26,15 @@ public class RemoveRandomMovementAction extends Action { } } - public static void cleanUp() { - entityRemovedGoalsMap.entrySet().removeIf(entry -> !entry.getKey().isValid()); + public static void cleanUp(FarmControl farmControl) { + List mobs = new ArrayList<>(entityRemovedGoalsMap.keySet()); + for (Mob mob : mobs) { + farmControl.getHookManager().getSchedulerHook().runEntityTaskAsap(() -> { + if (!mob.isValid()) { + entityRemovedGoalsMap.remove(mob); + } + }, () -> entityRemovedGoalsMap.remove(mob), mob); + } } public RemoveRandomMovementAction() { diff --git a/src/main/java/com/froobworld/farmcontrol/controller/task/TriggerCheckTask.java b/src/main/java/com/froobworld/farmcontrol/controller/task/TriggerCheckTask.java index b5bdd36..2aeda6c 100644 --- a/src/main/java/com/froobworld/farmcontrol/controller/task/TriggerCheckTask.java +++ b/src/main/java/com/froobworld/farmcontrol/controller/task/TriggerCheckTask.java @@ -7,10 +7,8 @@ import com.froobworld.farmcontrol.controller.tracker.CycleTracker; import com.froobworld.farmcontrol.controller.trigger.Trigger; import com.froobworld.farmcontrol.controller.trigger.UntriggerStrategy; -import com.froobworld.farmcontrol.hook.scheduler.ScheduledTask; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.bukkit.World; -import org.bukkit.entity.*; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -59,27 +57,15 @@ public void run() { } untriggerStrategyMap.entrySet().removeIf(entry -> worldLastTriggerCount.get(world).getOrDefault(entry.getKey(), 0) <= entry.getValue().getMinimumCyclesBeforeUndo()); - List snapshotEntities = new ArrayList<>(); - CompletableFuture completableFuture = CompletableFuture.completedFuture(null); + CompletableFuture> completableFuture = CompletableFuture.completedFuture(null); if (!profilesToRun.isEmpty() || !untriggerStrategyMap.isEmpty()) { - for (Entity entity : world.getEntitiesByClasses(FarmController.ENTITY_CLASSES)) { - CompletableFuture entityFuture = new CompletableFuture<>(); - ScheduledTask scheduledTask = farmControl.getHookManager().getSchedulerHook().runEntityTaskAsap(() -> { - try { - SnapshotEntity snapshotEntity = new SnapshotEntity(entity); - synchronized (snapshotEntities) { - snapshotEntities.add(snapshotEntity); - } - } finally { - entityFuture.complete(null); - } - }, () -> entityFuture.complete(null), entity); - if (scheduledTask != null) { - completableFuture = completableFuture.thenCompose(v -> entityFuture); - } - } + completableFuture = farmControl.getHookManager().getEntityGetterHook().getSnapshotEntities(world, FarmController.ENTITY_CLASSES); } - completableFuture.thenRunAsync(() -> { + completableFuture.thenAccept(snapshotEntities -> { + if (snapshotEntities == null || snapshotEntities.isEmpty()) { + cycleTracker.signalCompletion(world); + return; + } if (!profilesToRun.isEmpty()) { executorService.submit(new ActionAllocationTask(farmController, world, farmControl.getHookManager().getSchedulerHook(), triggeredTriggers, snapshotEntities, profilesToRun, farmControl.getExclusionManager().getExclusionPredicate(world), farmControl.getActionManager().getActions(), cycleTracker)); } else { diff --git a/src/main/java/com/froobworld/farmcontrol/hook/entitygetter/BukkitEntityGetterHook.java b/src/main/java/com/froobworld/farmcontrol/hook/entitygetter/BukkitEntityGetterHook.java new file mode 100644 index 0000000..6d8613f --- /dev/null +++ b/src/main/java/com/froobworld/farmcontrol/hook/entitygetter/BukkitEntityGetterHook.java @@ -0,0 +1,41 @@ +package com.froobworld.farmcontrol.hook.entitygetter; + +import com.froobworld.farmcontrol.controller.entity.SnapshotEntity; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.entity.Entity; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class BukkitEntityGetterHook implements EntityGetterHook { + @Override + public CompletableFuture> getEntities(World world, Class... classes) { + return CompletableFuture.completedFuture(world.getEntitiesByClasses(classes)); + } + + @Override + public CompletableFuture> getEntities(World world) { + return CompletableFuture.completedFuture(world.getEntities()); + } + + @Override + public CompletableFuture> getSnapshotEntities(World world, Class... classes) { + List snapshotEntities = new ArrayList<>(); + for (Entity entity : world.getEntitiesByClasses(classes)) { + snapshotEntities.add(new SnapshotEntity(entity)); + } + return CompletableFuture.completedFuture(snapshotEntities); + } + + @Override + public CompletableFuture> getSnapshotEntities(World world) { + List snapshotEntities = new ArrayList<>(); + for (Entity entity : world.getEntities()) { + snapshotEntities.add(new SnapshotEntity(entity)); + } + return CompletableFuture.completedFuture(snapshotEntities); + } +} diff --git a/src/main/java/com/froobworld/farmcontrol/hook/entitygetter/EntityGetterHook.java b/src/main/java/com/froobworld/farmcontrol/hook/entitygetter/EntityGetterHook.java new file mode 100644 index 0000000..58a0887 --- /dev/null +++ b/src/main/java/com/froobworld/farmcontrol/hook/entitygetter/EntityGetterHook.java @@ -0,0 +1,21 @@ +package com.froobworld.farmcontrol.hook.entitygetter; + +import com.froobworld.farmcontrol.controller.entity.SnapshotEntity; +import org.bukkit.World; +import org.bukkit.entity.Entity; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public interface EntityGetterHook { + + CompletableFuture> getEntities(World world, Class... classes); + + CompletableFuture> getEntities(World world); + + CompletableFuture> getSnapshotEntities(World world, Class... classes); + + CompletableFuture> getSnapshotEntities(World world); + +} diff --git a/src/main/java/com/froobworld/farmcontrol/hook/entitygetter/RegionisedEntityGetterHook.java b/src/main/java/com/froobworld/farmcontrol/hook/entitygetter/RegionisedEntityGetterHook.java new file mode 100644 index 0000000..48c2fc8 --- /dev/null +++ b/src/main/java/com/froobworld/farmcontrol/hook/entitygetter/RegionisedEntityGetterHook.java @@ -0,0 +1,88 @@ +package com.froobworld.farmcontrol.hook.entitygetter; + +import com.froobworld.farmcontrol.FarmControl; +import com.froobworld.farmcontrol.controller.entity.SnapshotEntity; +import com.froobworld.farmcontrol.hook.scheduler.RegionisedSchedulerHook; +import com.google.common.collect.Sets; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.entity.Entity; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.Function; + +public class RegionisedEntityGetterHook implements EntityGetterHook { + private final FarmControl farmControl; + + public RegionisedEntityGetterHook(FarmControl farmControl) { + this.farmControl = farmControl; + } + + private CompletableFuture> getMappedEntities(World world, Function mapper, Class... entityClasses) { + MappedEntityCollector entityCollector = new MappedEntityCollector<>(); + CompletableFuture future = CompletableFuture.completedFuture(null); + for (Chunk chunk : world.getLoadedChunks()) { + CompletableFuture chunkFuture = new CompletableFuture<>(); + Bukkit.getRegionScheduler().execute(farmControl, world, chunk.getX(), chunk.getZ(), () -> { + for (Entity entity : chunk.getEntities()) { + boolean eligible = entityClasses.length == 0; + for (Class entityClass : entityClasses) { + if (entityClass.isAssignableFrom(entity.getClass())) { + eligible = true; + break; + } + } + if (eligible) { + entityCollector.addEntity(entity, mapper.apply(entity)); + } + } + chunkFuture.complete(null); + }); + future = future.thenCompose(v -> chunkFuture); + } + return future.thenApply(v -> entityCollector.getEntities()); + } + + @Override + public CompletableFuture> getEntities(World world, Class... entityClasses) { + return getMappedEntities(world, Function.identity(), entityClasses); + } + + @Override + public CompletableFuture> getEntities(World world) { + return getMappedEntities(world, Function.identity()); + } + + @Override + public CompletableFuture> getSnapshotEntities(World world, Class... classes) { + return getMappedEntities(world, SnapshotEntity::new, classes).thenApply(ArrayList::new); + } + + @Override + public CompletableFuture> getSnapshotEntities(World world) { + return getMappedEntities(world, SnapshotEntity::new).thenApply(ArrayList::new); + } + + public static boolean isCompatible() { + return RegionisedSchedulerHook.isCompatible(); + } + + private static class MappedEntityCollector { + private final Set seenEntities = Sets.newConcurrentHashSet(); + private final Collection mappedEntities = new ConcurrentLinkedQueue<>(); + + public void addEntity(Entity entity, T mappedEntity) { + if (seenEntities.add(entity.getUniqueId())) { + mappedEntities.add(mappedEntity); + } + } + + public Collection getEntities() { + return mappedEntities; + } + } + +} diff --git a/src/main/java/com/froobworld/farmcontrol/hook/scheduler/BukkitSchedulerHook.java b/src/main/java/com/froobworld/farmcontrol/hook/scheduler/BukkitSchedulerHook.java index 35d3385..5765758 100644 --- a/src/main/java/com/froobworld/farmcontrol/hook/scheduler/BukkitSchedulerHook.java +++ b/src/main/java/com/froobworld/farmcontrol/hook/scheduler/BukkitSchedulerHook.java @@ -16,6 +16,14 @@ public ScheduledTask runTask(Runnable runnable) { return new BukkitScheduledTask(Bukkit.getScheduler().runTask(farmControl, runnable).getTaskId()); } + @Override + public ScheduledTask runTaskAsap(Runnable runnable) { + if (Bukkit.isPrimaryThread()) { + runnable.run(); + } + return runTask(runnable); + } + @Override public ScheduledTask runRepeatingTask(Runnable runnable, long initDelay, long period) { return new BukkitScheduledTask(Bukkit.getScheduler().scheduleSyncRepeatingTask(farmControl, runnable, initDelay, period)); diff --git a/src/main/java/com/froobworld/farmcontrol/hook/scheduler/RegionisedSchedulerHook.java b/src/main/java/com/froobworld/farmcontrol/hook/scheduler/RegionisedSchedulerHook.java index 2e806d0..4702c78 100644 --- a/src/main/java/com/froobworld/farmcontrol/hook/scheduler/RegionisedSchedulerHook.java +++ b/src/main/java/com/froobworld/farmcontrol/hook/scheduler/RegionisedSchedulerHook.java @@ -16,6 +16,14 @@ public ScheduledTask runTask(Runnable runnable) { return new RegionisedScheduledTask(Bukkit.getGlobalRegionScheduler().run(farmControl, task -> runnable.run())); } + @Override + public ScheduledTask runTaskAsap(Runnable runnable) { + if (Bukkit.isGlobalTickThread()) { + runnable.run(); + } + return runTask(runnable); + } + @Override public ScheduledTask runRepeatingTask(Runnable runnable, long initDelay, long period) { return new RegionisedScheduledTask(Bukkit.getGlobalRegionScheduler().runAtFixedRate(farmControl, task -> runnable.run(), initDelay, period)); diff --git a/src/main/java/com/froobworld/farmcontrol/hook/scheduler/SchedulerHook.java b/src/main/java/com/froobworld/farmcontrol/hook/scheduler/SchedulerHook.java index 0087406..06a8c11 100644 --- a/src/main/java/com/froobworld/farmcontrol/hook/scheduler/SchedulerHook.java +++ b/src/main/java/com/froobworld/farmcontrol/hook/scheduler/SchedulerHook.java @@ -6,6 +6,8 @@ public interface SchedulerHook { ScheduledTask runTask(Runnable runnable); + ScheduledTask runTaskAsap(Runnable runnable); + ScheduledTask runRepeatingTask(Runnable runnable, long initDelay, long period); ScheduledTask runEntityTask(Runnable runnable, Runnable retired, Entity entity);