diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java
index bffe76848..4b48913fe 100644
--- a/core/src/main/java/com/volmit/iris/Iris.java
+++ b/core/src/main/java/com/volmit/iris/Iris.java
@@ -31,7 +31,6 @@ import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.core.nms.v1X.NMSBinding1X;
import com.volmit.iris.core.pregenerator.LazyPregenerator;
import com.volmit.iris.core.safeguard.ServerBootSFG;
-import com.volmit.iris.core.service.HotDropWorldSVC;
import com.volmit.iris.core.service.StudioSVC;
import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.engine.EnginePanic;
@@ -474,8 +473,6 @@ public class Iris extends VolmitPlugin implements Listener {
UtilsSFG.SupportedServerSoftware();
UtilsSFG.printIncompatibleWarnings();
UtilsSFG.unstablePrompt();
- HotDropWorldSVC hotDropWorldSVC = new HotDropWorldSVC(this);
- hotDropWorldSVC.start();
autoStartStudio();
ServerBootSFG.CheckIrisWorlds();
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
new file mode 100644
index 000000000..0cc026dff
--- /dev/null
+++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java
@@ -0,0 +1,67 @@
+/*
+ * 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.core.commands;
+
+import com.volmit.iris.Iris;
+import com.volmit.iris.core.loader.IrisData;
+import com.volmit.iris.core.tools.IrisToolbelt;
+import com.volmit.iris.engine.framework.Engine;
+import com.volmit.iris.engine.mantle.EngineMantle;
+import com.volmit.iris.util.decree.DecreeExecutor;
+import com.volmit.iris.util.decree.DecreeOrigin;
+import com.volmit.iris.util.decree.annotations.Decree;
+import com.volmit.iris.util.decree.annotations.Param;
+import com.volmit.iris.util.format.C;
+import com.volmit.iris.util.format.Form;
+import com.volmit.iris.util.mantle.Mantle;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+
+@Decree(name = "Developer", origin = DecreeOrigin.BOTH, description = "Iris World Manager", aliases = {"dev"})
+public class CommandDeveloper implements DecreeExecutor {
+
+ @Decree(description = "Get Loaded TectonicPlates Count", origin = DecreeOrigin.BOTH, sync = true)
+ public void EngineStatus(
+ @Param(description = "World")
+ World world
+ ) {
+ if (!IrisToolbelt.isIrisWorld(world)) {
+ sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", Bukkit.getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList()));
+ return;
+ }
+
+ Engine engine = IrisToolbelt.access(world).getEngine();
+ if(engine != null) {
+ long lastUseSize = engine.getMantle().getLastUseMapMemoryUsage();
+ long outputToUnload = engine.getMantle().getToUnload();
+
+ Iris.info("-------------------------");
+ Iris.info(C.DARK_PURPLE + "Engine Status");
+ Iris.info(C.DARK_PURPLE + "Tectonic Plates: " + C.LIGHT_PURPLE + engine.getMantle().getLoadedRegionCount());
+ Iris.info(C.DARK_PURPLE + "Tectonic ToUnload: " + C.LIGHT_PURPLE + outputToUnload);
+ Iris.info(C.DARK_PURPLE + "Cache Size: " + C.LIGHT_PURPLE + Form.f(IrisData.cacheSize()));
+ Iris.info(C.DARK_PURPLE + "LastUse Size: " + C.LIGHT_PURPLE + Form.mem(lastUseSize) + " MB");
+ Iris.info("-------------------------");
+ } else {
+ Iris.info(C.RED + "Engine is null!");
+ }
+ }
+}
+
+
diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java
index 7d13a044b..b80548c29 100644
--- a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java
+++ b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java
@@ -69,6 +69,7 @@ public class CommandIris implements DecreeExecutor {
private CommandEdit edit;
private CommandFind find;
private CommandWorldManager manager;
+ private CommandDeveloper developer;
public static @Getter String BenchDimension;
diff --git a/core/src/main/java/com/volmit/iris/core/service/HotDropWorldSVC.java b/core/src/main/java/com/volmit/iris/core/service/HotDropWorldSVC.java
index 44166f64b..88bab482b 100644
--- a/core/src/main/java/com/volmit/iris/core/service/HotDropWorldSVC.java
+++ b/core/src/main/java/com/volmit/iris/core/service/HotDropWorldSVC.java
@@ -11,16 +11,28 @@ import com.google.gson.JsonParseException;
import com.volmit.iris.Iris;
import com.volmit.iris.util.SFG.WorldHandlerSFG;
import com.volmit.iris.util.format.C;
+import com.volmit.iris.util.plugin.IrisService;
import com.volmit.iris.util.scheduling.Looper;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
-public class HotDropWorldSVC extends Looper {
+public class HotDropWorldSVC extends Looper implements IrisService {
private WatchService watchService;
private JavaPlugin plugin;
- public HotDropWorldSVC(JavaPlugin plugin) {
- this.plugin = plugin;
+ @Override
+ public void onEnable() {
+ this.plugin = Iris.instance; // Assuming Iris.instance is your plugin instance
+ initializeWatchService();
+
+ }
+
+ @Override
+ public void onDisable() {
+
+ }
+
+ private void initializeWatchService() {
try {
this.watchService = FileSystems.getDefault().newWatchService();
Path path = Paths.get(Bukkit.getWorldContainer().getAbsolutePath());
diff --git a/core/src/main/java/com/volmit/iris/core/service/MemoryLeakSVC.java b/core/src/main/java/com/volmit/iris/core/service/MemoryLeakSVC.java
new file mode 100644
index 000000000..3baab50ed
--- /dev/null
+++ b/core/src/main/java/com/volmit/iris/core/service/MemoryLeakSVC.java
@@ -0,0 +1,78 @@
+package com.volmit.iris.core.service;
+
+import java.nio.file.*;
+import static java.nio.file.StandardWatchEventKinds.*;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonParseException;
+import com.volmit.iris.Iris;
+import com.volmit.iris.core.service.StudioSVC;
+import com.volmit.iris.core.tools.IrisToolbelt;
+import com.volmit.iris.engine.framework.Engine;
+import com.volmit.iris.util.SFG.WorldHandlerSFG;
+import com.volmit.iris.util.format.C;
+import com.volmit.iris.util.misc.getHardware;
+import com.volmit.iris.util.plugin.IrisService;
+import com.volmit.iris.util.scheduling.Looper;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class MemoryLeakSVC extends Looper implements IrisService {
+ private WatchService watchService;
+ private JavaPlugin plugin;
+
+ @Override
+ public void onEnable() {
+ //Iris.info("Enabled Mem Leak Detection thing wow it actually worked");
+ //this.plugin = Iris.instance;
+ }
+
+ @Override
+ public void onDisable() {
+
+ }
+
+ @Override
+ protected long loop() {
+ try {
+ if (getHardware.getAvailableProcessMemory() < 50){
+ PrintMemoryLeakDetected();
+ for (World world : Bukkit.getWorlds()) {
+ if (IrisToolbelt.isIrisWorld(world)){
+ Engine engine = IrisToolbelt.access(world).getEngine();
+ if (engine.getMantle().getLoadedRegionCount() > 0){
+ if (!IrisToolbelt.isIrisWorld(world)) {
+ Iris.info(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", Bukkit.getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList()));
+ return -1;
+ }
+ Iris.info(C.GREEN + "Unloading world: " + world.getName());
+ try {
+ IrisToolbelt.evacuate(world);
+ Bukkit.unloadWorld(world, false);
+ Iris.info(C.GREEN + "World unloaded successfully.");
+ } catch (Exception e) {
+ Iris.info(C.RED + "Failed to unload the world: " + e.getMessage());
+ e.printStackTrace();
+ }
+
+ }
+ }
+ }
+ }
+ } catch (Throwable e) {
+ Iris.reportError(e);
+ e.printStackTrace();
+ return -1;
+ }
+
+ return 1000;
+ }
+ public void PrintMemoryLeakDetected(){
+ Iris.info(C.DARK_RED + "--==< MEMORY LEAK DETECTED >==--");
+ }
+}
+
diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java
index 5dbd7a7d1..04a5263f3 100644
--- a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java
+++ b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java
@@ -257,6 +257,9 @@ public interface EngineMantle extends IObjectPlacer {
default int getLoadedRegionCount() {
return getMantle().getLoadedRegionCount();
}
+ default long getLastUseMapMemoryUsage(){
+ return getMantle().LastUseMapMemoryUsage();
+ }
MantleJigsawComponent getJigsawComponent();
@@ -288,4 +291,8 @@ public interface EngineMantle extends IObjectPlacer {
});
}
}
+
+ default long getToUnload(){
+ return getMantle().ToUnloadTectonic();
+ }
}
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 113405abf..6630fc019 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
@@ -36,6 +36,7 @@ import com.volmit.iris.util.function.Consumer4;
import com.volmit.iris.util.math.M;
import com.volmit.iris.util.matter.Matter;
import com.volmit.iris.util.matter.MatterSlice;
+import com.volmit.iris.util.misc.getHardware;
import com.volmit.iris.util.parallel.BurstExecutor;
import com.volmit.iris.util.parallel.HyperLock;
import com.volmit.iris.util.parallel.MultiBurst;
@@ -45,16 +46,18 @@ import org.bukkit.Chunk;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
+import java.util.HashSet;
import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
+import java.util.Set;
+import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* The mantle can store any type of data slice anywhere and manage regions & IO on it's own.
* This class is fully thread safe read & writeNodeData
*/
+
public class Mantle {
private final File dataFolder;
private final int worldHeight;
@@ -66,6 +69,11 @@ public class Mantle {
private final AtomicBoolean closed;
private final MultiBurst ioBurst;
private final AtomicBoolean io;
+ private final Object gcMonitor = new Object();
+ long apm = getHardware.getAvailableProcessMemory();
+ private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
+ int tectonicLimitBeforeOutMemory;
+ double tectonicLimit = 30;
/**
* Create a new mantle
@@ -374,45 +382,92 @@ public class Mantle {
Iris.debug("The Mantle has Closed " + C.DARK_AQUA + dataFolder.getAbsolutePath());
}
+ /**
+ * Estimates the memory usage of the lastUse map.
+ *
+ * @return Estimated memory usage in bytes.
+ */
+
+ public long LastUseMapMemoryUsage() {
+ long numberOfEntries = lastUse.size();
+ long bytesPerEntry = Long.BYTES * 2;
+ return numberOfEntries * bytesPerEntry;
+ }
+
/**
* Save & unload regions that have not been used for more than the
* specified amount of milliseconds
*
- * @param idleDuration the duration
+ * @param baseIdleDuration the duration
*/
- public synchronized void trim(long idleDuration) {
+
+ AtomicInteger FakeToUnload = new AtomicInteger(0);
+
+
+ public synchronized void trim(long baseIdleDuration) {
if (closed.get()) {
throw new RuntimeException("The Mantle is closed");
}
+ double adjustedIdleDuration = baseIdleDuration;
+
+ if (loadedRegions.size() > tectonicLimit) {
+ adjustedIdleDuration = Math.max(adjustedIdleDuration - ( 1000 * (loadedRegions.size() - tectonicLimit) * 1.35), 4000);
+ }
+
io.set(true);
- Iris.debug("Trimming Tectonic Plates older than " + Form.duration((double) idleDuration, 0));
- unload.clear();
- for (Long i : lastUse.keySet()) {
- hyperLock.withLong(i, () -> {
- if (M.ms() - lastUse.get(i) >= idleDuration) {
- unload.add(i);
- }
- });
+ try {
+ Iris.debug("Trimming Tectonic Plates older than " + Form.duration(adjustedIdleDuration, 0));
+ Set toUnload = new HashSet<>();
+
+ for (Long i : lastUse.keySet()) {
+ double finalAdjustedIdleDuration = adjustedIdleDuration;
+ hyperLock.withLong(i, () -> {
+ if (M.ms() - lastUse.get(i) >= finalAdjustedIdleDuration) {
+ toUnload.add(i);
+ FakeToUnload.addAndGet(1);
+ Iris.debug("Tectonic Region added to unload");
+ }
+ });
+ }
+
+ // Create a thread pool to handle unloading tasks
+ ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+
+ for (Long i : toUnload) {
+ executor.submit(() -> {
+ hyperLock.withLong(i, () -> {
+ TectonicPlate m = loadedRegions.get(i);
+ if (m != null) {
+ try {
+ m.write(fileForRegion(dataFolder, i));
+ loadedRegions.remove(i);
+ lastUse.remove(i);
+ Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(i) + " " + Cache.keyZ(i));
+ FakeToUnload.addAndGet(-1);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ });
+ }
+
+ // Shutdown the executor and wait for tasks to complete
+ executor.shutdown();
+ executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ e.printStackTrace();
+ } finally {
+ io.set(false);
}
-
- for (Long i : unload) {
- hyperLock.withLong(i, () -> {
- TectonicPlate m = loadedRegions.remove(i);
- lastUse.remove(i);
-
- try {
- m.write(fileForRegion(dataFolder, i));
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(i) + " " + Cache.keyZ(i));
- });
- }
- io.set(false);
}
+ public long ToUnloadTectonic(){
+ return FakeToUnload.get();
+ }
+
/**
* This retreives a future of the Tectonic Plate at the given coordinates.
@@ -443,7 +498,7 @@ public class Mantle {
try {
return getSafe(x, z).get();
} catch (InterruptedException e) {
- Iris.warn("Failed to get Tectonic Plate " + x + " " + z + " Due to a thread interruption (hotload?)");
+ Iris.warn("Failed to get Tectonic Plate " + x + " " + z + " Due to a thread intterruption (hotload?)");
Iris.reportError(e);
} catch (ExecutionException e) {
Iris.warn("Failed to get Tectonic Plate " + x + " " + z + " Due to a thread execution exception (engine close?)");
diff --git a/core/src/main/java/com/volmit/iris/util/misc/getHardware.java b/core/src/main/java/com/volmit/iris/util/misc/getHardware.java
index ea5aa06d5..80221590d 100644
--- a/core/src/main/java/com/volmit/iris/util/misc/getHardware.java
+++ b/core/src/main/java/com/volmit/iris/util/misc/getHardware.java
@@ -22,6 +22,20 @@ public class getHardware {
long maxMemory = Runtime.getRuntime().maxMemory() / (1024 * 1024);
return maxMemory;
}
+ public static long getProcessUsedMemory() {
+ Runtime runtime = Runtime.getRuntime();
+
+ long totalMemory = runtime.totalMemory();
+ long freeMemory = runtime.freeMemory();
+ long usedMemory = totalMemory - freeMemory;
+
+ return usedMemory / (1024 * 1024);
+ }
+
+ public static long getAvailableProcessMemory(){
+ long availableMemory = getHardware.getProcessMemory() - getHardware.getProcessUsedMemory();
+ return availableMemory;
+ }
public static String getCPUModel() {
try {