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:
@@ -58,6 +58,7 @@ import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.plugin.IrisService;
|
||||
import com.volmit.iris.util.plugin.VolmitPlugin;
|
||||
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.Queue;
|
||||
import com.volmit.iris.util.scheduling.ShurikenQueue;
|
||||
@@ -95,6 +96,7 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
public static MultiverseCoreLink linkMultiverseCore;
|
||||
public static IrisCompat compat;
|
||||
public static FileWatcher configWatcher;
|
||||
public static ChunkTickets tickets;
|
||||
private static VolmitSender sender;
|
||||
private static Thread shutdownHook;
|
||||
|
||||
@@ -450,6 +452,7 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
IrisSafeguard.IrisSafeguardSystem();
|
||||
getSender().setTag(getTag());
|
||||
IrisSafeguard.splash(true);
|
||||
tickets = new ChunkTickets();
|
||||
linkMultiverseCore = new MultiverseCoreLink();
|
||||
configWatcher = new FileWatcher(getDataFile("settings.json"));
|
||||
services.values().forEach(IrisService::onEnable);
|
||||
|
||||
@@ -177,6 +177,9 @@ public class IrisSettings {
|
||||
@Data
|
||||
public static class IrisSettingsUpdater {
|
||||
public int maxConcurrency = 256;
|
||||
public boolean nativeThreads = false;
|
||||
public double threadMultiplier = 2;
|
||||
|
||||
public double chunkLoadSensitivity = 0.7;
|
||||
public MsRange emptyMsRange = new MsRange(80, 100);
|
||||
public MsRange defaultMsRange = new MsRange(20, 40);
|
||||
@@ -185,6 +188,10 @@ public class IrisSettings {
|
||||
return Math.max(Math.abs(maxConcurrency), 1);
|
||||
}
|
||||
|
||||
public double getThreadMultiplier() {
|
||||
return Math.min(Math.abs(threadMultiplier), 0.1);
|
||||
}
|
||||
|
||||
public double getChunkLoadSensitivity() {
|
||||
return Math.min(chunkLoadSensitivity, 0.9);
|
||||
}
|
||||
|
||||
@@ -2,16 +2,15 @@ package com.volmit.iris.core.pregenerator;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
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.engine.data.cache.Cache;
|
||||
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.mantle.flag.MantleFlag;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
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.scheduling.J;
|
||||
import io.papermc.lib.PaperLib;
|
||||
@@ -21,7 +20,6 @@ import org.bukkit.World;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -31,7 +29,7 @@ public class ChunkUpdater {
|
||||
private static final String REGION_PATH = "region" + File.separator + "r.";
|
||||
private final AtomicBoolean paused = 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 AtomicInteger totalMaxChunks = new AtomicInteger();
|
||||
private final AtomicInteger chunksProcessed = new AtomicInteger();
|
||||
@@ -40,13 +38,13 @@ public class ChunkUpdater {
|
||||
private final AtomicBoolean serverEmpty = new AtomicBoolean(true);
|
||||
private final AtomicLong lastCpsTime = new AtomicLong(M.ms());
|
||||
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 LoadBalancer loadBalancer = new LoadBalancer(semaphore, maxConcurrency, IrisSettings.get().getUpdater().emptyMsRange);
|
||||
private final AtomicLong startTime = new AtomicLong();
|
||||
private final Dimensions dimensions;
|
||||
private final PregenTask task;
|
||||
private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
|
||||
private final ExecutorService chunkExecutor = Executors.newVirtualThreadPerTaskExecutor();
|
||||
private final ExecutorService chunkExecutor = IrisSettings.get().getUpdater().isNativeThreads() ? Executors.newFixedThreadPool(coreLimit) : Executors.newVirtualThreadPerTaskExecutor();
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
private final CountDownLatch latch;
|
||||
private final Engine engine;
|
||||
@@ -55,6 +53,7 @@ public class ChunkUpdater {
|
||||
public ChunkUpdater(World world) {
|
||||
this.engine = IrisToolbelt.access(world).getEngine();
|
||||
this.world = world;
|
||||
this.holder = Iris.tickets.getHolder(world);
|
||||
this.dimensions = calculateWorldDimensions(new File(world.getWorldFolder(), "region"));
|
||||
this.task = dimensions.task();
|
||||
this.totalMaxChunks.set(dimensions.count * 1024);
|
||||
@@ -113,7 +112,6 @@ public class ChunkUpdater {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, 0, 3, TimeUnit.SECONDS);
|
||||
scheduler.scheduleAtFixedRate(this::unloadChunks, 0, 1, TimeUnit.SECONDS);
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
boolean empty = Bukkit.getOnlinePlayers().isEmpty();
|
||||
if (serverEmpty.getAndSet(empty) == empty)
|
||||
@@ -128,6 +126,7 @@ public class ChunkUpdater {
|
||||
t.setPriority(Thread.MAX_PRIORITY);
|
||||
t.start();
|
||||
|
||||
Iris.service(PreservationSVC.class).register(t);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -140,8 +139,6 @@ public class ChunkUpdater {
|
||||
|
||||
chunkExecutor.shutdown();
|
||||
chunkExecutor.awaitTermination(5, TimeUnit.SECONDS);
|
||||
executor.shutdown();
|
||||
executor.awaitTermination(5, TimeUnit.SECONDS);
|
||||
scheduler.shutdownNow();
|
||||
unloadAndSaveAllChunks();
|
||||
} catch (Exception ignored) {}
|
||||
@@ -200,20 +197,16 @@ public class ChunkUpdater {
|
||||
return;
|
||||
}
|
||||
|
||||
var mc = engine.getMantle().getMantle().getChunk(x, z).use();
|
||||
try {
|
||||
Chunk c = world.getChunkAt(x, z);
|
||||
engine.getMantle().getMantle().getChunk(c);
|
||||
engine.updateChunk(c);
|
||||
|
||||
for (int xx = -1; xx <= 1; xx++) {
|
||||
for (int zz = -1; zz <= 1; zz++) {
|
||||
var counter = lastUse.get(Cache.key(x + xx, z + zz));
|
||||
if (counter != null) counter.getB().decrementAndGet();
|
||||
}
|
||||
}
|
||||
removeTickets(x, z);
|
||||
} finally {
|
||||
chunksUpdated.incrementAndGet();
|
||||
chunksProcessed.getAndIncrement();
|
||||
mc.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,40 +228,15 @@ public class ChunkUpdater {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
int xx = x + dx;
|
||||
int zz = z + dz;
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
Chunk c;
|
||||
try {
|
||||
c = PaperLib.getChunkAtAsync(world, xx, zz, false, true)
|
||||
.thenApply(chunk -> {
|
||||
if (chunk != null)
|
||||
chunk.addPluginChunkTicket(Iris.instance);
|
||||
return chunk;
|
||||
}).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 {
|
||||
PaperLib.getChunkAtAsync(world, xx, zz, false, true)
|
||||
.thenAccept(chunk -> {
|
||||
if (chunk == null || !chunk.isGenerated()) {
|
||||
latch.countDown();
|
||||
generated.set(false);
|
||||
return;
|
||||
}
|
||||
holder.addTicket(chunk);
|
||||
latch.countDown();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -278,27 +246,16 @@ public class ChunkUpdater {
|
||||
} catch (InterruptedException e) {
|
||||
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() {
|
||||
for (var key : new ArrayList<>(lastUse.keySet())) {
|
||||
if (key == null) continue;
|
||||
var pair = lastUse.get(key);
|
||||
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);
|
||||
});
|
||||
private void removeTickets(int x, int z) {
|
||||
for (int xx = -1; xx <= 1; xx++) {
|
||||
for (int zz = -1; zz <= 1; zz++) {
|
||||
holder.removeTicket(x + xx, z + zz);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,7 +268,6 @@ public class ChunkUpdater {
|
||||
return;
|
||||
}
|
||||
|
||||
unloadChunks();
|
||||
world.save();
|
||||
}).get();
|
||||
} catch (Throwable e) {
|
||||
|
||||
@@ -136,12 +136,12 @@ public class IrisEngineSVC implements IrisService {
|
||||
@Override
|
||||
protected long loop() {
|
||||
try {
|
||||
queuedTectonicPlates.set(0);
|
||||
tectonicPlates.set(0);
|
||||
loadedChunks.set(0);
|
||||
unloaderAlive.set(0);
|
||||
trimmerAlive.set(0);
|
||||
totalWorlds.set(0);
|
||||
int queuedPlates = 0;
|
||||
int totalPlates = 0;
|
||||
long chunks = 0;
|
||||
int unloaders = 0;
|
||||
int trimmers = 0;
|
||||
int iris = 0;
|
||||
|
||||
double maxDuration = Long.MIN_VALUE;
|
||||
double minDuration = Long.MAX_VALUE;
|
||||
@@ -149,23 +149,30 @@ public class IrisEngineSVC implements IrisService {
|
||||
var registered = entry.getValue();
|
||||
if (registered.closed) continue;
|
||||
|
||||
totalWorlds.incrementAndGet();
|
||||
unloaderAlive.addAndGet(registered.unloaderAlive() ? 1 : 0);
|
||||
trimmerAlive.addAndGet(registered.trimmerAlive() ? 1 : 0);
|
||||
iris++;
|
||||
if (registered.unloaderAlive()) unloaders++;
|
||||
if (registered.trimmerAlive()) trimmers++;
|
||||
|
||||
var engine = registered.getEngine();
|
||||
if (engine == null) continue;
|
||||
|
||||
queuedTectonicPlates.addAndGet((int) engine.getMantle().getUnloadRegionCount());
|
||||
tectonicPlates.addAndGet(engine.getMantle().getLoadedRegionCount());
|
||||
loadedChunks.addAndGet(entry.getKey().getLoadedChunks().length);
|
||||
queuedPlates += engine.getMantle().getUnloadRegionCount();
|
||||
totalPlates += engine.getMantle().getLoadedRegionCount();
|
||||
chunks += entry.getKey().getLoadedChunks().length;
|
||||
|
||||
double duration = engine.getMantle().getAdjustedIdleDuration();
|
||||
if (duration > maxDuration) maxDuration = duration;
|
||||
if (duration < minDuration) minDuration = duration;
|
||||
}
|
||||
|
||||
trimmerAlive.set(trimmers);
|
||||
unloaderAlive.set(unloaders);
|
||||
tectonicPlates.set(totalPlates);
|
||||
queuedTectonicPlates.set(queuedPlates);
|
||||
maxIdleDuration.set(maxDuration);
|
||||
minIdleDuration.set(minDuration);
|
||||
loadedChunks.set(chunks);
|
||||
totalWorlds.set(iris);
|
||||
|
||||
worlds.values().forEach(Registered::update);
|
||||
} catch (Throwable e) {
|
||||
|
||||
@@ -90,6 +90,7 @@ public class WandSVC implements IrisService {
|
||||
|
||||
int total = c.getSizeX() * c.getSizeY() * c.getSizeZ();
|
||||
var latch = new CountDownLatch(1);
|
||||
var holder = Iris.tickets.getHolder(p.getWorld());
|
||||
new Job() {
|
||||
private int i;
|
||||
private Chunk chunk;
|
||||
@@ -108,7 +109,7 @@ public class WandSVC implements IrisService {
|
||||
while (time > M.ms()) {
|
||||
if (!it.hasNext()) {
|
||||
if (chunk != null) {
|
||||
chunk.removePluginChunkTicket(Iris.instance);
|
||||
holder.removeTicket(chunk);
|
||||
chunk = null;
|
||||
}
|
||||
|
||||
@@ -122,9 +123,10 @@ public class WandSVC implements IrisService {
|
||||
var bChunk = b.getChunk();
|
||||
if (chunk == null) {
|
||||
chunk = bChunk;
|
||||
chunk.addPluginChunkTicket(Iris.instance);
|
||||
holder.addTicket(chunk);
|
||||
} else if (chunk != bChunk) {
|
||||
chunk.removePluginChunkTicket(Iris.instance);
|
||||
holder.removeTicket(chunk);
|
||||
holder.addTicket(bChunk);
|
||||
chunk = bChunk;
|
||||
}
|
||||
|
||||
|
||||
@@ -440,7 +440,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
//INMS.get().injectBiomesFromMantle(e, getMantle());
|
||||
|
||||
if (!IrisSettings.get().getGenerator().earlyCustomBlocks) return;
|
||||
e.addPluginChunkTicket(Iris.instance);
|
||||
Iris.tickets.addTicket(e);
|
||||
J.s(() -> {
|
||||
var chunk = getMantle().getChunk(e).use();
|
||||
int minY = getTarget().getWorld().minHeight();
|
||||
@@ -452,7 +452,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
});
|
||||
} finally {
|
||||
chunk.release();
|
||||
e.removePluginChunkTicket(Iris.instance);
|
||||
Iris.tickets.removeTicket(e);
|
||||
}
|
||||
}, RNG.r.i(20, 60));
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ public interface EngineMantle extends MatterGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
default long getUnloadRegionCount() {
|
||||
default int getUnloadRegionCount() {
|
||||
return getMantle().getUnloadRegionCount();
|
||||
}
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
|
||||
Chunk c = PaperLib.getChunkAtAsync(world, x, z)
|
||||
.thenApply(d -> {
|
||||
d.addPluginChunkTicket(Iris.instance);
|
||||
Iris.tickets.addTicket(d);
|
||||
|
||||
for (Entity ee : d.getEntities()) {
|
||||
if (ee instanceof Player) {
|
||||
@@ -217,7 +217,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
ee.remove();
|
||||
}
|
||||
|
||||
engine.getWorldManager().onChunkLoad(d, false);
|
||||
return d;
|
||||
}).get();
|
||||
|
||||
@@ -243,7 +242,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
|
||||
.thenRunAsync(() -> {
|
||||
c.removePluginChunkTicket(Iris.instance);
|
||||
Iris.tickets.removeTicket(c);
|
||||
engine.getWorldManager().onChunkLoad(c, true);
|
||||
}, syncExecutor)
|
||||
.get();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user