9
0
mirror of https://github.com/VolmitSoftware/Iris.git synced 2025-12-19 15:09:18 +00:00

reintroduce native threads for the updater as a setting and cleanup

This commit is contained in:
Julian Krings
2025-11-26 20:55:48 +01:00
parent 6373dbb1b8
commit cd179b4321
10 changed files with 172 additions and 93 deletions

View File

@@ -58,6 +58,7 @@ import com.volmit.iris.util.parallel.MultiBurst;
import com.volmit.iris.util.plugin.IrisService; import com.volmit.iris.util.plugin.IrisService;
import com.volmit.iris.util.plugin.VolmitPlugin; import com.volmit.iris.util.plugin.VolmitPlugin;
import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.plugin.VolmitSender;
import com.volmit.iris.util.plugin.chunk.ChunkTickets;
import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.J;
import com.volmit.iris.util.scheduling.Queue; import com.volmit.iris.util.scheduling.Queue;
import com.volmit.iris.util.scheduling.ShurikenQueue; import com.volmit.iris.util.scheduling.ShurikenQueue;
@@ -95,6 +96,7 @@ public class Iris extends VolmitPlugin implements Listener {
public static MultiverseCoreLink linkMultiverseCore; public static MultiverseCoreLink linkMultiverseCore;
public static IrisCompat compat; public static IrisCompat compat;
public static FileWatcher configWatcher; public static FileWatcher configWatcher;
public static ChunkTickets tickets;
private static VolmitSender sender; private static VolmitSender sender;
private static Thread shutdownHook; private static Thread shutdownHook;
@@ -450,6 +452,7 @@ public class Iris extends VolmitPlugin implements Listener {
IrisSafeguard.IrisSafeguardSystem(); IrisSafeguard.IrisSafeguardSystem();
getSender().setTag(getTag()); getSender().setTag(getTag());
IrisSafeguard.splash(true); IrisSafeguard.splash(true);
tickets = new ChunkTickets();
linkMultiverseCore = new MultiverseCoreLink(); linkMultiverseCore = new MultiverseCoreLink();
configWatcher = new FileWatcher(getDataFile("settings.json")); configWatcher = new FileWatcher(getDataFile("settings.json"));
services.values().forEach(IrisService::onEnable); services.values().forEach(IrisService::onEnable);

View File

@@ -177,6 +177,9 @@ public class IrisSettings {
@Data @Data
public static class IrisSettingsUpdater { public static class IrisSettingsUpdater {
public int maxConcurrency = 256; public int maxConcurrency = 256;
public boolean nativeThreads = false;
public double threadMultiplier = 2;
public double chunkLoadSensitivity = 0.7; public double chunkLoadSensitivity = 0.7;
public MsRange emptyMsRange = new MsRange(80, 100); public MsRange emptyMsRange = new MsRange(80, 100);
public MsRange defaultMsRange = new MsRange(20, 40); public MsRange defaultMsRange = new MsRange(20, 40);
@@ -185,6 +188,10 @@ public class IrisSettings {
return Math.max(Math.abs(maxConcurrency), 1); return Math.max(Math.abs(maxConcurrency), 1);
} }
public double getThreadMultiplier() {
return Math.min(Math.abs(threadMultiplier), 0.1);
}
public double getChunkLoadSensitivity() { public double getChunkLoadSensitivity() {
return Math.min(chunkLoadSensitivity, 0.9); return Math.min(chunkLoadSensitivity, 0.9);
} }

View File

@@ -2,16 +2,15 @@ package com.volmit.iris.core.pregenerator;
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.nms.container.Pair; import com.volmit.iris.core.service.PreservationSVC;
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.framework.Engine; import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.format.Form; import com.volmit.iris.util.format.Form;
import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.mantle.flag.MantleFlag;
import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.M;
import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.math.Position2;
import com.volmit.iris.util.math.RollingSequence; import com.volmit.iris.util.math.RollingSequence;
import com.volmit.iris.util.plugin.chunk.TicketHolder;
import com.volmit.iris.util.profile.LoadBalancer; import com.volmit.iris.util.profile.LoadBalancer;
import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.J;
import io.papermc.lib.PaperLib; import io.papermc.lib.PaperLib;
@@ -21,7 +20,6 @@ import org.bukkit.World;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -31,7 +29,7 @@ public class ChunkUpdater {
private static final String REGION_PATH = "region" + File.separator + "r."; private static final String REGION_PATH = "region" + File.separator + "r.";
private final AtomicBoolean paused = new AtomicBoolean(); private final AtomicBoolean paused = new AtomicBoolean();
private final AtomicBoolean cancelled = new AtomicBoolean(); private final AtomicBoolean cancelled = new AtomicBoolean();
private final KMap<Long, Pair<Long, AtomicInteger>> lastUse = new KMap<>(); private final TicketHolder holder;
private final RollingSequence chunksPerSecond = new RollingSequence(5); private final RollingSequence chunksPerSecond = new RollingSequence(5);
private final AtomicInteger totalMaxChunks = new AtomicInteger(); private final AtomicInteger totalMaxChunks = new AtomicInteger();
private final AtomicInteger chunksProcessed = new AtomicInteger(); private final AtomicInteger chunksProcessed = new AtomicInteger();
@@ -40,13 +38,13 @@ public class ChunkUpdater {
private final AtomicBoolean serverEmpty = new AtomicBoolean(true); private final AtomicBoolean serverEmpty = new AtomicBoolean(true);
private final AtomicLong lastCpsTime = new AtomicLong(M.ms()); private final AtomicLong lastCpsTime = new AtomicLong(M.ms());
private final int maxConcurrency = IrisSettings.get().getUpdater().getMaxConcurrency(); private final int maxConcurrency = IrisSettings.get().getUpdater().getMaxConcurrency();
private final int coreLimit = (int) Math.max(Runtime.getRuntime().availableProcessors() * IrisSettings.get().getUpdater().getThreadMultiplier(), 1);
private final Semaphore semaphore = new Semaphore(maxConcurrency); private final Semaphore semaphore = new Semaphore(maxConcurrency);
private final LoadBalancer loadBalancer = new LoadBalancer(semaphore, maxConcurrency, IrisSettings.get().getUpdater().emptyMsRange); private final LoadBalancer loadBalancer = new LoadBalancer(semaphore, maxConcurrency, IrisSettings.get().getUpdater().emptyMsRange);
private final AtomicLong startTime = new AtomicLong(); private final AtomicLong startTime = new AtomicLong();
private final Dimensions dimensions; private final Dimensions dimensions;
private final PregenTask task; private final PregenTask task;
private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); private final ExecutorService chunkExecutor = IrisSettings.get().getUpdater().isNativeThreads() ? Executors.newFixedThreadPool(coreLimit) : Executors.newVirtualThreadPerTaskExecutor();
private final ExecutorService chunkExecutor = Executors.newVirtualThreadPerTaskExecutor();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private final CountDownLatch latch; private final CountDownLatch latch;
private final Engine engine; private final Engine engine;
@@ -55,6 +53,7 @@ public class ChunkUpdater {
public ChunkUpdater(World world) { public ChunkUpdater(World world) {
this.engine = IrisToolbelt.access(world).getEngine(); this.engine = IrisToolbelt.access(world).getEngine();
this.world = world; this.world = world;
this.holder = Iris.tickets.getHolder(world);
this.dimensions = calculateWorldDimensions(new File(world.getWorldFolder(), "region")); this.dimensions = calculateWorldDimensions(new File(world.getWorldFolder(), "region"));
this.task = dimensions.task(); this.task = dimensions.task();
this.totalMaxChunks.set(dimensions.count * 1024); this.totalMaxChunks.set(dimensions.count * 1024);
@@ -113,7 +112,6 @@ public class ChunkUpdater {
e.printStackTrace(); e.printStackTrace();
} }
}, 0, 3, TimeUnit.SECONDS); }, 0, 3, TimeUnit.SECONDS);
scheduler.scheduleAtFixedRate(this::unloadChunks, 0, 1, TimeUnit.SECONDS);
scheduler.scheduleAtFixedRate(() -> { scheduler.scheduleAtFixedRate(() -> {
boolean empty = Bukkit.getOnlinePlayers().isEmpty(); boolean empty = Bukkit.getOnlinePlayers().isEmpty();
if (serverEmpty.getAndSet(empty) == empty) if (serverEmpty.getAndSet(empty) == empty)
@@ -128,6 +126,7 @@ public class ChunkUpdater {
t.setPriority(Thread.MAX_PRIORITY); t.setPriority(Thread.MAX_PRIORITY);
t.start(); t.start();
Iris.service(PreservationSVC.class).register(t);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -140,8 +139,6 @@ public class ChunkUpdater {
chunkExecutor.shutdown(); chunkExecutor.shutdown();
chunkExecutor.awaitTermination(5, TimeUnit.SECONDS); chunkExecutor.awaitTermination(5, TimeUnit.SECONDS);
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
scheduler.shutdownNow(); scheduler.shutdownNow();
unloadAndSaveAllChunks(); unloadAndSaveAllChunks();
} catch (Exception ignored) {} } catch (Exception ignored) {}
@@ -200,20 +197,16 @@ public class ChunkUpdater {
return; return;
} }
var mc = engine.getMantle().getMantle().getChunk(x, z).use();
try { try {
Chunk c = world.getChunkAt(x, z); Chunk c = world.getChunkAt(x, z);
engine.getMantle().getMantle().getChunk(c);
engine.updateChunk(c); engine.updateChunk(c);
for (int xx = -1; xx <= 1; xx++) { removeTickets(x, z);
for (int zz = -1; zz <= 1; zz++) {
var counter = lastUse.get(Cache.key(x + xx, z + zz));
if (counter != null) counter.getB().decrementAndGet();
}
}
} finally { } finally {
chunksUpdated.incrementAndGet(); chunksUpdated.incrementAndGet();
chunksProcessed.getAndIncrement(); chunksProcessed.getAndIncrement();
mc.release();
} }
} }
@@ -235,41 +228,16 @@ public class ChunkUpdater {
for (int dz = -1; dz <= 1; dz++) { for (int dz = -1; dz <= 1; dz++) {
int xx = x + dx; int xx = x + dx;
int zz = z + dz; int zz = z + dz;
executor.submit(() -> { PaperLib.getChunkAtAsync(world, xx, zz, false, true)
try { .thenAccept(chunk -> {
Chunk c; if (chunk == null || !chunk.isGenerated()) {
try { latch.countDown();
c = PaperLib.getChunkAtAsync(world, xx, zz, false, true) generated.set(false);
.thenApply(chunk -> { return;
if (chunk != null) }
chunk.addPluginChunkTicket(Iris.instance); holder.addTicket(chunk);
return chunk; latch.countDown();
}).get(); });
} catch (InterruptedException | ExecutionException e) {
generated.set(false);
return;
}
if (c == null) {
generated.set(false);
return;
}
if (!c.isLoaded()) {
var future = J.sfut(() -> c.load(false));
if (future != null) future.join();
}
if (!PaperLib.isChunkGenerated(c.getWorld(), xx, zz))
generated.set(false);
var pair = lastUse.computeIfAbsent(Cache.key(c), k -> new Pair<>(0L, new AtomicInteger(-1)));
pair.setA(M.ms());
pair.getB().updateAndGet(i -> i == -1 ? 1 : ++i);
} finally {
latch.countDown();
}
});
} }
} }
@@ -278,27 +246,16 @@ public class ChunkUpdater {
} catch (InterruptedException e) { } catch (InterruptedException e) {
Iris.info("Interrupted while waiting for chunks to load"); Iris.info("Interrupted while waiting for chunks to load");
} }
return generated.get();
if (generated.get()) return true;
removeTickets(x, z);
return false;
} }
private synchronized void unloadChunks() { private void removeTickets(int x, int z) {
for (var key : new ArrayList<>(lastUse.keySet())) { for (int xx = -1; xx <= 1; xx++) {
if (key == null) continue; for (int zz = -1; zz <= 1; zz++) {
var pair = lastUse.get(key); holder.removeTicket(x + xx, z + zz);
if (pair == null) continue;
var lastUseTime = pair.getA();
var counter = pair.getB();
if (lastUseTime == null || counter == null)
continue;
if (M.ms() - lastUseTime >= 5000 && counter.get() == 0) {
int x = Cache.keyX(key);
int z = Cache.keyZ(key);
J.s(() -> {
world.removePluginChunkTicket(x, z, Iris.instance);
world.unloadChunk(x, z);
lastUse.remove(key);
});
} }
} }
} }
@@ -311,7 +268,6 @@ public class ChunkUpdater {
return; return;
} }
unloadChunks();
world.save(); world.save();
}).get(); }).get();
} catch (Throwable e) { } catch (Throwable e) {

View File

@@ -136,12 +136,12 @@ public class IrisEngineSVC implements IrisService {
@Override @Override
protected long loop() { protected long loop() {
try { try {
queuedTectonicPlates.set(0); int queuedPlates = 0;
tectonicPlates.set(0); int totalPlates = 0;
loadedChunks.set(0); long chunks = 0;
unloaderAlive.set(0); int unloaders = 0;
trimmerAlive.set(0); int trimmers = 0;
totalWorlds.set(0); int iris = 0;
double maxDuration = Long.MIN_VALUE; double maxDuration = Long.MIN_VALUE;
double minDuration = Long.MAX_VALUE; double minDuration = Long.MAX_VALUE;
@@ -149,23 +149,30 @@ public class IrisEngineSVC implements IrisService {
var registered = entry.getValue(); var registered = entry.getValue();
if (registered.closed) continue; if (registered.closed) continue;
totalWorlds.incrementAndGet(); iris++;
unloaderAlive.addAndGet(registered.unloaderAlive() ? 1 : 0); if (registered.unloaderAlive()) unloaders++;
trimmerAlive.addAndGet(registered.trimmerAlive() ? 1 : 0); if (registered.trimmerAlive()) trimmers++;
var engine = registered.getEngine(); var engine = registered.getEngine();
if (engine == null) continue; if (engine == null) continue;
queuedTectonicPlates.addAndGet((int) engine.getMantle().getUnloadRegionCount()); queuedPlates += engine.getMantle().getUnloadRegionCount();
tectonicPlates.addAndGet(engine.getMantle().getLoadedRegionCount()); totalPlates += engine.getMantle().getLoadedRegionCount();
loadedChunks.addAndGet(entry.getKey().getLoadedChunks().length); chunks += entry.getKey().getLoadedChunks().length;
double duration = engine.getMantle().getAdjustedIdleDuration(); double duration = engine.getMantle().getAdjustedIdleDuration();
if (duration > maxDuration) maxDuration = duration; if (duration > maxDuration) maxDuration = duration;
if (duration < minDuration) minDuration = duration; if (duration < minDuration) minDuration = duration;
} }
trimmerAlive.set(trimmers);
unloaderAlive.set(unloaders);
tectonicPlates.set(totalPlates);
queuedTectonicPlates.set(queuedPlates);
maxIdleDuration.set(maxDuration); maxIdleDuration.set(maxDuration);
minIdleDuration.set(minDuration); minIdleDuration.set(minDuration);
loadedChunks.set(chunks);
totalWorlds.set(iris);
worlds.values().forEach(Registered::update); worlds.values().forEach(Registered::update);
} catch (Throwable e) { } catch (Throwable e) {

View File

@@ -90,6 +90,7 @@ public class WandSVC implements IrisService {
int total = c.getSizeX() * c.getSizeY() * c.getSizeZ(); int total = c.getSizeX() * c.getSizeY() * c.getSizeZ();
var latch = new CountDownLatch(1); var latch = new CountDownLatch(1);
var holder = Iris.tickets.getHolder(p.getWorld());
new Job() { new Job() {
private int i; private int i;
private Chunk chunk; private Chunk chunk;
@@ -108,7 +109,7 @@ public class WandSVC implements IrisService {
while (time > M.ms()) { while (time > M.ms()) {
if (!it.hasNext()) { if (!it.hasNext()) {
if (chunk != null) { if (chunk != null) {
chunk.removePluginChunkTicket(Iris.instance); holder.removeTicket(chunk);
chunk = null; chunk = null;
} }
@@ -122,9 +123,10 @@ public class WandSVC implements IrisService {
var bChunk = b.getChunk(); var bChunk = b.getChunk();
if (chunk == null) { if (chunk == null) {
chunk = bChunk; chunk = bChunk;
chunk.addPluginChunkTicket(Iris.instance); holder.addTicket(chunk);
} else if (chunk != bChunk) { } else if (chunk != bChunk) {
chunk.removePluginChunkTicket(Iris.instance); holder.removeTicket(chunk);
holder.addTicket(bChunk);
chunk = bChunk; chunk = bChunk;
} }

View File

@@ -440,7 +440,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
//INMS.get().injectBiomesFromMantle(e, getMantle()); //INMS.get().injectBiomesFromMantle(e, getMantle());
if (!IrisSettings.get().getGenerator().earlyCustomBlocks) return; if (!IrisSettings.get().getGenerator().earlyCustomBlocks) return;
e.addPluginChunkTicket(Iris.instance); Iris.tickets.addTicket(e);
J.s(() -> { J.s(() -> {
var chunk = getMantle().getChunk(e).use(); var chunk = getMantle().getChunk(e).use();
int minY = getTarget().getWorld().minHeight(); int minY = getTarget().getWorld().minHeight();
@@ -452,7 +452,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
}); });
} finally { } finally {
chunk.release(); chunk.release();
e.removePluginChunkTicket(Iris.instance); Iris.tickets.removeTicket(e);
} }
}, RNG.r.i(20, 60)); }, RNG.r.i(20, 60));
} }

View File

@@ -249,7 +249,7 @@ public interface EngineMantle extends MatterGenerator {
} }
} }
default long getUnloadRegionCount() { default int getUnloadRegionCount() {
return getMantle().getUnloadRegionCount(); return getMantle().getUnloadRegionCount();
} }

View File

@@ -207,7 +207,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
Chunk c = PaperLib.getChunkAtAsync(world, x, z) Chunk c = PaperLib.getChunkAtAsync(world, x, z)
.thenApply(d -> { .thenApply(d -> {
d.addPluginChunkTicket(Iris.instance); Iris.tickets.addTicket(d);
for (Entity ee : d.getEntities()) { for (Entity ee : d.getEntities()) {
if (ee instanceof Player) { if (ee instanceof Player) {
@@ -217,7 +217,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
ee.remove(); ee.remove();
} }
engine.getWorldManager().onChunkLoad(d, false);
return d; return d;
}).get(); }).get();
@@ -243,7 +242,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRunAsync(() -> { .thenRunAsync(() -> {
c.removePluginChunkTicket(Iris.instance); Iris.tickets.removeTicket(c);
engine.getWorldManager().onChunkLoad(c, true); engine.getWorldManager().onChunkLoad(c, true);
}, syncExecutor) }, syncExecutor)
.get(); .get();

View File

@@ -0,0 +1,57 @@
package com.volmit.iris.util.plugin.chunk;
import com.volmit.iris.Iris;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import java.util.HashMap;
import java.util.Map;
public class ChunkTickets implements Listener {
private final Map<World, TicketHolder> holders = new HashMap<>();
public ChunkTickets() {
Iris.instance.registerListener(this);
Bukkit.getWorlds().forEach(w -> holders.put(w, new TicketHolder(w)));
}
public TicketHolder getHolder(@NonNull World world) {
return holders.get(world);
}
public void addTicket(@NonNull Chunk chunk) {
addTicket(chunk.getWorld(), chunk.getX(), chunk.getZ());
}
public void addTicket(@NonNull World world, int x, int z) {
var holder = getHolder(world);
if (holder != null) holder.addTicket(x, z);
}
public boolean removeTicket(@NonNull Chunk chunk) {
return removeTicket(chunk.getWorld(), chunk.getX(), chunk.getZ());
}
public boolean removeTicket(@NonNull World world, int x, int z) {
var holder = getHolder(world);
if (holder != null) return holder.removeTicket(x, z);
return false;
}
@EventHandler(priority = EventPriority.LOWEST)
public void on(@NonNull WorldLoadEvent event) {
holders.put(event.getWorld(), new TicketHolder(event.getWorld()));
}
@EventHandler(priority = EventPriority.MONITOR)
public void on(@NonNull WorldUnloadEvent event) {
holders.remove(event.getWorld());
}
}

View File

@@ -0,0 +1,48 @@
package com.volmit.iris.util.plugin.chunk;
import com.volmit.iris.Iris;
import com.volmit.iris.engine.data.cache.Cache;
import com.volmit.iris.util.collection.KMap;
import lombok.NonNull;
import org.bukkit.Chunk;
import org.bukkit.World;
public class TicketHolder {
private final World world;
private final KMap<Long, Long> tickets = new KMap<>();
public TicketHolder(@NonNull World world) {
this.world = world;
}
public void addTicket(@NonNull Chunk chunk) {
if (chunk.getWorld() != world) return;
addTicket(chunk.getX(), chunk.getZ());
}
public void addTicket(int x, int z) {
tickets.compute(Cache.key(x, z), ($, ref) -> {
if (ref == null) {
world.addPluginChunkTicket(x, z, Iris.instance);
return 1L;
}
return ++ref;
});
}
public boolean removeTicket(@NonNull Chunk chunk) {
if (chunk.getWorld() != world) return false;
return removeTicket(chunk.getX(), chunk.getZ());
}
public boolean removeTicket(int x, int z) {
return tickets.compute(Cache.key(x, z), ($, ref) -> {
if (ref == null) return null;
if (--ref <= 0) {
world.removePluginChunkTicket(x, z, Iris.instance);
return null;
}
return ref;
}) == null;
}
}