diff --git a/src/main/java/com/volmit/iris/Iris.java b/src/main/java/com/volmit/iris/Iris.java index 57737fcf3..aae34c227 100644 --- a/src/main/java/com/volmit/iris/Iris.java +++ b/src/main/java/com/volmit/iris/Iris.java @@ -26,6 +26,7 @@ import com.volmit.iris.core.link.MythicMobsLink; import com.volmit.iris.core.link.OraxenLink; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.service.PreservationSVC; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.engine.object.IrisBiome; import com.volmit.iris.engine.object.IrisBiomeCustom; @@ -450,6 +451,8 @@ public class Iris extends VolmitPlugin implements Listener { J.a(ServerConfigurator::configure, 20); splash(); + J.sr(() -> Iris.service(PreservationSVC.class).printCaches(), 20); + if (IrisSettings.get().getStudio().isAutoStartDefaultStudio()) { Iris.info("Starting up auto Studio!"); try { diff --git a/src/main/java/com/volmit/iris/core/IrisSettings.java b/src/main/java/com/volmit/iris/core/IrisSettings.java index fd599a73c..18deed4ae 100644 --- a/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -64,7 +64,11 @@ public class IrisSettings { @Data public static class IrisSettingsPerformance { public int mantleKeepAliveSeconds = 60; - public int cacheSize = 131072; + public int maxStreamCacheSize = 1_000_000; + public int maxResourceLoaderCacheSize = 1_000; + public int maxObjectLoaderCacheSize = 3_000; + public int maxScriptLoaderCacheSize = 500; + public double cacheMemoryPercentage = 0.65; } @Data diff --git a/src/main/java/com/volmit/iris/core/loader/IrisData.java b/src/main/java/com/volmit/iris/core/loader/IrisData.java index 9e28dbc86..dbee39943 100644 --- a/src/main/java/com/volmit/iris/core/loader/IrisData.java +++ b/src/main/java/com/volmit/iris/core/loader/IrisData.java @@ -71,6 +71,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { private static final KMap dataLoaders = new KMap<>(); private final File dataFolder; private final int id; + private boolean closed = false; private ResourceLoader biomeLoader; private ResourceLoader lootLoader; private ResourceLoader regionLoader; @@ -115,7 +116,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { int m = 0; for (IrisData i : dataLoaders.values()) { for (ResourceLoader j : i.getLoaders().values()) { - m += j.getLoadCache().size(); + m += j.getLoadCache().getSize(); } } @@ -123,7 +124,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { } private static void printData(ResourceLoader rl) { - Iris.warn(" " + rl.getResourceTypeName() + " @ /" + rl.getFolderName() + ": Cache=" + rl.getLoadCache().size() + " Folders=" + rl.getFolders().size()); + Iris.warn(" " + rl.getResourceTypeName() + " @ /" + rl.getFolderName() + ": Cache=" + rl.getLoadCache().getSize() + " Folders=" + rl.getFolders().size()); } public static IrisObject loadAnyObject(String key) { @@ -274,6 +275,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { } public void close() { + closed = true; dump(); } @@ -286,11 +288,11 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { IrisRegistrant rr = registrant.getConstructor().newInstance(); ResourceLoader r = null; if (registrant.equals(IrisObject.class)) { - r = (ResourceLoader) new ObjectResourceLoader(dataFolder, this, rr.getFolderName(), rr.getTypeName()); - } else if (registrant.equals(IrisMatter.class)) { - r = (ResourceLoader) new MatterResourceLoader(dataFolder, this, rr.getFolderName(), rr.getTypeName()); + r = (ResourceLoader) new ObjectResourceLoader(dataFolder, this, rr.getFolderName(), + rr.getTypeName()); } else if (registrant.equals(IrisScript.class)) { - r = (ResourceLoader) new ScriptResourceLoader(dataFolder, this, rr.getFolderName(), rr.getTypeName()); + r = (ResourceLoader) new ScriptResourceLoader(dataFolder, this, rr.getFolderName(), + rr.getTypeName()); } else { J.attempt(() -> registrant.getConstructor().newInstance().registerTypeAdapters(builder)); r = new ResourceLoader<>(dataFolder, this, rr.getFolderName(), rr.getTypeName(), registrant); @@ -460,4 +462,8 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { return l; }); } + + public boolean isClosed() { + return closed; + } } \ No newline at end of file diff --git a/src/main/java/com/volmit/iris/core/loader/MatterResourceLoader.java b/src/main/java/com/volmit/iris/core/loader/MatterResourceLoader.java deleted file mode 100644 index 4efc2f06e..000000000 --- a/src/main/java/com/volmit/iris/core/loader/MatterResourceLoader.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.loader; - -import com.volmit.iris.Iris; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.collection.KSet; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.math.M; -import com.volmit.iris.util.matter.IrisMatter; -import com.volmit.iris.util.matter.Matter; -import com.volmit.iris.util.scheduling.ChronoLatch; -import com.volmit.iris.util.scheduling.J; -import com.volmit.iris.util.scheduling.PrecisionStopwatch; - -import java.io.File; -import java.util.concurrent.atomic.AtomicInteger; - -public class MatterResourceLoader extends ResourceLoader { - private final ChronoLatch useFlip = new ChronoLatch(2222); - private final KMap useCache = new KMap<>(); - private final ChronoLatch cl; - private final AtomicInteger unload; - - public MatterResourceLoader(File root, IrisData idm, String folderName, String resourceTypeName) { - super(root, idm, folderName, resourceTypeName, IrisMatter.class); - cl = new ChronoLatch(30000); - unload = new AtomicInteger(0); - } - - public boolean supportsSchemas() { - return false; - } - - public int getSize() { - return loadCache.size(); - } - - public int getTotalStorage() { - return getSize(); - } - - public void clean() { - if (useFlip.flip()) { - unloadLast(30000); - } - } - - public void unloadLast(long age) { - String v = getOldest(); - - if (v == null) { - return; - } - - if (M.ms() - useCache.get(v) > age) { - unload(v); - } - } - - private String getOldest() { - long min = M.ms(); - String v = null; - - for (String i : useCache.k()) { - long t = useCache.get(i); - if (t < min) { - min = t; - v = i; - } - } - - return v; - } - - private void unload(String v) { - lock.lock(); - useCache.remove(v); - loadCache.remove(v); - lock.unlock(); - unload.getAndIncrement(); - - if (unload.get() == 1) { - cl.flip(); - } - - if (cl.flip()) { - J.a(() -> { - Iris.verbose("Unloaded " + C.WHITE + unload.get() + " " + resourceTypeName + (unload.get() == 1 ? "" : "s") + C.GRAY + " to optimize memory usage." + " (" + Form.f(getLoadCache().size()) + " " + resourceTypeName + (loadCache.size() == 1 ? "" : "s") + " Loaded)"); - unload.set(0); - }); - } - } - - public IrisMatter loadFile(File j, String key, String name) { - lock.lock(); - try { - PrecisionStopwatch p = PrecisionStopwatch.start(); - IrisMatter t = (IrisMatter) Matter.read(j); - loadCache.put(key, t); - t.setLoadKey(name); - t.setLoader(manager); - t.setLoadFile(j); - logLoad(j, t); - lock.unlock(); - tlt.addAndGet(p.getMilliseconds()); - return t; - } catch (Throwable e) { - Iris.reportError(e); - lock.unlock(); - Iris.warn("Couldn't read " + resourceTypeName + " file: " + j.getPath() + ": " + e.getMessage()); - return null; - } - } - - public String[] getPossibleKeys() { - if (possibleKeys != null) { - return possibleKeys; - } - - Iris.debug("Building " + resourceTypeName + " Possibility Lists"); - KSet m = new KSet<>(); - - for (File i : getFolders()) { - for (File j : i.listFiles()) { - if (j.isFile() && j.getName().endsWith(".matter")) { - m.add(j.getName().replaceAll("\\Q.matter\\E", "")); - } else if (j.isDirectory()) { - for (File k : j.listFiles()) { - if (k.isFile() && k.getName().endsWith(".matter")) { - m.add(j.getName() + "/" + k.getName().replaceAll("\\Q.matter\\E", "")); - } else if (k.isDirectory()) { - for (File l : k.listFiles()) { - if (l.isFile() && l.getName().endsWith(".matter")) { - m.add(j.getName() + "/" + k.getName() + "/" + l.getName().replaceAll("\\Q.matter\\E", "")); - } - } - } - } - } - } - } - - KList v = new KList<>(m); - possibleKeys = v.toArray(new String[0]); - return possibleKeys; - } - - public File findFile(String name) { - lock.lock(); - for (File i : getFolders(name)) { - for (File j : i.listFiles()) { - if (j.isFile() && j.getName().endsWith(".matter") && j.getName().split("\\Q.\\E")[0].equals(name)) { - lock.unlock(); - return j; - } - } - - File file = new File(i, name + ".matter"); - - if (file.exists()) { - lock.unlock(); - return file; - } - } - - Iris.warn("Couldn't find " + resourceTypeName + ": " + name); - - lock.unlock(); - return null; - } - - public IrisMatter load(String name) { - return load(name, true); - } - - public IrisMatter load(String name, boolean warn) { - String key = name + "-" + objectClass.getCanonicalName(); - - if (loadCache.containsKey(key)) { - IrisMatter t = loadCache.get(key); - useCache.put(key, M.ms()); - return t; - } - - lock.lock(); - for (File i : getFolders(name)) { - for (File j : i.listFiles()) { - if (j.isFile() && j.getName().endsWith(".matter") && j.getName().split("\\Q.\\E")[0].equals(name)) { - useCache.put(key, M.ms()); - lock.unlock(); - return loadFile(j, key, name); - } - } - - File file = new File(i, name + ".matter"); - - if (file.exists()) { - useCache.put(key, M.ms()); - lock.unlock(); - return loadFile(file, key, name); - } - } - - Iris.warn("Couldn't find " + resourceTypeName + ": " + name); - - lock.unlock(); - return null; - } -} diff --git a/src/main/java/com/volmit/iris/core/loader/ObjectResourceLoader.java b/src/main/java/com/volmit/iris/core/loader/ObjectResourceLoader.java index 1fe368b99..c934b486b 100644 --- a/src/main/java/com/volmit/iris/core/loader/ObjectResourceLoader.java +++ b/src/main/java/com/volmit/iris/core/loader/ObjectResourceLoader.java @@ -19,10 +19,12 @@ package com.volmit.iris.core.loader; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; import com.volmit.iris.engine.object.IrisObject; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.data.KCache; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.math.M; @@ -34,94 +36,29 @@ import java.io.File; import java.util.concurrent.atomic.AtomicInteger; public class ObjectResourceLoader extends ResourceLoader { - private final ChronoLatch useFlip = new ChronoLatch(2222); - private final KMap useCache = new KMap<>(); - private final ChronoLatch cl; - private final AtomicInteger unload; - public ObjectResourceLoader(File root, IrisData idm, String folderName, String resourceTypeName) { super(root, idm, folderName, resourceTypeName, IrisObject.class); - cl = new ChronoLatch(30000); - unload = new AtomicInteger(0); + loadCache = new KCache<>(this::loadRaw, IrisSettings.get().getPerformance().getMaxObjectLoaderCacheSize()); } public boolean supportsSchemas() { return false; } - public int getSize() { - return loadCache.size(); + public long getSize() { + return loadCache.getSize(); } - public int getTotalStorage() { - int m = 0; - - for (IrisObject i : loadCache.values()) { - m += i.getBlocks().size(); - } - - return m; + public long getTotalStorage() { + return getSize(); } - public void clean() { - if (useFlip.flip()) { - unloadLast(30000); - } - } - - public void unloadLast(long age) { - String v = getOldest(); - - if (v == null) { - return; - } - - if (M.ms() - useCache.get(v) > age) { - unload(v); - } - } - - private String getOldest() { - long min = M.ms(); - String v = null; - - for (String i : useCache.k()) { - long t = useCache.get(i); - if (t < min) { - min = t; - v = i; - } - } - - return v; - } - - private void unload(String v) { - lock.lock(); - useCache.remove(v); - loadCache.remove(v); - lock.unlock(); - unload.getAndIncrement(); - - if (unload.get() == 1) { - cl.flip(); - } - - if (cl.flip()) { - J.a(() -> { - Iris.verbose("Unloaded " + C.WHITE + unload.get() + " " + resourceTypeName + (unload.get() == 1 ? "" : "s") + C.GRAY + " to optimize memory usage." + " (" + Form.f(getLoadCache().size()) + " " + resourceTypeName + (loadCache.size() == 1 ? "" : "s") + " Loaded)"); - unload.set(0); - }); - } - } - - public IrisObject loadFile(File j, String key, String name) { + protected IrisObject loadFile(File j, String name) { lock.lock(); try { PrecisionStopwatch p = PrecisionStopwatch.start(); IrisObject t = new IrisObject(0, 0, 0); t.read(j); - loadCache.put(key, t); t.setLoadKey(name); t.setLoader(manager); t.setLoadFile(j); @@ -198,31 +135,21 @@ public class ObjectResourceLoader extends ResourceLoader { return load(name, true); } - public IrisObject load(String name, boolean warn) { - String key = name + "-" + objectClass.getCanonicalName(); - - if (loadCache.containsKey(key)) { - IrisObject t = loadCache.get(key); - useCache.put(key, M.ms()); - return t; - } - + private IrisObject loadRaw(String name){ lock.lock(); for (File i : getFolders(name)) { for (File j : i.listFiles()) { if (j.isFile() && j.getName().endsWith(".iob") && j.getName().split("\\Q.\\E")[0].equals(name)) { - useCache.put(key, M.ms()); lock.unlock(); - return loadFile(j, key, name); + return loadFile(j, name); } } File file = new File(i, name + ".iob"); if (file.exists()) { - useCache.put(key, M.ms()); lock.unlock(); - return loadFile(file, key, name); + return loadFile(file, name); } } @@ -231,4 +158,8 @@ public class ObjectResourceLoader extends ResourceLoader { lock.unlock(); return null; } + + public IrisObject load(String name, boolean warn) { + return loadCache.get(name); + } } diff --git a/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java b/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java index 7a1c7885e..4153f2811 100644 --- a/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java +++ b/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java @@ -20,10 +20,14 @@ package com.volmit.iris.core.loader; import com.google.common.util.concurrent.AtomicDouble; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.project.SchemaBuilder; +import com.volmit.iris.core.service.PreservationSVC; +import com.volmit.iris.engine.framework.MeteredCache; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.data.KCache; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.io.IO; @@ -42,13 +46,13 @@ import java.util.function.Predicate; import java.util.stream.Stream; @Data -public class ResourceLoader { +public class ResourceLoader implements MeteredCache { public static final AtomicDouble tlt = new AtomicDouble(0); private static final int CACHE_SIZE = 100000; protected File root; protected String folderName; protected String resourceTypeName; - protected KMap loadCache; + protected KCache loadCache; protected KList folderCache; protected Class objectClass; protected String cname; @@ -68,8 +72,9 @@ public class ResourceLoader { this.resourceTypeName = resourceTypeName; this.root = root; this.folderName = folderName; - loadCache = new KMap<>(); + loadCache = new KCache<>(this::loadRaw, IrisSettings.get().getPerformance().getMaxResourceLoaderCacheSize()); Iris.debug("Loader<" + C.GREEN + resourceTypeName + C.LIGHT_PURPLE + "> created in " + C.RED + "IDM/" + manager.getId() + C.LIGHT_PURPLE + " on " + C.GRAY + manager.getDataFolder().getPath()); + Iris.service(PreservationSVC.class).registerCache(this); } public JSONObject buildSchema() { @@ -122,7 +127,7 @@ public class ResourceLoader { if (sec.flip()) { J.a(() -> { - Iris.verbose("Loaded " + C.WHITE + loads.get() + " " + resourceTypeName + (loads.get() == 1 ? "" : "s") + C.GRAY + " (" + Form.f(getLoadCache().size()) + " " + resourceTypeName + (loadCache.size() == 1 ? "" : "s") + " Loaded)"); + Iris.verbose("Loaded " + C.WHITE + loads.get() + " " + resourceTypeName + (loads.get() == 1 ? "" : "s") + C.GRAY + " (" + Form.f(getLoadCache().getSize()) + " " + resourceTypeName + (loadCache.getSize() == 1 ? "" : "s") + " Loaded)"); loads.set(0); }); } @@ -177,10 +182,10 @@ public class ResourceLoader { } public long count() { - return loadCache.size(); + return loadCache.getSize(); } - protected T loadFile(File j, String key, String name) { + protected T loadFile(File j, String name) { try { PrecisionStopwatch p = PrecisionStopwatch.start(); T t = getManager().getGson() @@ -189,7 +194,6 @@ public class ResourceLoader { t.setLoadFile(j); t.setLoader(manager); getManager().preprocessObject(t); - loadCache.put(key, t); logLoad(j, t); lock.unlock(); tlt.addAndGet(p.getMilliseconds()); @@ -242,6 +246,28 @@ public class ResourceLoader { return load(name, true); } + private T loadRaw(String name) + { + lock.lock(); + for (File i : getFolders(name)) { + //noinspection ConstantConditions + for (File j : i.listFiles()) { + if (j.isFile() && j.getName().endsWith(".json") && j.getName().split("\\Q.\\E")[0].equals(name)) { + return loadFile(j, name); + } + } + + File file = new File(i, name + ".json"); + + if (file.exists()) { + return loadFile(file, name); + } + } + + lock.unlock(); + return null; + } + public T load(String name, boolean warn) { if (name == null) { return null; @@ -251,33 +277,7 @@ public class ResourceLoader { return null; } - String key = name + "-" + cname; - - if (loadCache.containsKey(key)) { - return loadCache.get(key); - } - - lock.lock(); - for (File i : getFolders(name)) { - for (File j : i.listFiles()) { - if (j.isFile() && j.getName().endsWith(".json") && j.getName().split("\\Q.\\E")[0].equals(name)) { - return loadFile(j, key, name); - } - } - - File file = new File(i, name + ".json"); - - if (file.exists()) { - return loadFile(file, key, name); - } - } - - if (warn && !resourceTypeName.equals("Dimension")) { - J.a(() -> Iris.warn("Couldn't find " + resourceTypeName + ": " + name)); - } - - lock.unlock(); - return null; + return loadCache.get(name); } public KList getFolders() { @@ -323,7 +323,7 @@ public class ResourceLoader { public void clearCache() { lock.lock(); possibleKeys = null; - loadCache.clear(); + loadCache.invalidate(); folderCache = null; lock.unlock(); } @@ -347,7 +347,7 @@ public class ResourceLoader { } public boolean isLoaded(String next) { - return loadCache.containsKey(next); + return loadCache.contains(next); } public void clearList() { @@ -377,11 +377,21 @@ public class ResourceLoader { } - public int getSize() { - return loadCache.size(); + public long getSize() { + return loadCache.getSize(); } - public int getTotalStorage() { + @Override + public long getMaxSize() { + return loadCache.getMaxSize(); + } + + @Override + public boolean isClosed() { + return getManager().isClosed(); + } + + public long getTotalStorage() { return getSize(); } } diff --git a/src/main/java/com/volmit/iris/core/loader/ScriptResourceLoader.java b/src/main/java/com/volmit/iris/core/loader/ScriptResourceLoader.java index 252709ea5..25fde01e3 100644 --- a/src/main/java/com/volmit/iris/core/loader/ScriptResourceLoader.java +++ b/src/main/java/com/volmit/iris/core/loader/ScriptResourceLoader.java @@ -19,34 +19,35 @@ package com.volmit.iris.core.loader; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; import com.volmit.iris.engine.object.IrisScript; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.data.KCache; import com.volmit.iris.util.io.IO; import com.volmit.iris.util.scheduling.PrecisionStopwatch; import java.io.File; public class ScriptResourceLoader extends ResourceLoader { - public ScriptResourceLoader(File root, IrisData idm, String folderName, String resourceTypeName) { super(root, idm, folderName, resourceTypeName, IrisScript.class); + loadCache = new KCache<>(this::loadRaw, IrisSettings.get().getPerformance().getMaxScriptLoaderCacheSize()); } public boolean supportsSchemas() { return false; } - public int getSize() { - return loadCache.size(); + public long getSize() { + return loadCache.getSize(); } - public IrisScript loadFile(File j, String key, String name) { + protected IrisScript loadFile(File j, String name) { lock.lock(); try { PrecisionStopwatch p = PrecisionStopwatch.start(); IrisScript t = new IrisScript(IO.readAll(j)); - loadCache.put(key, t); t.setLoadKey(name); t.setLoader(manager); t.setLoadFile(j); @@ -119,20 +120,14 @@ public class ScriptResourceLoader extends ResourceLoader { return null; } - public IrisScript load(String name, boolean warn) { - String key = name + "-" + objectClass.getCanonicalName(); - - if (loadCache.containsKey(key)) { - IrisScript t = loadCache.get(key); - return t; - } - + private IrisScript loadRaw(String name) + { lock.lock(); for (File i : getFolders(name)) { for (File j : i.listFiles()) { if (j.isFile() && j.getName().endsWith(".js") && j.getName().split("\\Q.\\E")[0].equals(name)) { lock.unlock(); - return loadFile(j, key, name); + return loadFile(j, name); } } @@ -140,7 +135,7 @@ public class ScriptResourceLoader extends ResourceLoader { if (file.exists()) { lock.unlock(); - return loadFile(file, key, name); + return loadFile(file, name); } } @@ -149,4 +144,8 @@ public class ScriptResourceLoader extends ResourceLoader { lock.unlock(); return null; } + + public IrisScript load(String name, boolean warn) { + return loadCache.get(name); + } } diff --git a/src/main/java/com/volmit/iris/core/service/PreservationSVC.java b/src/main/java/com/volmit/iris/core/service/PreservationSVC.java index 4213b30e4..d054619c7 100644 --- a/src/main/java/com/volmit/iris/core/service/PreservationSVC.java +++ b/src/main/java/com/volmit/iris/core/service/PreservationSVC.java @@ -19,19 +19,27 @@ package com.volmit.iris.core.service; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.framework.MeteredCache; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.context.IrisContext; +import com.volmit.iris.util.format.Form; import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.IrisService; import com.volmit.iris.util.scheduling.Looper; +import com.volmit.iris.util.stream.utility.CachedStream2D; +import java.util.Comparator; +import java.util.Objects; import java.util.concurrent.ExecutorService; public class PreservationSVC implements IrisService { private final KList threads = new KList<>(); private final KList services = new KList<>(); private Looper dereferencer; + private final KList caches = new KList<>(); public void register(Thread t) { threads.add(t); @@ -45,11 +53,35 @@ public class PreservationSVC implements IrisService { services.add(service); } + public void printCaches() + { + long s = caches.stream().filter(i -> !i.isClosed()).mapToLong(MeteredCache::getSize).sum(); + long m = caches.stream().filter(i -> !i.isClosed()).mapToLong(MeteredCache::getMaxSize).sum(); + double p = 0; + double mf = 0; + + for(MeteredCache i : caches) + { + if(i.isClosed()) + { + continue; + } + + mf++; + p+= i.getUsage(); + } + + mf = mf == 0 ? 1 : mf; + + Iris.info("Cached " + Form.f(s) + " / " + Form.f(m) + " (" + Form.pc(p/mf) + ") from " + caches.size() + " Caches"); + } + public void dereference() { IrisContext.dereference(); IrisData.dereference(); threads.removeWhere((i) -> !i.isAlive()); services.removeWhere(ExecutorService::isShutdown); + updateCaches(); } @Override @@ -94,4 +126,13 @@ public class PreservationSVC implements IrisService { } }); } + + public void updateCaches() + { + caches.removeWhere(MeteredCache::isClosed); + } + + public void registerCache(MeteredCache cache) { + caches.add(cache); + } } diff --git a/src/main/java/com/volmit/iris/engine/IrisComplex.java b/src/main/java/com/volmit/iris/engine/IrisComplex.java index 94f380c0e..9706bd379 100644 --- a/src/main/java/com/volmit/iris/engine/IrisComplex.java +++ b/src/main/java/com/volmit/iris/engine/IrisComplex.java @@ -89,7 +89,7 @@ public class IrisComplex implements DataProvider { } public IrisComplex(Engine engine, boolean simple) { - int cacheSize = IrisSettings.get().getPerformance().getCacheSize(); + int cacheSize = IrisSettings.get().getPerformance().getMaxStreamCacheSize(); IrisBiome emptyBiome = new IrisBiome(); UUID focusUUID = UUID.nameUUIDFromBytes("focus".getBytes()); this.rng = new RNG(engine.getSeedManager().getComplex()); @@ -124,7 +124,7 @@ public class IrisComplex implements DataProvider { Interpolated.of(a -> 0D, a -> focusRegion)) : regionStyleStream .selectRarity(engine.getDimension().getRegions(), (i) -> data.getRegionLoader().load(i)) - .convertCached((s) -> data.getRegionLoader().load(s)).cache2D(cacheSize); + .convertCached((s) -> data.getRegionLoader().load(s)).cache2D(engine, cacheSize); regionIDStream = regionIdentityStream.convertCached((i) -> new UUID(Double.doubleToLongBits(i), String.valueOf(i * 38445).hashCode() * 3245556666L)); caveBiomeStream = regionStream.convert((r) -> engine.getDimension().getCaveBiomeStyle().create(rng.nextParallelRNG(InferredType.CAVE.ordinal()), getData()).stream() @@ -139,7 +139,7 @@ public class IrisComplex implements DataProvider { return data.getBiomeLoader().load(s) .setInferredType(InferredType.CAVE); }) - ).convertAware2D(ProceduralStream::get).cache2D(cacheSize); + ).convertAware2D(ProceduralStream::get).cache2D(engine, cacheSize); inferredStreams.put(InferredType.CAVE, caveBiomeStream); landBiomeStream = regionStream.convert((r) -> engine.getDimension().getLandBiomeStyle().create(rng.nextParallelRNG(InferredType.LAND.ordinal()), getData()).stream() @@ -148,7 +148,7 @@ public class IrisComplex implements DataProvider { .convertCached((s) -> data.getBiomeLoader().load(s) .setInferredType(InferredType.LAND)) ).convertAware2D(ProceduralStream::get) - .cache2D(cacheSize); + .cache2D(engine, cacheSize); inferredStreams.put(InferredType.LAND, landBiomeStream); seaBiomeStream = regionStream.convert((r) -> engine.getDimension().getSeaBiomeStyle().create(rng.nextParallelRNG(InferredType.SEA.ordinal()), getData()).stream() @@ -157,7 +157,7 @@ public class IrisComplex implements DataProvider { .convertCached((s) -> data.getBiomeLoader().load(s) .setInferredType(InferredType.SEA)) ).convertAware2D(ProceduralStream::get) - .cache2D(cacheSize); + .cache2D(engine, cacheSize); inferredStreams.put(InferredType.SEA, seaBiomeStream); shoreBiomeStream = regionStream.convert((r) -> engine.getDimension().getShoreBiomeStyle().create(rng.nextParallelRNG(InferredType.SHORE.ordinal()), getData()).stream() @@ -165,60 +165,60 @@ public class IrisComplex implements DataProvider { .selectRarity(r.getShoreBiomes(), (i) -> data.getBiomeLoader().load(i)) .convertCached((s) -> data.getBiomeLoader().load(s) .setInferredType(InferredType.SHORE)) - ).convertAware2D(ProceduralStream::get).cache2D(cacheSize); + ).convertAware2D(ProceduralStream::get).cache2D(engine, cacheSize); inferredStreams.put(InferredType.SHORE, shoreBiomeStream); bridgeStream = focus != null ? ProceduralStream.of((x, z) -> focus.getInferredType(), Interpolated.of(a -> 0D, a -> focus.getInferredType())) : engine.getDimension().getContinentalStyle().create(rng.nextParallelRNG(234234565), getData()) .bake().scale(1D / engine.getDimension().getContinentZoom()).bake().stream() - .convert((v) -> v >= engine.getDimension().getLandChance() ? InferredType.SEA : InferredType.LAND).cache2D(cacheSize); + .convert((v) -> v >= engine.getDimension().getLandChance() ? InferredType.SEA : InferredType.LAND).cache2D(engine, cacheSize); baseBiomeStream = focus != null ? ProceduralStream.of((x, z) -> focus, Interpolated.of(a -> 0D, a -> focus)) : bridgeStream.convertAware2D((t, x, z) -> inferredStreams.get(t).get(x, z)) - .convertAware2D(this::implode).cache2D(cacheSize); + .convertAware2D(this::implode).cache2D(engine, cacheSize); heightStream = ProceduralStream.of((x, z) -> { IrisBiome b = focus != null ? focus : baseBiomeStream.get(x, z); return getHeight(engine, b, x, z, engine.getSeedManager().getHeight()); - }, Interpolated.DOUBLE).clamp(0, engine.getHeight()).cache2D(cacheSize); + }, Interpolated.DOUBLE).clamp(0, engine.getHeight()).cache2D(engine, cacheSize, true); roundedHeighteightStream = heightStream.round(); - slopeStream = heightStream.slope(3).cache2D(cacheSize); + slopeStream = heightStream.slope(3).cache2D(engine, cacheSize); trueBiomeStream = focus != null ? ProceduralStream.of((x, y) -> focus, Interpolated.of(a -> 0D, b -> focus)) - .cache2D(cacheSize) : heightStream + .cache2D(engine, cacheSize) : heightStream .convertAware2D((h, x, z) -> fixBiomeType(h, baseBiomeStream.get(x, z), regionStream.get(x, z), x, z, fluidHeight)) - .cache2D(cacheSize); + .cache2D(engine, cacheSize); trueBiomeStream = focus != null ? ProceduralStream.of((x, y) -> focus, Interpolated.of(a -> 0D, b -> focus)) - .cache2D(cacheSize) : heightStream + .cache2D(engine, cacheSize) : heightStream .convertAware2D((h, x, z) -> fixBiomeType(h, baseBiomeStream.get(x, z), regionStream.get(x, z), x, z, fluidHeight)) - .cache2D(cacheSize); - trueBiomeDerivativeStream = trueBiomeStream.convert(IrisBiome::getDerivative).cache2D(cacheSize); - heightFluidStream = heightStream.max(fluidHeight).cache2D(cacheSize); + .cache2D(engine, cacheSize); + trueBiomeDerivativeStream = trueBiomeStream.convert(IrisBiome::getDerivative).cache2D(engine, cacheSize); + heightFluidStream = heightStream.max(fluidHeight).cache2D(engine, cacheSize, true); maxHeightStream = ProceduralStream.ofDouble((x, z) -> height); terrainSurfaceDecoration = trueBiomeStream - .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)).cache2D(cacheSize); + .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)).cache2D(engine, cacheSize); terrainCeilingDecoration = trueBiomeStream - .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.CEILING)).cache2D(cacheSize); + .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.CEILING)).cache2D(engine, cacheSize); terrainCaveSurfaceDecoration = caveBiomeStream - .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)).cache2D(cacheSize); + .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)).cache2D(engine, cacheSize); terrainCaveCeilingDecoration = caveBiomeStream - .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.CEILING)).cache2D(cacheSize); + .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.CEILING)).cache2D(engine, cacheSize); shoreSurfaceDecoration = trueBiomeStream - .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SHORE_LINE)).cache2D(cacheSize); + .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SHORE_LINE)).cache2D(engine, cacheSize); seaSurfaceDecoration = trueBiomeStream - .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SEA_SURFACE)).cache2D(cacheSize); + .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SEA_SURFACE)).cache2D(engine, cacheSize); seaFloorDecoration = trueBiomeStream - .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SEA_FLOOR)).cache2D(cacheSize); + .convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SEA_FLOOR)).cache2D(engine, cacheSize); baseBiomeIDStream = trueBiomeStream.convertAware2D((b, x, z) -> { UUID d = regionIDStream.get(x, z); return new UUID(b.getLoadKey().hashCode() * 818223L, d.hashCode()); }) - .cache2D(cacheSize); + .cache2D(engine, cacheSize); //@done } diff --git a/src/main/java/com/volmit/iris/engine/framework/MeteredCache.java b/src/main/java/com/volmit/iris/engine/framework/MeteredCache.java new file mode 100644 index 000000000..cd9dcc0fa --- /dev/null +++ b/src/main/java/com/volmit/iris/engine/framework/MeteredCache.java @@ -0,0 +1,33 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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 MeteredCache +{ + long getSize(); + + long getMaxSize(); + + default double getUsage() + { + return (double)getSize() / (double)getMaxSize(); + } + + boolean isClosed(); +} diff --git a/src/main/java/com/volmit/iris/engine/modifier/IrisCarveModifier.java b/src/main/java/com/volmit/iris/engine/modifier/IrisCarveModifier.java index d063cae49..d711512ab 100644 --- a/src/main/java/com/volmit/iris/engine/modifier/IrisCarveModifier.java +++ b/src/main/java/com/volmit/iris/engine/modifier/IrisCarveModifier.java @@ -39,6 +39,7 @@ import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.matter.MatterCavern; import com.volmit.iris.util.matter.slices.MarkerMatter; import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import com.volmit.iris.util.stream.utility.CachedStream2D; import lombok.Data; import org.bukkit.Material; import org.bukkit.block.data.BlockData; diff --git a/src/main/java/com/volmit/iris/util/data/KCache.java b/src/main/java/com/volmit/iris/util/data/KCache.java new file mode 100644 index 000000000..2246d3c51 --- /dev/null +++ b/src/main/java/com/volmit/iris/util/data/KCache.java @@ -0,0 +1,82 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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.util.data; + +import com.github.benmanes.caffeine.cache.CacheLoader; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.volmit.iris.engine.framework.MeteredCache; + +import java.util.function.Function; + +public class KCache implements MeteredCache { + private long max; + private CacheLoader loader; + private LoadingCache cache; + + public KCache(CacheLoader loader, long max) + { + this.max = max; + this.loader = loader; + this.cache = Caffeine + .newBuilder() + .maximumSize(max) + .build((k) -> loader == null ? null : loader.load(k)); + } + + public void setLoader(CacheLoader loader) + { + this.loader = loader; + } + + public void invalidate(K k) + { + cache.invalidate(k); + } + + public void invalidate() + { + cache.invalidateAll(); + cache.cleanUp(); + } + + public V get(K k) + { + return cache.get(k); + } + + @Override + public long getSize() { + return cache.estimatedSize(); + } + + @Override + public long getMaxSize() { + return max; + } + + @Override + public boolean isClosed() { + return false; + } + + public boolean contains(K next) { + return cache.getIfPresent(next) != null; + } +} diff --git a/src/main/java/com/volmit/iris/util/stream/ProceduralStream.java b/src/main/java/com/volmit/iris/util/stream/ProceduralStream.java index b7ffa53f8..f39284770 100644 --- a/src/main/java/com/volmit/iris/util/stream/ProceduralStream.java +++ b/src/main/java/com/volmit/iris/util/stream/ProceduralStream.java @@ -20,6 +20,7 @@ package com.volmit.iris.util.stream; import com.volmit.iris.Iris; import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IRare; import com.volmit.iris.engine.object.IrisStyledRange; import com.volmit.iris.util.collection.KList; @@ -287,8 +288,12 @@ public interface ProceduralStream extends ProceduralLayer, Interpolated { return new To3DStream(this); } - default ProceduralStream cache2D(int maxSize) { - return new CachedStream2D(this, maxSize); + default ProceduralStream cache2D(Engine engine, int maxSize) { + return cache2D(engine, maxSize, false); + } + + default ProceduralStream cache2D(Engine engine, int maxSize, boolean weak) { + return new CachedStream2D(engine, this, maxSize, weak); } default ProceduralStream cache3D(int maxSize) { diff --git a/src/main/java/com/volmit/iris/util/stream/utility/CachedStream2D.java b/src/main/java/com/volmit/iris/util/stream/utility/CachedStream2D.java index bee54f896..fa6f4e2fd 100644 --- a/src/main/java/com/volmit/iris/util/stream/utility/CachedStream2D.java +++ b/src/main/java/com/volmit/iris/util/stream/utility/CachedStream2D.java @@ -18,23 +18,30 @@ package com.volmit.iris.util.stream.utility; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.service.PreservationSVC; import com.volmit.iris.engine.data.cache.Cache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.framework.MeteredCache; +import com.volmit.iris.util.data.KCache; import com.volmit.iris.util.stream.BasicStream; import com.volmit.iris.util.stream.ProceduralStream; -public class CachedStream2D extends BasicStream implements ProceduralStream { +public class CachedStream2D extends BasicStream implements ProceduralStream, MeteredCache { private final ProceduralStream stream; - private final ConcurrentLinkedHashMap cache; + private final KCache cache; + private final Engine engine; - public CachedStream2D(ProceduralStream stream, int size) { + public CachedStream2D(Engine engine, ProceduralStream stream, int size, boolean weak) { super(); this.stream = stream; - cache = new ConcurrentLinkedHashMap.Builder() - .initialCapacity(size) - .maximumWeightedCapacity(size) - .concurrencyLevel(32) - .build(); + this.engine = engine; + cache = new KCache<>(k -> stream.get(Cache.keyX(k), Cache.keyZ(k)), size); + Iris.service(PreservationSVC.class).registerCache(this); } @Override @@ -49,11 +56,26 @@ public class CachedStream2D extends BasicStream implements ProceduralStrea @Override public T get(double x, double z) { - return cache.compute(Cache.key((int) x, (int) z), (k, v) -> v != null ? v : stream.get((int) x, (int) z)); + return cache.get(Cache.key((int)x, (int)z)); } @Override public T get(double x, double y, double z) { return stream.get(x, y, z); } + + @Override + public long getSize() { + return cache.getSize(); + } + + @Override + public long getMaxSize() { + return cache.getMaxSize(); + } + + @Override + public boolean isClosed() { + return engine.isClosed(); + } }