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 f2991e75a..bcc1da635 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 @@ -22,11 +22,11 @@ import com.volmit.iris.Iris; import com.volmit.iris.core.ServerConfigurator; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.nms.datapack.DataVersion; -import com.volmit.iris.core.service.IrisEngineSVC; import com.volmit.iris.core.tools.IrisPackBenchmarking; import com.volmit.iris.core.tools.IrisToolbelt; 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; @@ -66,53 +66,20 @@ public class CommandDeveloper implements DecreeExecutor { @Decree(description = "Get Loaded TectonicPlates Count", origin = DecreeOrigin.BOTH, 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 + IrisEngineSVC.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") diff --git a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java b/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java deleted file mode 100644 index 2220ccc26..000000000 --- a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java +++ /dev/null @@ -1,317 +0,0 @@ -package com.volmit.iris.core.service; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.IrisSettings; -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.KList; -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.mantle.TectonicPlate; -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 org.checkerframework.checker.units.qual.A; - -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Supplier; - -public class IrisEngineSVC implements IrisService { - public static IrisEngineSVC 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 AtomicBoolean IsUnloadAlive; - private AtomicBoolean IsTrimAlive; - ChronoLatch cl; - - public List corruptedIrisWorlds = new ArrayList<>(); - - @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(); - 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.info(C.RED + "EngineSVC: Failed to trim."); - 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.info(C.RED + "EngineSVC: Failed to unload."); - 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) { - Iris.info(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/engine/IrisEngine.java b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java index 88c5fdc14..ee304ae14 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisEngine.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java @@ -43,6 +43,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; @@ -60,7 +61,11 @@ import org.bukkit.command.CommandSender; import java.io.File; import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Collections; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; @@ -71,6 +76,8 @@ import java.util.concurrent.atomic.AtomicLong; @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; @@ -111,6 +118,7 @@ public class IrisEngine implements Engine { getEngineData(); verifySeed(); this.seedManager = new SeedManager(target.getWorld().getRawWorldSeed()); + services = new KMap<>(); bud = new AtomicInteger(0); buds = new AtomicInteger(0); metrics = new EngineMetrics(32); @@ -137,6 +145,26 @@ public class IrisEngine implements Engine { Iris.debug("Engine Initialized " + getCacheID()); } + @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); + } + private void verifySeed() { if (getEngineData().getSeed() != null && getEngineData().getSeed() != target.getWorld().getRawWorldSeed()) { target.getWorld().setRawWorldSeed(getEngineData().getSeed()); @@ -161,6 +189,8 @@ public class IrisEngine implements Engine { 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()); } @@ -169,6 +199,24 @@ 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); @@ -418,6 +466,7 @@ public class IrisEngine implements Engine { PregeneratorJob.shutdownInstance(); closed = true; J.car(art); + services.values().forEach(s -> s.onDisable(false)); getWorldManager().close(); getTarget().close(); saveEngineData(); 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/service/EngineStatusSVC.java b/core/src/main/java/com/volmit/iris/engine/service/EngineStatusSVC.java new file mode 100644 index 000000000..2b65650be --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/service/EngineStatusSVC.java @@ -0,0 +1,65 @@ +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; + +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 INSTANCES.size(); + } + + 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; + + 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..f3bf2bda1 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/service/MantleCleanerSVC.java @@ -0,0 +1,127 @@ +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 lombok.SneakyThrows; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.LongSupplier; + +public class MantleCleanerSVC extends IrisEngineService { + private static final AtomicInteger tectonicLimit = new AtomicInteger(30); + + static { + // todo: Redo this + tectonicLimit.set(2); + long t = getHardware.getProcessMemory(); + while (t > 200) { + tectonicLimit.incrementAndGet(); + t = t - 200; + } + } + + private Ticker trimmer; + private Ticker unloader; + + public MantleCleanerSVC(Engine engine) { + super(engine); + } + + public static int getTectonicLimit() { + return tectonicLimit.get(); + } + + private static Ticker createTrimmer(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 - " + engine.getWorld().name()); + } + + private static Ticker createUnloader(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 - " + engine.getWorld().name()); + } + + private static int getEngineCount() { + return Math.max(EngineStatusSVC.getEngineCount(), 1); + } + + @Override + public void onEnable(boolean hotload) { + if (engine.isStudio() && !IrisSettings.get().getPerformance().trimMantleInStudio) + return; + if (trimmer == null || !trimmer.isAlive()) + trimmer = createTrimmer(engine); + if (unloader == null || !unloader.isAlive()) + unloader = createUnloader(engine); + } + + @Override + public void onDisable(boolean hotload) { + if (hotload) return; + if (trimmer != null) trimmer.await(); + if (unloader != null) unloader.await(); + } + + private static class Ticker extends Looper { + private final LongSupplier supplier; + + private Ticker(LongSupplier supplier, String name) { + this.supplier = supplier; + setPriority(Thread.MIN_PRIORITY); + setName(name); + start(); + } + + @Override + protected long loop() { + try { + return supplier.getAsLong(); + } catch (Throwable e) { + Iris.error("Exception in Looper " + getName()); + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + Iris.error(sw.toString()); + return 3000; + } + } + + @SneakyThrows + public void await() { + join(); + } + } +}