From 3b98b20f7359bf1041fca848ce41c655b52ddd78 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 7 Aug 2024 13:40:17 +0200 Subject: [PATCH] implement engine services and other improvements --- .../iris/core/commands/CommandDeveloper.java | 83 ++--- .../com/volmit/iris/core/nms/IHeadless.java | 2 +- .../methods/HeadlessPregenMethod.java | 5 +- .../iris/core/service/IrisCleanerSVC.java | 318 ------------------ .../iris/core/tools/IrisPackBenchmarking.java | 10 +- .../com/volmit/iris/engine/IrisComplex.java | 5 +- .../com/volmit/iris/engine/IrisEngine.java | 69 +++- .../volmit/iris/engine/framework/Engine.java | 3 +- .../iris/engine/framework/EngineEffects.java | 25 -- .../iris/engine/object/IrisEngineService.java | 19 ++ .../volmit/iris/engine/object/IrisWorld.java | 6 + .../EngineEffectsSVC.java} | 47 +-- .../iris/engine/service/EngineStatusSVC.java | 62 ++++ .../iris/engine/service/MantleCleanerSVC.java | 123 +++++++ .../com/volmit/iris/util/mantle/Mantle.java | 105 +++--- .../iris/core/nms/v1_20_R3/Headless.java | 112 +++--- 16 files changed, 428 insertions(+), 566 deletions(-) delete mode 100644 core/src/main/java/com/volmit/iris/core/service/IrisCleanerSVC.java delete mode 100644 core/src/main/java/com/volmit/iris/engine/framework/EngineEffects.java create mode 100644 core/src/main/java/com/volmit/iris/engine/object/IrisEngineService.java rename core/src/main/java/com/volmit/iris/engine/{IrisEngineEffects.java => service/EngineEffectsSVC.java} (53%) create mode 100644 core/src/main/java/com/volmit/iris/engine/service/EngineStatusSVC.java create mode 100644 core/src/main/java/com/volmit/iris/engine/service/MantleCleanerSVC.java diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java index 43c1eaff4..e9beecda5 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java @@ -20,12 +20,12 @@ package com.volmit.iris.core.commands; import com.volmit.iris.Iris; import com.volmit.iris.core.loader.IrisData; -import com.volmit.iris.core.service.IrisCleanerSVC; import com.volmit.iris.core.tools.IrisPackBenchmarking; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.core.tools.IrisWorldDump; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.engine.service.EngineStatusSVC; import com.volmit.iris.util.decree.DecreeExecutor; import com.volmit.iris.util.decree.DecreeOrigin; import com.volmit.iris.util.decree.annotations.Decree; @@ -50,8 +50,6 @@ import java.io.*; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.*; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -60,55 +58,22 @@ public class CommandDeveloper implements DecreeExecutor { private CommandTurboPregen turboPregen; private CommandUpdater updater; - @Decree(description = "Get Loaded TectonicPlates Count", origin = DecreeOrigin.BOTH, sync = true) + @Decree(description = "Get Loaded TectonicPlates Count", origin = DecreeOrigin.BOTH, aliases = "status", sync = true) public void EngineStatus() { - List IrisWorlds = new ArrayList<>(); - int TotalLoadedChunks = 0; - int TotalQueuedTectonicPlates = 0; - int TotalNotQueuedTectonicPlates = 0; - int TotalTectonicPlates = 0; + var status = EngineStatusSVC.getStatus(); - long lowestUnloadDuration = 0; - long highestUnloadDuration = 0; - - for (World world : Bukkit.getWorlds()) { - try { - if (IrisToolbelt.access(world).getEngine() != null) { - IrisWorlds.add(world); - } - } catch (Exception e) { - // no - } - } - - for (World world : IrisWorlds) { - Engine engine = IrisToolbelt.access(world).getEngine(); - TotalQueuedTectonicPlates += (int) engine.getMantle().getToUnload(); - TotalNotQueuedTectonicPlates += (int) engine.getMantle().getNotQueuedLoadedRegions(); - TotalTectonicPlates += engine.getMantle().getLoadedRegionCount(); - if (highestUnloadDuration <= (long) engine.getMantle().getTectonicDuration()) { - highestUnloadDuration = (long) engine.getMantle().getTectonicDuration(); - } - if (lowestUnloadDuration >= (long) engine.getMantle().getTectonicDuration()) { - lowestUnloadDuration = (long) engine.getMantle().getTectonicDuration(); - } - for (Chunk chunk : world.getLoadedChunks()) { - if (chunk.isLoaded()) { - TotalLoadedChunks++; - } - } - } - Iris.info("-------------------------"); - Iris.info(C.DARK_PURPLE + "Engine Status"); - Iris.info(C.DARK_PURPLE + "Total Loaded Chunks: " + C.LIGHT_PURPLE + TotalLoadedChunks); - Iris.info(C.DARK_PURPLE + "Tectonic Limit: " + C.LIGHT_PURPLE + IrisCleanerSVC.getTectonicLimit()); - Iris.info(C.DARK_PURPLE + "Tectonic Total Plates: " + C.LIGHT_PURPLE + TotalTectonicPlates); - Iris.info(C.DARK_PURPLE + "Tectonic Active Plates: " + C.LIGHT_PURPLE + TotalNotQueuedTectonicPlates); - Iris.info(C.DARK_PURPLE + "Tectonic ToUnload: " + C.LIGHT_PURPLE + TotalQueuedTectonicPlates); - Iris.info(C.DARK_PURPLE + "Lowest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(lowestUnloadDuration)); - Iris.info(C.DARK_PURPLE + "Highest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(highestUnloadDuration)); - Iris.info(C.DARK_PURPLE + "Cache Size: " + C.LIGHT_PURPLE + Form.f(IrisData.cacheSize())); - Iris.info("-------------------------"); + sender().sendMessage("-------------------------"); + sender().sendMessage(C.DARK_PURPLE + "Engine Status"); + sender().sendMessage(C.DARK_PURPLE + "Total Engines: " + C.LIGHT_PURPLE + status.engineCount()); + sender().sendMessage(C.DARK_PURPLE + "Total Loaded Chunks: " + C.LIGHT_PURPLE + status.loadedChunks()); + sender().sendMessage(C.DARK_PURPLE + "Tectonic Limit: " + C.LIGHT_PURPLE + status.tectonicLimit()); + sender().sendMessage(C.DARK_PURPLE + "Tectonic Total Plates: " + C.LIGHT_PURPLE + status.tectonicPlates()); + sender().sendMessage(C.DARK_PURPLE + "Tectonic Active Plates: " + C.LIGHT_PURPLE + status.activeTectonicPlates()); + sender().sendMessage(C.DARK_PURPLE + "Tectonic ToUnload: " + C.LIGHT_PURPLE + status.queuedTectonicPlates()); + sender().sendMessage(C.DARK_PURPLE + "Lowest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(status.minTectonicUnloadDuration())); + sender().sendMessage(C.DARK_PURPLE + "Highest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(status.maxTectonicUnloadDuration())); + sender().sendMessage(C.DARK_PURPLE + "Cache Size: " + C.LIGHT_PURPLE + Form.f(IrisData.cacheSize())); + sender().sendMessage("-------------------------"); } @Decree(description = "Test") @@ -126,7 +91,7 @@ public class CommandDeveloper implements DecreeExecutor { for (File i : Objects.requireNonNull(tectonicplates.listFiles())) { TectonicPlate.read(maxHeight, i); c++; - Iris.info("Loaded count: " + c ); + sender().sendMessage("Loaded count: " + c ); } @@ -139,12 +104,14 @@ public class CommandDeveloper implements DecreeExecutor { @Param(description = "Headless", defaultValue = "true") boolean headless, @Param(description = "GUI", defaultValue = "false") - boolean gui + boolean gui, + @Param(description = "Diameter in regions", defaultValue = "5") + int diameter ) { - Iris.info("test"); - IrisPackBenchmarking benchmark = new IrisPackBenchmarking(dimension, 1, headless, gui); + int rb = diameter << 9; + Iris.info("Benchmarking pack " + dimension.getName() + " with diameter: " + rb + "(" + diameter + ")"); + IrisPackBenchmarking benchmark = new IrisPackBenchmarking(dimension, diameter, headless, gui); benchmark.runBenchmark(); - } @Decree(description = "test") @@ -235,9 +202,8 @@ public class CommandDeveloper implements DecreeExecutor { Engine engine = IrisToolbelt.access(world).getEngine(); if(engine != null) { int height = engine.getTarget().getHeight(); - ExecutorService service = Executors.newFixedThreadPool(1); VolmitSender sender = sender(); - service.submit(() -> { + new Thread(() -> { try { DataInputStream raw = new DataInputStream(new FileInputStream(file)); TectonicPlate plate = new TectonicPlate(height, raw); @@ -271,8 +237,7 @@ public class CommandDeveloper implements DecreeExecutor { } catch (Throwable e) { e.printStackTrace(); } - }); - service.shutdown(); + }, "Compression Test").start(); } else { Iris.info(C.RED + "Engine is null!"); } diff --git a/core/src/main/java/com/volmit/iris/core/nms/IHeadless.java b/core/src/main/java/com/volmit/iris/core/nms/IHeadless.java index 2b7accdc1..f95d19457 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/IHeadless.java +++ b/core/src/main/java/com/volmit/iris/core/nms/IHeadless.java @@ -9,7 +9,7 @@ import java.io.Closeable; public interface IHeadless extends Closeable { - void save(); + int getLoadedChunks(); @ChunkCoordinates boolean exists(int x, int z); diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java index 6c54c0249..709fb0186 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java @@ -37,7 +37,6 @@ public class HeadlessPregenMethod implements PregeneratorMethod { try { semaphore.acquire(max); } catch (InterruptedException ignored) {} - headless.save(); try { headless.close(); } catch (IOException e) { @@ -47,9 +46,7 @@ public class HeadlessPregenMethod implements PregeneratorMethod { } @Override - public void save() { - headless.save(); - } + public void save() {} @Override public boolean supportsRegions(int x, int z, PregenListener listener) { diff --git a/core/src/main/java/com/volmit/iris/core/service/IrisCleanerSVC.java b/core/src/main/java/com/volmit/iris/core/service/IrisCleanerSVC.java deleted file mode 100644 index 896212115..000000000 --- a/core/src/main/java/com/volmit/iris/core/service/IrisCleanerSVC.java +++ /dev/null @@ -1,318 +0,0 @@ -package com.volmit.iris.core.service; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.engine.platform.PlatformChunkGenerator; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.misc.getHardware; -import com.volmit.iris.util.plugin.IrisService; -import com.volmit.iris.util.scheduling.ChronoLatch; -import com.volmit.iris.util.scheduling.Looper; -import com.volmit.iris.util.scheduling.PrecisionStopwatch; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.event.EventHandler; -import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.server.ServerLoadEvent; -import org.bukkit.event.world.WorldLoadEvent; -import org.bukkit.event.world.WorldUnloadEvent; - -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Supplier; - -public class IrisCleanerSVC implements IrisService { - public static IrisCleanerSVC instance; - public boolean isServerShuttingDown = false; - public boolean isServerLoaded = false; - private static final AtomicInteger tectonicLimit = new AtomicInteger(30); - private ReentrantLock lastUseLock; - private KMap lastUse; - private List IrisWorlds; - private Looper cacheTicker; - private Looper trimTicker; - private Looper unloadTicker; - private Looper updateTicker; - private PrecisionStopwatch trimAlive; - private PrecisionStopwatch unloadAlive; - public PrecisionStopwatch trimActiveAlive; - public PrecisionStopwatch unloadActiveAlive; - private AtomicInteger TotalTectonicPlates; - private AtomicInteger TotalQueuedTectonicPlates; - private AtomicInteger TotalNotQueuedTectonicPlates; - private AtomicInteger failedTrim; - private AtomicInteger failedUnload; - private AtomicInteger failedSupply; - private AtomicBoolean IsUnloadAlive; - private AtomicBoolean IsTrimAlive; - ChronoLatch cl; - - @Override - public void onEnable() { - this.cl = new ChronoLatch(5000); - lastUse = new KMap<>(); - lastUseLock = new ReentrantLock(); - IrisWorlds = new ArrayList<>(); - IsUnloadAlive = new AtomicBoolean(true); - IsTrimAlive = new AtomicBoolean(true); - trimActiveAlive = new PrecisionStopwatch(); - unloadActiveAlive = new PrecisionStopwatch(); - trimAlive = new PrecisionStopwatch(); - unloadAlive = new PrecisionStopwatch(); - TotalTectonicPlates = new AtomicInteger(); - TotalQueuedTectonicPlates = new AtomicInteger(); - TotalNotQueuedTectonicPlates = new AtomicInteger(); - failedTrim = new AtomicInteger(); - failedUnload = new AtomicInteger(); - failedSupply = new AtomicInteger(); - tectonicLimit.set(2); - long t = getHardware.getProcessMemory(); - while (t > 200) { - tectonicLimit.getAndAdd(1); - t = t - 200; - } - this.setup(); - this.TrimLogic(); - this.UnloadLogic(); - - trimAlive.begin(); - unloadAlive.begin(); - trimActiveAlive.begin(); - unloadActiveAlive.begin(); - - updateTicker.start(); - cacheTicker.start(); - //trimTicker.start(); - //unloadTicker.start(); - instance = this; - - } - - public void engineStatus() { - boolean trimAlive = trimTicker.isAlive(); - boolean unloadAlive = unloadTicker.isAlive(); - Iris.info("Status:"); - Iris.info("- Trim: " + trimAlive); - Iris.info("- Unload: " + unloadAlive); - } - - public static int getTectonicLimit() { - return tectonicLimit.get(); - } - - @EventHandler - public void onWorldUnload(WorldUnloadEvent event) { - updateWorlds(); - } - - @EventHandler - public void onWorldLoad(WorldLoadEvent event) { - updateWorlds(); - } - - @EventHandler - public void onServerBoot(ServerLoadEvent event) { - isServerLoaded = true; - } - - @EventHandler - public void onPluginDisable(PluginDisableEvent event) { - if (event.getPlugin().equals(Iris.instance)) { - isServerShuttingDown = true; - } - } - - public void updateWorlds() { - for (World world : Bukkit.getWorlds()) { - try { - if (IrisToolbelt.access(world).getEngine() != null) { - IrisWorlds.add(world); - } - } catch (Exception e) { - // no - } - } - } - - private void setup() { - cacheTicker = new Looper() { - @Override - protected long loop() { - long now = System.currentTimeMillis(); - lastUseLock.lock(); - try { - for (World key : new ArrayList<>(lastUse.keySet())) { - Long last = lastUse.get(key); - if (last == null) - continue; - if (now - last > 60000) { - lastUse.remove(key); - } - } - } finally { - lastUseLock.unlock(); - } - return 1000; - } - }; - - updateTicker = new Looper() { - @Override - protected long loop() { - try { - TotalQueuedTectonicPlates.set(0); - TotalNotQueuedTectonicPlates.set(0); - TotalTectonicPlates.set(0); - for (World world : IrisWorlds) { - Engine engine = Objects.requireNonNull(IrisToolbelt.access(world)).getEngine(); - TotalQueuedTectonicPlates.addAndGet((int) engine.getMantle().getToUnload()); - TotalNotQueuedTectonicPlates.addAndGet((int) engine.getMantle().getNotQueuedLoadedRegions()); - TotalTectonicPlates.addAndGet(engine.getMantle().getLoadedRegionCount()); - } - if (!isServerShuttingDown && isServerLoaded) { - if (!trimTicker.isAlive()) { - Iris.info(C.RED + "TrimTicker found dead! Booting it up!"); - try { - TrimLogic(); - } catch (Exception e) { - Iris.error("What happened?"); - e.printStackTrace(); - } - } - - if (!unloadTicker.isAlive()) { - Iris.info(C.RED + "UnloadTicker found dead! Booting it up!"); - try { - UnloadLogic(); - } catch (Exception e) { - Iris.error("What happened?"); - e.printStackTrace(); - } - } - } - - } catch (Exception e) { - return -1; - } - return 1000; - } - }; - } - public void TrimLogic() { - if (trimTicker == null || !trimTicker.isAlive()) { - trimTicker = new Looper() { - private final Supplier supplier = createSupplier(); - - @Override - protected long loop() { - long start = System.currentTimeMillis(); - trimAlive.reset(); - try { - Engine engine = supplier.get(); - if (engine != null) { - engine.getMantle().trim(tectonicLimit.get() / lastUse.size()); - } - } catch (Throwable e) { - Iris.reportError(e); - Iris.debug(C.RED + "EngineSVC: Failed to trim."); - failedTrim.getAndIncrement(); - e.printStackTrace(); - return -1; - } - - int size = lastUse.size(); - long time = (size > 0 ? 1000 / size : 1000) - (System.currentTimeMillis() - start); - if (time <= 0) - return 0; - return time; - } - }; - trimTicker.start(); - } - } - public void UnloadLogic() { - if (unloadTicker == null || !unloadTicker.isAlive()) { - unloadTicker = new Looper() { - private final Supplier supplier = createSupplier(); - - @Override - protected long loop() { - long start = System.currentTimeMillis(); - unloadAlive.reset(); - try { - Engine engine = supplier.get(); - if (engine != null) { - long unloadStart = System.currentTimeMillis(); - int count = engine.getMantle().unloadTectonicPlate(tectonicLimit.get() / lastUse.size()); - if (count > 0) { - Iris.debug(C.GOLD + "Unloaded " + C.YELLOW + count + " TectonicPlates in " + C.RED + Form.duration(System.currentTimeMillis() - unloadStart, 2)); - } - } - } catch (Throwable e) { - Iris.reportError(e); - Iris.debug(C.RED + "EngineSVC: Failed to unload."); - failedUnload.getAndIncrement(); - e.printStackTrace(); - return -1; - } - - int size = lastUse.size(); - long time = (size > 0 ? 1000 / size : 1000) - (System.currentTimeMillis() - start); - if (time <= 0) - return 0; - return time; - } - }; - unloadTicker.start(); - } - } - - private Supplier createSupplier() { - AtomicInteger i = new AtomicInteger(); - return () -> { - List worlds = Bukkit.getWorlds(); - if (i.get() >= worlds.size()) { - i.set(0); - } - try { - for (int j = 0; j < worlds.size(); j++) { - World world = worlds.get(i.getAndIncrement()); - PlatformChunkGenerator generator = IrisToolbelt.access(world); - if (i.get() >= worlds.size()) { - i.set(0); - } - - if (generator != null) { - Engine engine = generator.getEngine(); - boolean closed = engine.getMantle().getData().isClosed(); - if (engine != null && !engine.isStudio() && !closed) { - lastUseLock.lock(); - lastUse.put(world, System.currentTimeMillis()); - lastUseLock.unlock(); - return engine; - } - } - } - } catch (Throwable e) { - failedSupply.getAndIncrement(); - Iris.debug(C.RED + "EngineSVC: Failed to create supplier."); - e.printStackTrace(); - Iris.reportError(e); - } - return null; - }; - } - - @Override - public void onDisable() { - cacheTicker.interrupt(); - trimTicker.interrupt(); - unloadTicker.interrupt(); - lastUse.clear(); - } -} diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java b/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java index 5e6759ad2..45262e9d6 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java @@ -96,7 +96,7 @@ public class IrisPackBenchmarking { File profilers = new File("plugins" + File.separator + "Iris" + File.separator + "packbenchmarks"); profilers.mkdir(); - File results = new File("plugins" + File.separator + "Iris", IrisDimension.getName() + " " + LocalDateTime.now(Clock.systemDefaultZone()).toString().replace(':', '-') + ".txt"); + File results = new File(profilers, IrisDimension.getName() + " " + LocalDateTime.now(Clock.systemDefaultZone()).toString().replace(':', '-') + ".txt"); results.getParentFile().mkdirs(); KMap metrics = engine.getMetrics().pull(); try (FileWriter writer = new FileWriter(results)) { @@ -124,7 +124,9 @@ public class IrisPackBenchmarking { e.printStackTrace(); } - Bukkit.getServer().unloadWorld("benchmark", true); + if (headless) engine.close(); + else J.s(() -> Bukkit.unloadWorld("benchmark", true)); + stopwatch.end(); } catch (Exception e) { Iris.error("Something has gone wrong!"); @@ -171,8 +173,8 @@ public class IrisPackBenchmarking { .builder() .gui(gui) .center(new Position2(x, z)) - .width(5) - .height(5) + .width(radius) + .height(radius) .build(), headless ? new HeadlessPregenMethod(engine) : new HybridPregenMethod(engine.getWorld().realWorld(), IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())), engine); } diff --git a/core/src/main/java/com/volmit/iris/engine/IrisComplex.java b/core/src/main/java/com/volmit/iris/engine/IrisComplex.java index 908757c90..257849669 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisComplex.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisComplex.java @@ -296,7 +296,8 @@ public class IrisComplex implements DataProvider { var cache = new HashMap(); double hi = interpolator.interpolate(x, z, (xx, zz) -> { try { - IrisBiome bx = cache.computeIfAbsent(new DPair(xx, zz), k -> baseBiomeStream.get(k.x, k.z)); + IrisBiome bx = baseBiomeStream.get(xx, zz); + cache.put(new DPair(xx, zz), bx); double b = 0; for (IrisGenerator gen : generators) { @@ -315,7 +316,7 @@ public class IrisComplex implements DataProvider { double lo = interpolator.interpolate(x, z, (xx, zz) -> { try { - IrisBiome bx = cache.computeIfAbsent(new DPair(xx, zz), k -> baseBiomeStream.get(k.x, k.z)); + IrisBiome bx = cache.get(new DPair(xx, zz)); double b = 0; for (IrisGenerator gen : generators) { diff --git a/core/src/main/java/com/volmit/iris/engine/IrisEngine.java b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java index 706ba24fc..019f932ad 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisEngine.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java @@ -32,6 +32,7 @@ import com.volmit.iris.engine.framework.*; import com.volmit.iris.engine.mantle.EngineMantle; import com.volmit.iris.engine.object.*; import com.volmit.iris.engine.scripting.EngineExecutionEnvironment; +import com.volmit.iris.engine.service.EngineEffectsSVC; import com.volmit.iris.util.atomics.AtomicRollingSequence; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.context.ChunkContext; @@ -41,6 +42,7 @@ import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.hunk.Hunk; import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.io.JarScanner; import com.volmit.iris.util.mantle.MantleFlag; import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.RNG; @@ -62,9 +64,9 @@ import org.bukkit.entity.Player; import java.io.File; import java.io.IOException; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -74,6 +76,8 @@ import java.util.concurrent.locks.ReentrantLock; @EqualsAndHashCode(exclude = "context") @ToString(exclude = "context") public class IrisEngine implements Engine { + private static final Map, Constructor> SERVICES = scanServices(); + private final KMap, IrisEngineService> services; private final AtomicInteger bud; private final AtomicInteger buds; private final AtomicInteger generated; @@ -94,7 +98,6 @@ public class IrisEngine implements Engine { private final ChronoLatch cleanLatch; private final SeedManager seedManager; private EngineMode mode; - private EngineEffects effects; private EngineExecutionEnvironment execution; private EngineWorldManager worldManager; private volatile int parallelism; @@ -114,6 +117,7 @@ public class IrisEngine implements Engine { getEngineData(); verifySeed(); this.seedManager = new SeedManager(target.getWorld().getRawWorldSeed()); + services = new KMap<>(); dataLock = new ReentrantLock(); bud = new AtomicInteger(0); buds = new AtomicInteger(0); @@ -154,17 +158,17 @@ public class IrisEngine implements Engine { bud.set(0); } - if (effects != null) { - effects.tickRandomPlayer(); - } + var effects = getService(EngineEffectsSVC.class); + if (effects != null) effects.tickRandomPlayer(); } private void prehotload() { worldManager.close(); complex.close(); execution.close(); - effects.close(); mode.close(); + services.values().forEach(s -> s.onDisable(true)); + services.values().forEach(Iris.instance::unregisterListener); J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace()); } @@ -173,10 +177,25 @@ public class IrisEngine implements Engine { try { Iris.debug("Setup Engine " + getCacheID()); cacheId = RNG.r.nextInt(); + boolean hotload = true; + if (services.isEmpty()) { + SERVICES.forEach((s, c) -> { + try { + services.put(s, c.newInstance(this)); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + Iris.error("Failed to create service " + s.getName()); + e.printStackTrace(); + } + }); + hotload = false; + } + for (var service : services.values()) { + service.onEnable(hotload); + Iris.instance.registerListener(service); + } worldManager = new IrisWorldManager(this); complex = new IrisComplex(this); execution = new IrisExecutionEnvironment(this); - effects = new IrisEngineEffects(this); setupMode(); J.a(this::computeBiomeMaxes); } catch (Throwable e) { @@ -439,6 +458,12 @@ public class IrisEngine implements Engine { PregeneratorJob.shutdownInstance(); closed = true; J.car(art); + try { + if (getWorld().hasHeadless()) getWorld().headless().close(); + } catch (IOException e) { + Iris.reportError(e); + } + services.values().forEach(s -> s.onDisable(false)); getWorldManager().close(); getTarget().close(); saveEngineData(); @@ -554,6 +579,12 @@ public class IrisEngine implements Engine { return cacheId; } + @Override + @SuppressWarnings("unchecked") + public T getService(Class clazz) { + return (T) services.get(clazz); + } + private boolean EngineSafe() { // Todo: this has potential if done right int EngineMCVersion = getEngineData().getStatistics().getMCVersion(); @@ -568,4 +599,24 @@ public class IrisEngine implements Engine { } return true; } + + @SuppressWarnings("unchecked") + private static Map, Constructor> scanServices() { + JarScanner js = new JarScanner(Iris.instance.getJarFile(), "com.volmit.iris.engine.service"); + J.attempt(js::scan); + KMap, Constructor> map = new KMap<>(); + js.getClasses() + .stream() + .filter(IrisEngineService.class::isAssignableFrom) + .map(c -> (Class) c) + .forEach(c -> { + try { + map.put(c, c.getConstructor(Engine.class)); + } catch (NoSuchMethodException e) { + Iris.warn("Failed to load service " + c.getName() + " due to missing constructor"); + } + }); + + return Collections.unmodifiableMap(map); + } } diff --git a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java index 7bf1356bf..1314056f5 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java @@ -534,8 +534,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat } } - EngineEffects getEffects(); - default MultiBurst burst() { return getTarget().getBurster(); } @@ -960,4 +958,5 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat J.a(() -> getMantle().cleanupChunk(x, z)); } } + T getService(Class clazz); } diff --git a/core/src/main/java/com/volmit/iris/engine/framework/EngineEffects.java b/core/src/main/java/com/volmit/iris/engine/framework/EngineEffects.java deleted file mode 100644 index ff6326985..000000000 --- a/core/src/main/java/com/volmit/iris/engine/framework/EngineEffects.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine.framework; - -public interface EngineEffects extends EngineComponent { - void updatePlayerMap(); - - void tickRandomPlayer(); -} diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisEngineService.java b/core/src/main/java/com/volmit/iris/engine/object/IrisEngineService.java new file mode 100644 index 000000000..81f72d43d --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisEngineService.java @@ -0,0 +1,19 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.Iris; +import com.volmit.iris.engine.framework.Engine; +import lombok.RequiredArgsConstructor; +import org.bukkit.event.Listener; + +@RequiredArgsConstructor +public abstract class IrisEngineService implements Listener { + protected final Engine engine; + + public abstract void onEnable(boolean hotload); + + public abstract void onDisable(boolean hotload); + + public final void postShutdown(Runnable r) { + Iris.instance.postShutdown(r); + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisWorld.java b/core/src/main/java/com/volmit/iris/engine/object/IrisWorld.java index 19c8d348d..f7465e09b 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisWorld.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisWorld.java @@ -19,6 +19,7 @@ package com.volmit.iris.engine.object; import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.IHeadless; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.util.collection.KList; import lombok.*; @@ -48,6 +49,7 @@ public class IrisWorld { private long seed; private World.Environment environment; private World realWorld; + private IHeadless headless; private int minHeight; private int maxHeight; @@ -91,6 +93,10 @@ public class IrisWorld { return realWorld != null; } + public boolean hasHeadless() { + return headless != null; + } + public List getPlayers() { if (hasRealWorld()) { diff --git a/core/src/main/java/com/volmit/iris/engine/IrisEngineEffects.java b/core/src/main/java/com/volmit/iris/engine/service/EngineEffectsSVC.java similarity index 53% rename from core/src/main/java/com/volmit/iris/engine/IrisEngineEffects.java rename to core/src/main/java/com/volmit/iris/engine/service/EngineEffectsSVC.java index 6d0804892..fb83625ba 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisEngineEffects.java +++ b/core/src/main/java/com/volmit/iris/engine/service/EngineEffectsSVC.java @@ -1,27 +1,8 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine; +package com.volmit.iris.engine.service; import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.engine.framework.EngineAssignedComponent; -import com.volmit.iris.engine.framework.EngineEffects; import com.volmit.iris.engine.framework.EnginePlayer; +import com.volmit.iris.engine.object.IrisEngineService; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.math.M; import com.volmit.iris.util.scheduling.PrecisionStopwatch; @@ -31,19 +12,28 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.Semaphore; -public class IrisEngineEffects extends EngineAssignedComponent implements EngineEffects { - private final KMap players; - private final Semaphore limit; +public class EngineEffectsSVC extends IrisEngineService { + private KMap players; + private Semaphore limit; - public IrisEngineEffects(Engine engine) { - super(engine, "FX"); + public EngineEffectsSVC(Engine engine) { + super(engine); + } + + @Override + public void onEnable(boolean hotload) { players = new KMap<>(); limit = new Semaphore(1); } @Override + public void onDisable(boolean hotload) { + players = null; + limit = null; + } + public void updatePlayerMap() { - List pr = getEngine().getWorld().getPlayers(); + List pr = engine.getWorld().getPlayers(); if (pr == null) { return; @@ -52,7 +42,7 @@ public class IrisEngineEffects extends EngineAssignedComponent implements Engine for (Player i : pr) { boolean pcc = players.containsKey(i.getUniqueId()); if (!pcc) { - players.put(i.getUniqueId(), new EnginePlayer(getEngine(), i)); + players.put(i.getUniqueId(), new EnginePlayer(engine, i)); } } @@ -63,7 +53,6 @@ public class IrisEngineEffects extends EngineAssignedComponent implements Engine } } - @Override public void tickRandomPlayer() { if (limit.tryAcquire()) { if (M.r(0.02)) { diff --git a/core/src/main/java/com/volmit/iris/engine/service/EngineStatusSVC.java b/core/src/main/java/com/volmit/iris/engine/service/EngineStatusSVC.java new file mode 100644 index 000000000..96c9882ea --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/service/EngineStatusSVC.java @@ -0,0 +1,62 @@ +package com.volmit.iris.engine.service; + +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.IrisEngineService; +import com.volmit.iris.util.collection.KList; +import lombok.Getter; + +@Getter +public class EngineStatusSVC extends IrisEngineService { + private static final KList INSTANCES = new KList<>(); + + public EngineStatusSVC(Engine engine) { + super(engine); + } + + @Override + public void onEnable(boolean hotload) { + if (hotload) return; + synchronized (INSTANCES) { + INSTANCES.add(this); + } + } + + @Override + public void onDisable(boolean hotload) { + if (hotload) return; + + synchronized (INSTANCES) { + INSTANCES.remove(this); + } + } + + public static int getEngineCount() { + return Math.max(INSTANCES.size(), 1); + } + + public static Status getStatus() { + synchronized (INSTANCES) { + long loadedChunks = 0; + long tectonicPlates = 0; + long activeTectonicPlates = 0; + long queuedTectonicPlates = 0; + long minTectonicUnloadDuration = Long.MAX_VALUE; + long maxTectonicUnloadDuration = Long.MIN_VALUE; + + for (var service : INSTANCES) { + var world = service.engine.getWorld(); + if (world.hasRealWorld()) loadedChunks += world.realWorld().getLoadedChunks().length; + if (world.hasHeadless()) loadedChunks += world.headless().getLoadedChunks(); + + tectonicPlates += service.engine.getMantle().getLoadedRegionCount(); + activeTectonicPlates += service.engine.getMantle().getNotQueuedLoadedRegions(); + queuedTectonicPlates += service.engine.getMantle().getToUnload(); + minTectonicUnloadDuration = Math.min(minTectonicUnloadDuration, (long) service.engine.getMantle().getTectonicDuration()); + maxTectonicUnloadDuration = Math.max(maxTectonicUnloadDuration, (long) service.engine.getMantle().getTectonicDuration()); + } + return new Status(INSTANCES.size(), loadedChunks, MantleCleanerSVC.getTectonicLimit(), tectonicPlates, activeTectonicPlates, queuedTectonicPlates, minTectonicUnloadDuration, maxTectonicUnloadDuration); + } + } + + public record Status(int engineCount, long loadedChunks, int tectonicLimit, long tectonicPlates, long activeTectonicPlates, long queuedTectonicPlates, long minTectonicUnloadDuration, long maxTectonicUnloadDuration) {} +} diff --git a/core/src/main/java/com/volmit/iris/engine/service/MantleCleanerSVC.java b/core/src/main/java/com/volmit/iris/engine/service/MantleCleanerSVC.java new file mode 100644 index 000000000..6d344bb27 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/service/MantleCleanerSVC.java @@ -0,0 +1,123 @@ +package com.volmit.iris.engine.service; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.IrisEngineService; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.misc.getHardware; +import com.volmit.iris.util.scheduling.Looper; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.LongSupplier; + +import static com.volmit.iris.engine.service.EngineStatusSVC.getEngineCount; + +public class MantleCleanerSVC extends IrisEngineService { + private static final AtomicInteger tectonicLimit = new AtomicInteger(30); + private static final AtomicInteger idCounter = new AtomicInteger(); + private int id = -1; + private Ticker trimmer; + private Ticker unloader; + + public MantleCleanerSVC(Engine engine) { + super(engine); + } + + @Override + public void onEnable(boolean hotload) { + if (engine.isStudio() && !IrisSettings.get().getPerformance().trimMantleInStudio) + return; + if (id == -1) id = idCounter.getAndIncrement(); + if (trimmer == null || !trimmer.isAlive()) + trimmer = createTrimmer(id, engine); + if (unloader == null || !unloader.isAlive()) + unloader = createUnloader(id, engine); + } + + @Override + public void onDisable(boolean hotload) { + if (hotload) return; + if (trimmer != null) trimmer.await(); + if (unloader != null) unloader.await(); + } + + static { + tectonicLimit.set(2); + long t = getHardware.getProcessMemory(); + while (t > 200) { + tectonicLimit.incrementAndGet(); + t = t - 200; + } + } + + public static int getTectonicLimit() { + return tectonicLimit.get(); + } + + private static Ticker createTrimmer(int id, Engine engine) { + return new Ticker(() -> { + if (engine.isClosed()) return -1; + long start = M.ms(); + try { + engine.getMantle().trim(tectonicLimit.get() / getEngineCount()); + } catch (Throwable e) { + Iris.debug(C.RED + "Mantle: Failed to trim."); + Iris.reportError(e); + e.printStackTrace(); + } + + if (engine.isClosed()) return -1; + int size = getEngineCount(); + return Math.max(1000 / size - (M.ms() - start), 0); + }, "Iris Mantle Trimmer-" + id); + } + + private static Ticker createUnloader(int id, Engine engine) { + return new Ticker(() -> { + if (engine.isClosed()) return -1; + long start = M.ms(); + try { + engine.getMantle().unloadTectonicPlate(tectonicLimit.get() / getEngineCount()); + } catch (Throwable e) { + Iris.debug(C.RED + "Mantle: Failed to unload."); + Iris.reportError(e); + e.printStackTrace(); + } + + if (engine.isClosed()) return -1; + int size = getEngineCount(); + return Math.max(1000 / size - (M.ms() - start), 0); + }, "Iris Mantle Unloader-" + id); + } + + private static class Ticker extends Looper { + private final LongSupplier supplier; + private final CountDownLatch exit = new CountDownLatch(1); + + private Ticker(LongSupplier supplier, String name) { + this.supplier = supplier; + setPriority(Thread.MIN_PRIORITY); + setName(name); + start(); + } + + @Override + protected long loop() { + long wait = -1; + try { + wait = supplier.getAsLong(); + } catch (Throwable ignored) {} + if (wait < 0) exit.countDown(); + return wait; + } + + public void await() { + try { + exit.await(); + } catch (InterruptedException ignored) {} + } + } +} diff --git a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java index 442c16d1e..005b94788 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java @@ -21,7 +21,6 @@ package com.volmit.iris.util.mantle; import com.google.common.util.concurrent.AtomicDouble; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.service.IrisCleanerSVC; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.framework.Engine; @@ -41,6 +40,7 @@ import com.volmit.iris.util.matter.MatterSlice; import com.volmit.iris.util.parallel.BurstExecutor; import com.volmit.iris.util.parallel.HyperLock; import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.scheduling.Looper; import lombok.Getter; import org.bukkit.Chunk; @@ -61,6 +61,7 @@ import java.util.concurrent.locks.ReentrantLock; public class Mantle { private static final boolean disableClear = System.getProperty("disableClear", "false").equals("true"); + private final File dataFolder; @Getter private final int worldHeight; @@ -425,23 +426,19 @@ public class Mantle { ioTrim.set(true); unloadLock.lock(); try { - if (lastUse != null && IrisCleanerSVC.instance != null) { - if (!lastUse.isEmpty()) { - Iris.debug("Trimming Tectonic Plates older than " + Form.duration(adjustedIdleDuration.get(), 0)); - for (long i : new ArrayList<>(lastUse.keySet())) { - double finalAdjustedIdleDuration = adjustedIdleDuration.get(); - hyperLock.withLong(i, () -> { - Long lastUseTime = lastUse.get(i); - if (lastUseTime != null && M.ms() - lastUseTime >= finalAdjustedIdleDuration) { - toUnload.add(i); - Iris.debug("Tectonic Region added to unload"); - IrisCleanerSVC.instance.trimActiveAlive.reset(); - } - }); - } - } - } + if (lastUse == null || lastUse.isEmpty()) return; + Iris.debug("Trimming Tectonic Plates older than " + Form.duration(adjustedIdleDuration.get(), 0)); + for (long i : new ArrayList<>(lastUse.keySet())) { + double finalAdjustedIdleDuration = adjustedIdleDuration.get(); + hyperLock.withLong(i, () -> { + Long lastUseTime = lastUse.get(i); + if (lastUseTime != null && M.ms() - lastUseTime >= finalAdjustedIdleDuration) { + toUnload.add(i); + Iris.debug("Tectonic Region added to unload"); + } + }); + } } catch (Throwable e) { Iris.reportError(e); } finally { @@ -454,47 +451,43 @@ public class Mantle { AtomicInteger i = new AtomicInteger(); unloadLock.lock(); BurstExecutor burst = null; - if (IrisCleanerSVC.instance != null) { - try { - KList copy = toUnload.copy(); - if (!disableClear) toUnload.clear(); - burst = MultiBurst.burst.burst(copy.size()); - burst.setMulticore(copy.size() > tectonicLimit); - for (int j = 0; j < copy.size(); j++) { - Long id = copy.get(j); - if (id == null) { - Iris.error("Null id in unloadTectonicPlate at index " + j); - continue; - } - - burst.queue(() -> - hyperLock.withLong(id, () -> { - TectonicPlate m = loadedRegions.get(id); - if (m != null) { - try { - m.write(fileForRegion(dataFolder, id)); - loadedRegions.remove(id); - lastUse.remove(id); - if (disableClear) toUnload.remove(id); - i.incrementAndGet(); - Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(id) + " " + Cache.keyZ(id)); - IrisCleanerSVC.instance.unloadActiveAlive.reset(); - } catch (IOException e) { - Iris.reportError(e); - } - } - })); + try { + KList copy = toUnload.copy(); + if (!disableClear) toUnload.clear(); + burst = MultiBurst.burst.burst(copy.size()); + burst.setMulticore(copy.size() > tectonicLimit); + for (int j = 0; j < copy.size(); j++) { + Long id = copy.get(j); + if (id == null) { + Iris.error("Null id in unloadTectonicPlate at index " + j); + continue; } - burst.complete(); - } catch (Throwable e) { - e.printStackTrace(); - if (burst != null) - burst.complete(); - } finally { - unloadLock.unlock(); - ioTectonicUnload.set(true); + + burst.queue(() -> + hyperLock.withLong(id, () -> { + TectonicPlate m = loadedRegions.get(id); + if (m != null) { + try { + m.write(fileForRegion(dataFolder, id)); + loadedRegions.remove(id); + lastUse.remove(id); + if (disableClear) toUnload.remove(id); + i.incrementAndGet(); + Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(id) + " " + Cache.keyZ(id)); + } catch (IOException e) { + Iris.reportError(e); + } + } + })); } - return i.get(); + burst.complete(); + } catch (Throwable e) { + e.printStackTrace(); + if (burst != null) + burst.complete(); + } finally { + unloadLock.unlock(); + ioTectonicUnload.set(true); } return i.get(); } diff --git a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java index 5af84c6fc..c17271bd9 100644 --- a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java @@ -21,33 +21,32 @@ import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder; import com.volmit.iris.util.mantle.MantleFlag; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.parallel.MultiBurst; -import com.volmit.iris.util.scheduling.Looper; import com.volmit.iris.util.scheduling.PrecisionStopwatch; import net.minecraft.core.Holder; +import net.minecraft.core.QuartPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.LevelHeightAccessor; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.PalettedContainer; import net.minecraft.world.level.chunk.ProtoChunk; import org.bukkit.Material; import org.bukkit.block.data.BlockData; import java.io.File; import java.io.IOException; -import java.util.ArrayDeque; -import java.util.Queue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; public class Headless implements IHeadless, LevelHeightAccessor { private final NMSBinding binding; private final Engine engine; private final RegionFileStorage storage; - private final Queue chunkQueue = new ArrayDeque<>(); - private final ReentrantLock saveLock = new ReentrantLock(); + private final ExecutorService executor = Executors.newSingleThreadExecutor(); + private final AtomicInteger loadedChunks = new AtomicInteger(); private final KMap> customBiomes = new KMap<>(); private final KMap> minecraftBiomes = new KMap<>(); private final RNG BIOME_RNG; @@ -56,17 +55,9 @@ public class Headless implements IHeadless, LevelHeightAccessor { public Headless(NMSBinding binding, Engine engine) { this.binding = binding; this.engine = engine; - this.storage = new RegionFileStorage(new File(engine.getWorld().worldFolder(), "region").toPath(), false); + this.storage = new RegionFileStorage(new File(engine.getWorld().worldFolder(), "region").toPath(), true); this.BIOME_RNG = new RNG(engine.getSeedManager().getBiome()); - var queueLooper = new Looper() { - @Override - protected long loop() { - save(); - return closed ? -1 : 100; - } - }; - queueLooper.setName("Region Save Looper"); - queueLooper.start(); + engine.getWorld().headless(this); var dimKey = engine.getDimension().getLoadKey(); for (var biome : engine.getAllBiomes()) { @@ -83,6 +74,11 @@ public class Headless implements IHeadless, LevelHeightAccessor { ServerConfigurator.dumpDataPack(); } + @Override + public int getLoadedChunks() { + return loadedChunks.get(); + } + /** * Checks if the mca plate is fully generated or not. * @@ -101,26 +97,6 @@ public class Headless implements IHeadless, LevelHeightAccessor { } } - @Override - public void save() { - if (closed) return; - saveLock.lock(); - try { - while (!chunkQueue.isEmpty()) { - ChunkAccess chunk = chunkQueue.poll(); - if (chunk == null) break; - try { - storage.write(chunk.getPos(), binding.serializeChunk(chunk, this)); - } catch (Throwable e) { - Iris.error("Failed to save chunk " + chunk.getPos().x + ", " + chunk.getPos().z); - e.printStackTrace(); - } - } - } finally { - saveLock.unlock(); - } - } - @Override public void generateRegion(MultiBurst burst, int x, int z, PregenListener listener) { if (closed) return; @@ -157,6 +133,7 @@ public class Headless implements IHeadless, LevelHeightAccessor { var pos = new ChunkPos(x, z); ProtoChunk chunk = binding.createProtoChunk(pos, this); var tc = new MCATerrainChunk(chunk); + loadedChunks.incrementAndGet(); ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc); BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight()); @@ -166,13 +143,27 @@ public class Headless implements IHeadless, LevelHeightAccessor { inject(engine, chunk, ctx); chunk.setStatus(ChunkStatus.FULL); - chunkQueue.add(chunk); + executor.submit(saveChunk(chunk)); } catch (Throwable e) { + loadedChunks.decrementAndGet(); Iris.error("Failed to generate " + x + ", " + z); e.printStackTrace(); } } + private Runnable saveChunk(ProtoChunk chunk) { + return () -> { + if (closed) return; + try { + storage.write(chunk.getPos(), binding.serializeChunk(chunk, this)); + loadedChunks.decrementAndGet(); + } catch (Throwable e) { + Iris.error("Failed to save chunk " + chunk.getPos().x + ", " + chunk.getPos().z); + e.printStackTrace(); + } + }; + } + @BlockCoordinates private ChunkContext generate(Engine engine, int x, int z, Hunk vblocks, Hunk vbiomes) throws WrongEngineBroException { if (engine.isClosed()) { @@ -214,32 +205,33 @@ public class Headless implements IHeadless, LevelHeightAccessor { } private void inject(Engine engine, ChunkAccess chunk, ChunkContext ctx) { - var pos = chunk.getPos(); - for (int y = engine.getMinHeight(); y < engine.getMaxHeight(); y++) { - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - int wX = pos.getBlockX(x); - int wZ = pos.getBlockZ(z); - try { - chunk.setBiome(x, y, z, getNoiseBiome(engine, ctx, x, z, wX, y, wZ)); - } catch (Throwable e) { - Iris.error("Failed to inject biome for " + wX + ", " + y + ", " + wZ); - e.printStackTrace(); + chunk.fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null); + /* + int qX = QuartPos.fromBlock(chunk.getPos().getMinBlockX()); + int qZ = QuartPos.fromBlock(chunk.getPos().getMinBlockZ()); + + for (int i = chunk.getMinSection(); i < chunk.getMaxSection(); i++) { + var section = chunk.getSection(chunk.getSectionIndexFromSectionY(i)); + PalettedContainer> biomes = (PalettedContainer>) section.getBiomes(); + int qY = QuartPos.fromSection(i); + + for (int sX = 0; sX < 4; sX++) { + for (int sZ = 0; sZ < 4; sZ++) { + for (int sY = 0; sY < 4; sY++) { + biomes.getAndSetUnchecked(sX, sY, sZ, getNoiseBiome(engine, ctx, (qX + sX) << 2, (qY + sY) << 2, (qZ + sZ) << 2)); } } } - } + }*/ } - private Holder getNoiseBiome(Engine engine, ChunkContext ctx, int rX, int rZ, int x, int y, int z) { - int m = (y - engine.getMinHeight()) << 2; - IrisBiome ib = ctx == null ? - engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2) : - ctx.getBiome().get(rX, rZ); + private Holder getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) { + int m = y - engine.getMinHeight(); + IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15); if (ib.isCustom()) { - return customBiomes.get(ib.getCustomBiome(BIOME_RNG, x << 2, m, z << 2).getId()); + return customBiomes.get(ib.getCustomBiome(BIOME_RNG, x, m, z).getId()); } else { - return minecraftBiomes.get(ib.getSkyBiome(BIOME_RNG, x << 2, m, z << 2)); + return minecraftBiomes.get(ib.getSkyBiome(BIOME_RNG, x, m, z)); } } @@ -247,7 +239,13 @@ public class Headless implements IHeadless, LevelHeightAccessor { public void close() throws IOException { if (closed) return; try { + executor.shutdown(); + try { + if (executor.awaitTermination(10, TimeUnit.SECONDS)) + executor.shutdownNow(); + } catch (InterruptedException ignored) {} storage.close(); + engine.getWorld().headless(null); } finally { closed = true; customBiomes.clear();