implement engine services and other improvements

This commit is contained in:
Julian Krings 2024-08-07 13:40:17 +02:00
parent 90bab2b292
commit 3b98b20f73
16 changed files with 428 additions and 566 deletions

View File

@ -20,12 +20,12 @@ package com.volmit.iris.core.commands;
import com.volmit.iris.Iris; import com.volmit.iris.Iris;
import com.volmit.iris.core.loader.IrisData; 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.IrisPackBenchmarking;
import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.core.tools.IrisWorldDump; import com.volmit.iris.core.tools.IrisWorldDump;
import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisDimension; 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.DecreeExecutor;
import com.volmit.iris.util.decree.DecreeOrigin; import com.volmit.iris.util.decree.DecreeOrigin;
import com.volmit.iris.util.decree.annotations.Decree; import com.volmit.iris.util.decree.annotations.Decree;
@ -50,8 +50,6 @@ import java.io.*;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.util.*; import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
@ -60,55 +58,22 @@ public class CommandDeveloper implements DecreeExecutor {
private CommandTurboPregen turboPregen; private CommandTurboPregen turboPregen;
private CommandUpdater updater; 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() { public void EngineStatus() {
List<World> IrisWorlds = new ArrayList<>(); var status = EngineStatusSVC.getStatus();
int TotalLoadedChunks = 0;
int TotalQueuedTectonicPlates = 0;
int TotalNotQueuedTectonicPlates = 0;
int TotalTectonicPlates = 0;
long lowestUnloadDuration = 0; sender().sendMessage("-------------------------");
long highestUnloadDuration = 0; sender().sendMessage(C.DARK_PURPLE + "Engine Status");
sender().sendMessage(C.DARK_PURPLE + "Total Engines: " + C.LIGHT_PURPLE + status.engineCount());
for (World world : Bukkit.getWorlds()) { sender().sendMessage(C.DARK_PURPLE + "Total Loaded Chunks: " + C.LIGHT_PURPLE + status.loadedChunks());
try { sender().sendMessage(C.DARK_PURPLE + "Tectonic Limit: " + C.LIGHT_PURPLE + status.tectonicLimit());
if (IrisToolbelt.access(world).getEngine() != null) { sender().sendMessage(C.DARK_PURPLE + "Tectonic Total Plates: " + C.LIGHT_PURPLE + status.tectonicPlates());
IrisWorlds.add(world); 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());
} catch (Exception e) { sender().sendMessage(C.DARK_PURPLE + "Lowest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(status.minTectonicUnloadDuration()));
// no 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("-------------------------");
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("-------------------------");
} }
@Decree(description = "Test") @Decree(description = "Test")
@ -126,7 +91,7 @@ public class CommandDeveloper implements DecreeExecutor {
for (File i : Objects.requireNonNull(tectonicplates.listFiles())) { for (File i : Objects.requireNonNull(tectonicplates.listFiles())) {
TectonicPlate.read(maxHeight, i); TectonicPlate.read(maxHeight, i);
c++; 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") @Param(description = "Headless", defaultValue = "true")
boolean headless, boolean headless,
@Param(description = "GUI", defaultValue = "false") @Param(description = "GUI", defaultValue = "false")
boolean gui boolean gui,
@Param(description = "Diameter in regions", defaultValue = "5")
int diameter
) { ) {
Iris.info("test"); int rb = diameter << 9;
IrisPackBenchmarking benchmark = new IrisPackBenchmarking(dimension, 1, headless, gui); Iris.info("Benchmarking pack " + dimension.getName() + " with diameter: " + rb + "(" + diameter + ")");
IrisPackBenchmarking benchmark = new IrisPackBenchmarking(dimension, diameter, headless, gui);
benchmark.runBenchmark(); benchmark.runBenchmark();
} }
@Decree(description = "test") @Decree(description = "test")
@ -235,9 +202,8 @@ public class CommandDeveloper implements DecreeExecutor {
Engine engine = IrisToolbelt.access(world).getEngine(); Engine engine = IrisToolbelt.access(world).getEngine();
if(engine != null) { if(engine != null) {
int height = engine.getTarget().getHeight(); int height = engine.getTarget().getHeight();
ExecutorService service = Executors.newFixedThreadPool(1);
VolmitSender sender = sender(); VolmitSender sender = sender();
service.submit(() -> { new Thread(() -> {
try { try {
DataInputStream raw = new DataInputStream(new FileInputStream(file)); DataInputStream raw = new DataInputStream(new FileInputStream(file));
TectonicPlate plate = new TectonicPlate(height, raw); TectonicPlate plate = new TectonicPlate(height, raw);
@ -271,8 +237,7 @@ public class CommandDeveloper implements DecreeExecutor {
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
} }
}); }, "Compression Test").start();
service.shutdown();
} else { } else {
Iris.info(C.RED + "Engine is null!"); Iris.info(C.RED + "Engine is null!");
} }

View File

@ -9,7 +9,7 @@ import java.io.Closeable;
public interface IHeadless extends Closeable { public interface IHeadless extends Closeable {
void save(); int getLoadedChunks();
@ChunkCoordinates @ChunkCoordinates
boolean exists(int x, int z); boolean exists(int x, int z);

View File

@ -37,7 +37,6 @@ public class HeadlessPregenMethod implements PregeneratorMethod {
try { try {
semaphore.acquire(max); semaphore.acquire(max);
} catch (InterruptedException ignored) {} } catch (InterruptedException ignored) {}
headless.save();
try { try {
headless.close(); headless.close();
} catch (IOException e) { } catch (IOException e) {
@ -47,9 +46,7 @@ public class HeadlessPregenMethod implements PregeneratorMethod {
} }
@Override @Override
public void save() { public void save() {}
headless.save();
}
@Override @Override
public boolean supportsRegions(int x, int z, PregenListener listener) { public boolean supportsRegions(int x, int z, PregenListener listener) {

View File

@ -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<World, Long> lastUse;
private List<World> 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<Engine> 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<Engine> 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<Engine> createSupplier() {
AtomicInteger i = new AtomicInteger();
return () -> {
List<World> 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();
}
}

View File

@ -96,7 +96,7 @@ public class IrisPackBenchmarking {
File profilers = new File("plugins" + File.separator + "Iris" + File.separator + "packbenchmarks"); File profilers = new File("plugins" + File.separator + "Iris" + File.separator + "packbenchmarks");
profilers.mkdir(); 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(); results.getParentFile().mkdirs();
KMap<String, Double> metrics = engine.getMetrics().pull(); KMap<String, Double> metrics = engine.getMetrics().pull();
try (FileWriter writer = new FileWriter(results)) { try (FileWriter writer = new FileWriter(results)) {
@ -124,7 +124,9 @@ public class IrisPackBenchmarking {
e.printStackTrace(); e.printStackTrace();
} }
Bukkit.getServer().unloadWorld("benchmark", true); if (headless) engine.close();
else J.s(() -> Bukkit.unloadWorld("benchmark", true));
stopwatch.end(); stopwatch.end();
} catch (Exception e) { } catch (Exception e) {
Iris.error("Something has gone wrong!"); Iris.error("Something has gone wrong!");
@ -171,8 +173,8 @@ public class IrisPackBenchmarking {
.builder() .builder()
.gui(gui) .gui(gui)
.center(new Position2(x, z)) .center(new Position2(x, z))
.width(5) .width(radius)
.height(5) .height(radius)
.build(), headless ? new HeadlessPregenMethod(engine) : new HybridPregenMethod(engine.getWorld().realWorld(), .build(), headless ? new HeadlessPregenMethod(engine) : new HybridPregenMethod(engine.getWorld().realWorld(),
IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())), engine); IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())), engine);
} }

View File

@ -296,7 +296,8 @@ public class IrisComplex implements DataProvider {
var cache = new HashMap<DPair, IrisBiome>(); var cache = new HashMap<DPair, IrisBiome>();
double hi = interpolator.interpolate(x, z, (xx, zz) -> { double hi = interpolator.interpolate(x, z, (xx, zz) -> {
try { 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; double b = 0;
for (IrisGenerator gen : generators) { for (IrisGenerator gen : generators) {
@ -315,7 +316,7 @@ public class IrisComplex implements DataProvider {
double lo = interpolator.interpolate(x, z, (xx, zz) -> { double lo = interpolator.interpolate(x, z, (xx, zz) -> {
try { 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; double b = 0;
for (IrisGenerator gen : generators) { for (IrisGenerator gen : generators) {

View File

@ -32,6 +32,7 @@ import com.volmit.iris.engine.framework.*;
import com.volmit.iris.engine.mantle.EngineMantle; import com.volmit.iris.engine.mantle.EngineMantle;
import com.volmit.iris.engine.object.*; import com.volmit.iris.engine.object.*;
import com.volmit.iris.engine.scripting.EngineExecutionEnvironment; 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.atomics.AtomicRollingSequence;
import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.context.ChunkContext; 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.format.Form;
import com.volmit.iris.util.hunk.Hunk; import com.volmit.iris.util.hunk.Hunk;
import com.volmit.iris.util.io.IO; 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.mantle.MantleFlag;
import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.M;
import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.math.RNG;
@ -62,9 +64,9 @@ import org.bukkit.entity.Player;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.HashSet; import java.lang.reflect.Constructor;
import java.util.Set; import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.ConcurrentHashMap; import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@ -74,6 +76,8 @@ import java.util.concurrent.locks.ReentrantLock;
@EqualsAndHashCode(exclude = "context") @EqualsAndHashCode(exclude = "context")
@ToString(exclude = "context") @ToString(exclude = "context")
public class IrisEngine implements Engine { public class IrisEngine implements Engine {
private static final Map<Class<? extends IrisEngineService>, Constructor<? extends IrisEngineService>> SERVICES = scanServices();
private final KMap<Class<? extends IrisEngineService>, IrisEngineService> services;
private final AtomicInteger bud; private final AtomicInteger bud;
private final AtomicInteger buds; private final AtomicInteger buds;
private final AtomicInteger generated; private final AtomicInteger generated;
@ -94,7 +98,6 @@ public class IrisEngine implements Engine {
private final ChronoLatch cleanLatch; private final ChronoLatch cleanLatch;
private final SeedManager seedManager; private final SeedManager seedManager;
private EngineMode mode; private EngineMode mode;
private EngineEffects effects;
private EngineExecutionEnvironment execution; private EngineExecutionEnvironment execution;
private EngineWorldManager worldManager; private EngineWorldManager worldManager;
private volatile int parallelism; private volatile int parallelism;
@ -114,6 +117,7 @@ public class IrisEngine implements Engine {
getEngineData(); getEngineData();
verifySeed(); verifySeed();
this.seedManager = new SeedManager(target.getWorld().getRawWorldSeed()); this.seedManager = new SeedManager(target.getWorld().getRawWorldSeed());
services = new KMap<>();
dataLock = new ReentrantLock(); dataLock = new ReentrantLock();
bud = new AtomicInteger(0); bud = new AtomicInteger(0);
buds = new AtomicInteger(0); buds = new AtomicInteger(0);
@ -154,17 +158,17 @@ public class IrisEngine implements Engine {
bud.set(0); bud.set(0);
} }
if (effects != null) { var effects = getService(EngineEffectsSVC.class);
effects.tickRandomPlayer(); if (effects != null) effects.tickRandomPlayer();
}
} }
private void prehotload() { private void prehotload() {
worldManager.close(); worldManager.close();
complex.close(); complex.close();
execution.close(); execution.close();
effects.close();
mode.close(); mode.close();
services.values().forEach(s -> s.onDisable(true));
services.values().forEach(Iris.instance::unregisterListener);
J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace()); J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace());
} }
@ -173,10 +177,25 @@ public class IrisEngine implements Engine {
try { try {
Iris.debug("Setup Engine " + getCacheID()); Iris.debug("Setup Engine " + getCacheID());
cacheId = RNG.r.nextInt(); 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); worldManager = new IrisWorldManager(this);
complex = new IrisComplex(this); complex = new IrisComplex(this);
execution = new IrisExecutionEnvironment(this); execution = new IrisExecutionEnvironment(this);
effects = new IrisEngineEffects(this);
setupMode(); setupMode();
J.a(this::computeBiomeMaxes); J.a(this::computeBiomeMaxes);
} catch (Throwable e) { } catch (Throwable e) {
@ -439,6 +458,12 @@ public class IrisEngine implements Engine {
PregeneratorJob.shutdownInstance(); PregeneratorJob.shutdownInstance();
closed = true; closed = true;
J.car(art); 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(); getWorldManager().close();
getTarget().close(); getTarget().close();
saveEngineData(); saveEngineData();
@ -554,6 +579,12 @@ public class IrisEngine implements Engine {
return cacheId; return cacheId;
} }
@Override
@SuppressWarnings("unchecked")
public <T extends IrisEngineService> T getService(Class<T> clazz) {
return (T) services.get(clazz);
}
private boolean EngineSafe() { private boolean EngineSafe() {
// Todo: this has potential if done right // Todo: this has potential if done right
int EngineMCVersion = getEngineData().getStatistics().getMCVersion(); int EngineMCVersion = getEngineData().getStatistics().getMCVersion();
@ -568,4 +599,24 @@ public class IrisEngine implements Engine {
} }
return true; return true;
} }
@SuppressWarnings("unchecked")
private static Map<Class<? extends IrisEngineService>, Constructor<? extends IrisEngineService>> scanServices() {
JarScanner js = new JarScanner(Iris.instance.getJarFile(), "com.volmit.iris.engine.service");
J.attempt(js::scan);
KMap<Class<? extends IrisEngineService>, Constructor<? extends IrisEngineService>> map = new KMap<>();
js.getClasses()
.stream()
.filter(IrisEngineService.class::isAssignableFrom)
.map(c -> (Class<? extends IrisEngineService>) 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);
}
} }

View File

@ -534,8 +534,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
} }
} }
EngineEffects getEffects();
default MultiBurst burst() { default MultiBurst burst() {
return getTarget().getBurster(); return getTarget().getBurster();
} }
@ -960,4 +958,5 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
J.a(() -> getMantle().cleanupChunk(x, z)); J.a(() -> getMantle().cleanupChunk(x, z));
} }
} }
<T extends IrisEngineService> T getService(Class<T> clazz);
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.volmit.iris.engine.framework;
public interface EngineEffects extends EngineComponent {
void updatePlayerMap();
void tickRandomPlayer();
}

View File

@ -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);
}
}

View File

@ -19,6 +19,7 @@
package com.volmit.iris.engine.object; package com.volmit.iris.engine.object;
import com.volmit.iris.Iris; import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.IHeadless;
import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KList;
import lombok.*; import lombok.*;
@ -48,6 +49,7 @@ public class IrisWorld {
private long seed; private long seed;
private World.Environment environment; private World.Environment environment;
private World realWorld; private World realWorld;
private IHeadless headless;
private int minHeight; private int minHeight;
private int maxHeight; private int maxHeight;
@ -91,6 +93,10 @@ public class IrisWorld {
return realWorld != null; return realWorld != null;
} }
public boolean hasHeadless() {
return headless != null;
}
public List<Player> getPlayers() { public List<Player> getPlayers() {
if (hasRealWorld()) { if (hasRealWorld()) {

View File

@ -1,27 +1,8 @@
/* package com.volmit.iris.engine.service;
* 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 <https://www.gnu.org/licenses/>.
*/
package com.volmit.iris.engine;
import com.volmit.iris.engine.framework.Engine; 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.framework.EnginePlayer;
import com.volmit.iris.engine.object.IrisEngineService;
import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.M;
import com.volmit.iris.util.scheduling.PrecisionStopwatch; import com.volmit.iris.util.scheduling.PrecisionStopwatch;
@ -31,19 +12,28 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
public class IrisEngineEffects extends EngineAssignedComponent implements EngineEffects { public class EngineEffectsSVC extends IrisEngineService {
private final KMap<UUID, EnginePlayer> players; private KMap<UUID, EnginePlayer> players;
private final Semaphore limit; private Semaphore limit;
public IrisEngineEffects(Engine engine) { public EngineEffectsSVC(Engine engine) {
super(engine, "FX"); super(engine);
}
@Override
public void onEnable(boolean hotload) {
players = new KMap<>(); players = new KMap<>();
limit = new Semaphore(1); limit = new Semaphore(1);
} }
@Override @Override
public void onDisable(boolean hotload) {
players = null;
limit = null;
}
public void updatePlayerMap() { public void updatePlayerMap() {
List<Player> pr = getEngine().getWorld().getPlayers(); List<Player> pr = engine.getWorld().getPlayers();
if (pr == null) { if (pr == null) {
return; return;
@ -52,7 +42,7 @@ public class IrisEngineEffects extends EngineAssignedComponent implements Engine
for (Player i : pr) { for (Player i : pr) {
boolean pcc = players.containsKey(i.getUniqueId()); boolean pcc = players.containsKey(i.getUniqueId());
if (!pcc) { 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() { public void tickRandomPlayer() {
if (limit.tryAcquire()) { if (limit.tryAcquire()) {
if (M.r(0.02)) { if (M.r(0.02)) {

View File

@ -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<EngineStatusSVC> 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) {}
}

View File

@ -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) {}
}
}
}

View File

@ -21,7 +21,6 @@ package com.volmit.iris.util.mantle;
import com.google.common.util.concurrent.AtomicDouble; import com.google.common.util.concurrent.AtomicDouble;
import com.volmit.iris.Iris; import com.volmit.iris.Iris;
import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.IrisSettings;
import com.volmit.iris.core.service.IrisCleanerSVC;
import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.data.cache.Cache;
import com.volmit.iris.engine.framework.Engine; 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.BurstExecutor;
import com.volmit.iris.util.parallel.HyperLock; import com.volmit.iris.util.parallel.HyperLock;
import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.parallel.MultiBurst;
import com.volmit.iris.util.scheduling.Looper;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Chunk; import org.bukkit.Chunk;
@ -61,6 +61,7 @@ import java.util.concurrent.locks.ReentrantLock;
public class Mantle { public class Mantle {
private static final boolean disableClear = System.getProperty("disableClear", "false").equals("true"); private static final boolean disableClear = System.getProperty("disableClear", "false").equals("true");
private final File dataFolder; private final File dataFolder;
@Getter @Getter
private final int worldHeight; private final int worldHeight;
@ -425,8 +426,8 @@ public class Mantle {
ioTrim.set(true); ioTrim.set(true);
unloadLock.lock(); unloadLock.lock();
try { try {
if (lastUse != null && IrisCleanerSVC.instance != null) { if (lastUse == null || lastUse.isEmpty()) return;
if (!lastUse.isEmpty()) {
Iris.debug("Trimming Tectonic Plates older than " + Form.duration(adjustedIdleDuration.get(), 0)); Iris.debug("Trimming Tectonic Plates older than " + Form.duration(adjustedIdleDuration.get(), 0));
for (long i : new ArrayList<>(lastUse.keySet())) { for (long i : new ArrayList<>(lastUse.keySet())) {
double finalAdjustedIdleDuration = adjustedIdleDuration.get(); double finalAdjustedIdleDuration = adjustedIdleDuration.get();
@ -435,13 +436,9 @@ public class Mantle {
if (lastUseTime != null && M.ms() - lastUseTime >= finalAdjustedIdleDuration) { if (lastUseTime != null && M.ms() - lastUseTime >= finalAdjustedIdleDuration) {
toUnload.add(i); toUnload.add(i);
Iris.debug("Tectonic Region added to unload"); Iris.debug("Tectonic Region added to unload");
IrisCleanerSVC.instance.trimActiveAlive.reset();
} }
}); });
} }
}
}
} catch (Throwable e) { } catch (Throwable e) {
Iris.reportError(e); Iris.reportError(e);
} finally { } finally {
@ -454,7 +451,6 @@ public class Mantle {
AtomicInteger i = new AtomicInteger(); AtomicInteger i = new AtomicInteger();
unloadLock.lock(); unloadLock.lock();
BurstExecutor burst = null; BurstExecutor burst = null;
if (IrisCleanerSVC.instance != null) {
try { try {
KList<Long> copy = toUnload.copy(); KList<Long> copy = toUnload.copy();
if (!disableClear) toUnload.clear(); if (!disableClear) toUnload.clear();
@ -478,7 +474,6 @@ public class Mantle {
if (disableClear) toUnload.remove(id); if (disableClear) toUnload.remove(id);
i.incrementAndGet(); i.incrementAndGet();
Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(id) + " " + Cache.keyZ(id)); Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(id) + " " + Cache.keyZ(id));
IrisCleanerSVC.instance.unloadActiveAlive.reset();
} catch (IOException e) { } catch (IOException e) {
Iris.reportError(e); Iris.reportError(e);
} }
@ -496,8 +491,6 @@ public class Mantle {
} }
return i.get(); return i.get();
} }
return i.get();
}
/** /**

View File

@ -21,33 +21,32 @@ import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder;
import com.volmit.iris.util.mantle.MantleFlag; import com.volmit.iris.util.mantle.MantleFlag;
import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.math.RNG;
import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.parallel.MultiBurst;
import com.volmit.iris.util.scheduling.Looper;
import com.volmit.iris.util.scheduling.PrecisionStopwatch; import com.volmit.iris.util.scheduling.PrecisionStopwatch;
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
import net.minecraft.core.QuartPos;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor; import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.ProtoChunk; import net.minecraft.world.level.chunk.ProtoChunk;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayDeque; import java.util.concurrent.*;
import java.util.Queue; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer; import java.util.function.Consumer;
public class Headless implements IHeadless, LevelHeightAccessor { public class Headless implements IHeadless, LevelHeightAccessor {
private final NMSBinding binding; private final NMSBinding binding;
private final Engine engine; private final Engine engine;
private final RegionFileStorage storage; private final RegionFileStorage storage;
private final Queue<ProtoChunk> chunkQueue = new ArrayDeque<>(); private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final ReentrantLock saveLock = new ReentrantLock(); private final AtomicInteger loadedChunks = new AtomicInteger();
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>(); private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes = new KMap<>(); private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes = new KMap<>();
private final RNG BIOME_RNG; private final RNG BIOME_RNG;
@ -56,17 +55,9 @@ public class Headless implements IHeadless, LevelHeightAccessor {
public Headless(NMSBinding binding, Engine engine) { public Headless(NMSBinding binding, Engine engine) {
this.binding = binding; this.binding = binding;
this.engine = engine; 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()); this.BIOME_RNG = new RNG(engine.getSeedManager().getBiome());
var queueLooper = new Looper() { engine.getWorld().headless(this);
@Override
protected long loop() {
save();
return closed ? -1 : 100;
}
};
queueLooper.setName("Region Save Looper");
queueLooper.start();
var dimKey = engine.getDimension().getLoadKey(); var dimKey = engine.getDimension().getLoadKey();
for (var biome : engine.getAllBiomes()) { for (var biome : engine.getAllBiomes()) {
@ -83,6 +74,11 @@ public class Headless implements IHeadless, LevelHeightAccessor {
ServerConfigurator.dumpDataPack(); ServerConfigurator.dumpDataPack();
} }
@Override
public int getLoadedChunks() {
return loadedChunks.get();
}
/** /**
* Checks if the mca plate is fully generated or not. * 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 @Override
public void generateRegion(MultiBurst burst, int x, int z, PregenListener listener) { public void generateRegion(MultiBurst burst, int x, int z, PregenListener listener) {
if (closed) return; if (closed) return;
@ -157,6 +133,7 @@ public class Headless implements IHeadless, LevelHeightAccessor {
var pos = new ChunkPos(x, z); var pos = new ChunkPos(x, z);
ProtoChunk chunk = binding.createProtoChunk(pos, this); ProtoChunk chunk = binding.createProtoChunk(pos, this);
var tc = new MCATerrainChunk(chunk); var tc = new MCATerrainChunk(chunk);
loadedChunks.incrementAndGet();
ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc); ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc);
BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight()); BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight());
@ -166,13 +143,27 @@ public class Headless implements IHeadless, LevelHeightAccessor {
inject(engine, chunk, ctx); inject(engine, chunk, ctx);
chunk.setStatus(ChunkStatus.FULL); chunk.setStatus(ChunkStatus.FULL);
chunkQueue.add(chunk); executor.submit(saveChunk(chunk));
} catch (Throwable e) { } catch (Throwable e) {
loadedChunks.decrementAndGet();
Iris.error("Failed to generate " + x + ", " + z); Iris.error("Failed to generate " + x + ", " + z);
e.printStackTrace(); 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 @BlockCoordinates
private ChunkContext generate(Engine engine, int x, int z, Hunk<BlockData> vblocks, Hunk<org.bukkit.block.Biome> vbiomes) throws WrongEngineBroException { private ChunkContext generate(Engine engine, int x, int z, Hunk<BlockData> vblocks, Hunk<org.bukkit.block.Biome> vbiomes) throws WrongEngineBroException {
if (engine.isClosed()) { if (engine.isClosed()) {
@ -214,32 +205,33 @@ public class Headless implements IHeadless, LevelHeightAccessor {
} }
private void inject(Engine engine, ChunkAccess chunk, ChunkContext ctx) { private void inject(Engine engine, ChunkAccess chunk, ChunkContext ctx) {
var pos = chunk.getPos(); chunk.fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
for (int y = engine.getMinHeight(); y < engine.getMaxHeight(); y++) { /*
for (int x = 0; x < 16; x++) { int qX = QuartPos.fromBlock(chunk.getPos().getMinBlockX());
for (int z = 0; z < 16; z++) { int qZ = QuartPos.fromBlock(chunk.getPos().getMinBlockZ());
int wX = pos.getBlockX(x);
int wZ = pos.getBlockZ(z); for (int i = chunk.getMinSection(); i < chunk.getMaxSection(); i++) {
try { var section = chunk.getSection(chunk.getSectionIndexFromSectionY(i));
chunk.setBiome(x, y, z, getNoiseBiome(engine, ctx, x, z, wX, y, wZ)); PalettedContainer<Holder<Biome>> biomes = (PalettedContainer<Holder<Biome>>) section.getBiomes();
} catch (Throwable e) { int qY = QuartPos.fromSection(i);
Iris.error("Failed to inject biome for " + wX + ", " + y + ", " + wZ);
e.printStackTrace(); 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<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int rX, int rZ, int x, int y, int z) { private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
int m = (y - engine.getMinHeight()) << 2; int m = y - engine.getMinHeight();
IrisBiome ib = ctx == null ? IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2) :
ctx.getBiome().get(rX, rZ);
if (ib.isCustom()) { 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 { } 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 { public void close() throws IOException {
if (closed) return; if (closed) return;
try { try {
executor.shutdown();
try {
if (executor.awaitTermination(10, TimeUnit.SECONDS))
executor.shutdownNow();
} catch (InterruptedException ignored) {}
storage.close(); storage.close();
engine.getWorld().headless(null);
} finally { } finally {
closed = true; closed = true;
customBiomes.clear(); customBiomes.clear();