mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-07-01 23:47:21 +00:00
Merge branch 'v3.4.3' of https://github.com/VolmitSoftware/Iris into v3.4.3-dev
This commit is contained in:
commit
2976390e78
@ -25,7 +25,9 @@ import com.volmit.iris.util.json.JSONException;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -42,6 +44,7 @@ public class IrisSettings {
|
||||
private IrisSettingsConcurrency concurrency = new IrisSettingsConcurrency();
|
||||
private IrisSettingsStudio studio = new IrisSettingsStudio();
|
||||
private IrisSettingsPerformance performance = new IrisSettingsPerformance();
|
||||
private IrisSettingsUpdater updater = new IrisSettingsUpdater();
|
||||
|
||||
public static int getThreadCount(int c) {
|
||||
if (System.getProperty("os.name").toLowerCase().contains("win"))
|
||||
@ -147,6 +150,30 @@ public class IrisSettings {
|
||||
public int scriptLoaderCacheSize = 512;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsUpdater {
|
||||
public double threadMultiplier = 2;
|
||||
public double chunkLoadSensitivity = 0.7;
|
||||
public MsRange emptyMsRange = new MsRange(80, 100);
|
||||
public MsRange defaultMsRange = new MsRange(20, 40);
|
||||
|
||||
public double getThreadMultiplier() {
|
||||
return Math.min(Math.abs(threadMultiplier), 0.1);
|
||||
}
|
||||
|
||||
public double getChunkLoadSensitivity() {
|
||||
return Math.min(chunkLoadSensitivity, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class MsRange {
|
||||
public int min = 20;
|
||||
public int max = 40;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsGeneral {
|
||||
public boolean DoomsdayAnnihilationSelfDestructMode = false;
|
||||
|
@ -43,6 +43,10 @@ public class CommandUpdater implements DecreeExecutor {
|
||||
sender().sendMessage(C.GOLD + "This is not an Iris world");
|
||||
return;
|
||||
}
|
||||
if (chunkUpdater != null) {
|
||||
chunkUpdater.stop();
|
||||
}
|
||||
|
||||
chunkUpdater = new ChunkUpdater(world);
|
||||
if (sender().isPlayer()) {
|
||||
sender().sendMessage(C.GREEN + "Updating " + world.getName() + C.GRAY + " Total chunks: " + Form.f(chunkUpdater.getChunks()));
|
||||
@ -53,14 +57,7 @@ public class CommandUpdater implements DecreeExecutor {
|
||||
}
|
||||
|
||||
@Decree(description = "Pause the updater")
|
||||
public void pause(
|
||||
@Param(description = "World to pause the Updater at")
|
||||
World world
|
||||
) {
|
||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||
sender().sendMessage(C.GOLD + "This is not an Iris world");
|
||||
return;
|
||||
}
|
||||
public void pause( ) {
|
||||
if (chunkUpdater == null) {
|
||||
sender().sendMessage(C.GOLD + "You cant pause something that doesnt exist?");
|
||||
return;
|
||||
@ -68,40 +65,32 @@ public class CommandUpdater implements DecreeExecutor {
|
||||
boolean status = chunkUpdater.pause();
|
||||
if (sender().isPlayer()) {
|
||||
if (status) {
|
||||
sender().sendMessage(C.IRIS + "Paused task for: " + C.GRAY + world.getName());
|
||||
sender().sendMessage(C.IRIS + "Paused task for: " + C.GRAY + chunkUpdater.getName());
|
||||
} else {
|
||||
sender().sendMessage(C.IRIS + "Unpause task for: " + C.GRAY + world.getName());
|
||||
sender().sendMessage(C.IRIS + "Unpause task for: " + C.GRAY + chunkUpdater.getName());
|
||||
}
|
||||
} else {
|
||||
if (status) {
|
||||
Iris.info(C.IRIS + "Paused task for: " + C.GRAY + world.getName());
|
||||
Iris.info(C.IRIS + "Paused task for: " + C.GRAY + chunkUpdater.getName());
|
||||
} else {
|
||||
Iris.info(C.IRIS + "Unpause task for: " + C.GRAY + world.getName());
|
||||
Iris.info(C.IRIS + "Unpause task for: " + C.GRAY + chunkUpdater.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Stops the updater")
|
||||
public void stop(
|
||||
@Param(description = "World to stop the Updater at")
|
||||
World world
|
||||
) {
|
||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||
sender().sendMessage(C.GOLD + "This is not an Iris world");
|
||||
return;
|
||||
}
|
||||
public void stop() {
|
||||
if (chunkUpdater == null) {
|
||||
sender().sendMessage(C.GOLD + "You cant stop something that doesnt exist?");
|
||||
return;
|
||||
}
|
||||
if (sender().isPlayer()) {
|
||||
sender().sendMessage("Stopping Updater for: " + C.GRAY + world.getName());
|
||||
sender().sendMessage("Stopping Updater for: " + C.GRAY + chunkUpdater.getName());
|
||||
} else {
|
||||
Iris.info("Stopping Updater for: " + C.GRAY + world.getName());
|
||||
Iris.info("Stopping Updater for: " + C.GRAY + chunkUpdater.getName());
|
||||
}
|
||||
chunkUpdater.stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,16 +1,21 @@
|
||||
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.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.mantle.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.math.Spiraler;
|
||||
import com.volmit.iris.util.profile.LoadBalancer;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
|
||||
@ -23,53 +28,40 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class ChunkUpdater {
|
||||
private AtomicBoolean paused;
|
||||
private AtomicBoolean cancelled;
|
||||
private KMap<Chunk, Long> lastUse;
|
||||
private final RollingSequence chunksPerSecond;
|
||||
private final AtomicInteger worldheightsize;
|
||||
private final AtomicInteger worldwidthsize;
|
||||
private final AtomicInteger totalChunks;
|
||||
private final AtomicInteger totalMaxChunks;
|
||||
private final AtomicInteger totalMcaregions;
|
||||
private final AtomicInteger position;
|
||||
private AtomicInteger chunksProcessed;
|
||||
private AtomicInteger chunksUpdated;
|
||||
private AtomicLong startTime;
|
||||
private ExecutorService executor;
|
||||
private ExecutorService chunkExecutor;
|
||||
private ScheduledExecutorService scheduler;
|
||||
private CompletableFuture future;
|
||||
private CountDownLatch latch;
|
||||
private final Object pauseLock;
|
||||
private final AtomicBoolean paused = new AtomicBoolean();
|
||||
private final AtomicBoolean cancelled = new AtomicBoolean();
|
||||
private final KMap<Long, Pair<Long, AtomicInteger>> lastUse = new KMap<>();
|
||||
private final RollingSequence chunksPerSecond = new RollingSequence(5);
|
||||
private final AtomicInteger totalMaxChunks = new AtomicInteger();
|
||||
private final AtomicInteger chunksProcessed = new AtomicInteger();
|
||||
private final AtomicInteger chunksProcessedLast = new AtomicInteger();
|
||||
private final AtomicInteger chunksUpdated = new AtomicInteger();
|
||||
private final AtomicBoolean serverEmpty = new AtomicBoolean(true);
|
||||
private final AtomicLong lastCpsTime = new AtomicLong(M.ms());
|
||||
private final int coreLimit = (int) Math.max(Runtime.getRuntime().availableProcessors() * IrisSettings.get().getUpdater().getThreadMultiplier(), 1);
|
||||
private final Semaphore semaphore = new Semaphore(256);
|
||||
private final LoadBalancer loadBalancer = new LoadBalancer(semaphore, 256, IrisSettings.get().getUpdater().emptyMsRange);
|
||||
private final AtomicLong startTime = new AtomicLong();
|
||||
private final Dimensions dimensions;
|
||||
private final PregenTask task;
|
||||
private final ExecutorService executor = Executors.newFixedThreadPool(coreLimit);
|
||||
private final ExecutorService chunkExecutor = Executors.newFixedThreadPool(coreLimit);
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
private final CountDownLatch latch;
|
||||
private final Engine engine;
|
||||
private final World world;
|
||||
|
||||
public ChunkUpdater(World world) {
|
||||
this.engine = IrisToolbelt.access(world).getEngine();
|
||||
this.chunksPerSecond = new RollingSequence(5);
|
||||
this.world = world;
|
||||
this.lastUse = new KMap();
|
||||
this.worldheightsize = new AtomicInteger(calculateWorldDimensions(new File(world.getWorldFolder(), "region"), 1));
|
||||
this.worldwidthsize = new AtomicInteger(calculateWorldDimensions(new File(world.getWorldFolder(), "region"), 0));
|
||||
int m = Math.max(worldheightsize.get(), worldwidthsize.get());
|
||||
this.executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors() / 3, 1));
|
||||
this.chunkExecutor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors() / 3, 1));
|
||||
this.scheduler = Executors.newScheduledThreadPool(1);
|
||||
this.future = new CompletableFuture<>();
|
||||
this.startTime = new AtomicLong();
|
||||
this.worldheightsize.set(m);
|
||||
this.worldwidthsize.set(m);
|
||||
this.totalMaxChunks = new AtomicInteger((worldheightsize.get() / 16) * (worldwidthsize.get() / 16));
|
||||
this.chunksProcessed = new AtomicInteger();
|
||||
this.chunksUpdated = new AtomicInteger();
|
||||
this.position = new AtomicInteger(0);
|
||||
this.dimensions = calculateWorldDimensions(new File(world.getWorldFolder(), "region"));
|
||||
this.task = dimensions.task();
|
||||
this.totalMaxChunks.set(dimensions.count * 1024);
|
||||
this.latch = new CountDownLatch(totalMaxChunks.get());
|
||||
this.paused = new AtomicBoolean(false);
|
||||
this.pauseLock = new Object();
|
||||
this.cancelled = new AtomicBoolean(false);
|
||||
this.totalChunks = new AtomicInteger(0);
|
||||
this.totalMcaregions = new AtomicInteger(0);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return world.getName();
|
||||
}
|
||||
|
||||
public int getChunks() {
|
||||
@ -97,7 +89,6 @@ public class ChunkUpdater {
|
||||
cancelled.set(true);
|
||||
}
|
||||
|
||||
|
||||
private void update() {
|
||||
Iris.info("Updating..");
|
||||
try {
|
||||
@ -106,11 +97,11 @@ public class ChunkUpdater {
|
||||
try {
|
||||
if (!paused.get()) {
|
||||
long eta = computeETA();
|
||||
long elapsedSeconds = (System.currentTimeMillis() - startTime.get()) / 1000;
|
||||
int processed = chunksProcessed.get();
|
||||
double cps = elapsedSeconds > 0 ? processed / (double) elapsedSeconds : 0;
|
||||
double last = processed - chunksProcessedLast.getAndSet(processed);
|
||||
double cps = last / ((M.ms() - lastCpsTime.getAndSet(M.ms())) / 1000d);
|
||||
chunksPerSecond.put(cps);
|
||||
double percentage = ((double) chunksProcessed.get() / (double) totalMaxChunks.get()) * 100;
|
||||
double percentage = ((double) processed / (double) totalMaxChunks.get()) * 100;
|
||||
if (!cancelled.get()) {
|
||||
Iris.info("Updated: " + Form.f(processed) + " of " + Form.f(totalMaxChunks.get()) + " (%.0f%%) " + Form.f(chunksPerSecond.getAverage()) + "/s, ETA: " + Form.duration(eta,
|
||||
2), percentage);
|
||||
@ -120,35 +111,20 @@ 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)
|
||||
return;
|
||||
loadBalancer.setRange(empty ? IrisSettings.get().getUpdater().emptyMsRange : IrisSettings.get().getUpdater().defaultMsRange);
|
||||
}, 0, 10, TimeUnit.SECONDS);
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
for (int i = 0; i < totalMaxChunks.get(); i++) {
|
||||
if (paused.get()) {
|
||||
synchronized (pauseLock) {
|
||||
try {
|
||||
pauseLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
Iris.error("Interrupted while waiting for executor: ");
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
executor.submit(() -> {
|
||||
if (!cancelled.get()) {
|
||||
processNextChunk();
|
||||
}
|
||||
latch.countDown();
|
||||
});
|
||||
}
|
||||
}).thenRun(() -> {
|
||||
try {
|
||||
latch.await();
|
||||
close();
|
||||
} catch (Exception e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
var t = new Thread(() -> {
|
||||
run();
|
||||
close();
|
||||
}, "Iris Chunk Updater - " + world.getName());
|
||||
t.setPriority(Thread.MAX_PRIORITY);
|
||||
t.start();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@ -157,14 +133,16 @@ public class ChunkUpdater {
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
unloadAndSaveAllChunks();
|
||||
loadBalancer.close();
|
||||
semaphore.acquire(256);
|
||||
|
||||
executor.shutdown();
|
||||
executor.awaitTermination(5, TimeUnit.SECONDS);
|
||||
chunkExecutor.shutdown();
|
||||
chunkExecutor.awaitTermination(5, TimeUnit.SECONDS);
|
||||
scheduler.shutdownNow();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
unloadAndSaveAllChunks();
|
||||
} catch (Exception ignored) {}
|
||||
if (cancelled.get()) {
|
||||
Iris.info("Updated: " + Form.f(chunksUpdated.get()) + " Chunks");
|
||||
Iris.info("Irritated: " + Form.f(chunksProcessed.get()) + " of " + Form.f(totalMaxChunks.get()));
|
||||
@ -175,18 +153,69 @@ public class ChunkUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
private void processNextChunk() {
|
||||
int pos = position.getAndIncrement();
|
||||
int[] coords = getChunk(pos);
|
||||
if (loadChunksIfGenerated(coords[0], coords[1])) {
|
||||
Chunk c = world.getChunkAt(coords[0], coords[1]);
|
||||
engine.updateChunk(c);
|
||||
chunksUpdated.incrementAndGet();
|
||||
private void run() {
|
||||
task.iterateRegions((rX, rZ) -> {
|
||||
if (cancelled.get())
|
||||
return;
|
||||
|
||||
while (paused.get()) {
|
||||
J.sleep(50);
|
||||
}
|
||||
|
||||
if (rX < dimensions.min.getX() || rX > dimensions.max.getX() || rZ < dimensions.min.getZ() || rZ > dimensions.max.getZ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PregenTask.iterateRegion(rX, rZ, (x, z) -> {
|
||||
while (paused.get() && !cancelled.get()) {
|
||||
J.sleep(50);
|
||||
}
|
||||
|
||||
try {
|
||||
semaphore.acquire();
|
||||
} catch (InterruptedException ignored) {
|
||||
return;
|
||||
}
|
||||
chunkExecutor.submit(() -> {
|
||||
try {
|
||||
if (!cancelled.get())
|
||||
processChunk(x, z);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
semaphore.release();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void processChunk(int x, int z) {
|
||||
if (!loadChunksIfGenerated(x, z)) {
|
||||
chunksProcessed.getAndIncrement();
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
chunksUpdated.incrementAndGet();
|
||||
chunksProcessed.getAndIncrement();
|
||||
}
|
||||
chunksProcessed.getAndIncrement();
|
||||
}
|
||||
|
||||
private boolean loadChunksIfGenerated(int x, int z) {
|
||||
if (engine.getMantle().getMantle().hasFlag(x, z, MantleFlag.ETCHED))
|
||||
return false;
|
||||
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
if (!PaperLib.isChunkGenerated(world, x + dx, z + dz)) {
|
||||
@ -196,45 +225,73 @@ public class ChunkUpdater {
|
||||
}
|
||||
|
||||
AtomicBoolean generated = new AtomicBoolean(true);
|
||||
KList<Future<?>> futures = new KList<>(9);
|
||||
CountDownLatch latch = new CountDownLatch(9);
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
int xx = x + dx;
|
||||
int zz = z + dz;
|
||||
futures.add(chunkExecutor.submit(() -> {
|
||||
Chunk c;
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
c = PaperLib.getChunkAtAsync(world, xx, zz, false).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
generated.set(false);
|
||||
return;
|
||||
}
|
||||
if (!c.isLoaded()) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
J.s(() -> {
|
||||
c.load(false);
|
||||
latch.countDown();
|
||||
});
|
||||
Chunk c;
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException ignored) {}
|
||||
c = PaperLib.getChunkAtAsync(world, xx, zz, false, true)
|
||||
.thenApply(chunk -> {
|
||||
chunk.addPluginChunkTicket(Iris.instance);
|
||||
return chunk;
|
||||
}).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
generated.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c.isLoaded()) {
|
||||
var future = J.sfut(() -> c.load(false));
|
||||
if (future != null) future.join();
|
||||
}
|
||||
|
||||
if (!c.isGenerated())
|
||||
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();
|
||||
}
|
||||
if (!c.isGenerated()) {
|
||||
generated.set(false);
|
||||
}
|
||||
lastUse.put(c, M.ms());
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
while (!futures.isEmpty()) {
|
||||
futures.removeIf(Future::isDone);
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException ignored) {}
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Iris.info("Interrupted while waiting for chunks to load");
|
||||
}
|
||||
return generated.get();
|
||||
}
|
||||
|
||||
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 unloadAndSaveAllChunks() {
|
||||
try {
|
||||
J.sfut(() -> {
|
||||
@ -243,13 +300,7 @@ public class ChunkUpdater {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Chunk i : new ArrayList<>(lastUse.keySet())) {
|
||||
Long lastUseTime = lastUse.get(i);
|
||||
if (lastUseTime != null && M.ms() - lastUseTime >= 5000) {
|
||||
i.unload();
|
||||
lastUse.remove(i);
|
||||
}
|
||||
}
|
||||
unloadChunks();
|
||||
world.save();
|
||||
}).get();
|
||||
} catch (Throwable e) {
|
||||
@ -266,7 +317,7 @@ public class ChunkUpdater {
|
||||
);
|
||||
}
|
||||
|
||||
public int calculateWorldDimensions(File regionDir, Integer o) {
|
||||
private Dimensions calculateWorldDimensions(File regionDir) {
|
||||
File[] files = regionDir.listFiles((dir, name) -> name.endsWith(".mca"));
|
||||
|
||||
int minX = Integer.MAX_VALUE;
|
||||
@ -279,40 +330,23 @@ public class ChunkUpdater {
|
||||
int x = Integer.parseInt(parts[1]);
|
||||
int z = Integer.parseInt(parts[2]);
|
||||
|
||||
if (x < minX) minX = x;
|
||||
if (x > maxX) maxX = x;
|
||||
if (z < minZ) minZ = z;
|
||||
if (z > maxZ) maxZ = z;
|
||||
minX = Math.min(minX, x);
|
||||
maxX = Math.max(maxX, x);
|
||||
minZ = Math.min(minZ, z);
|
||||
maxZ = Math.max(maxZ, z);
|
||||
}
|
||||
int oX = minX + ((maxX - minX) / 2);
|
||||
int oZ = minZ + ((maxZ - minZ) / 2);
|
||||
|
||||
int height = (maxX - minX + 1) * 32 * 16;
|
||||
int width = (maxZ - minZ + 1) * 32 * 16;
|
||||
int height = maxX - minX + 1;
|
||||
int width = maxZ - minZ + 1;
|
||||
|
||||
if (o == 1) {
|
||||
return height;
|
||||
}
|
||||
if (o == 0) {
|
||||
return width;
|
||||
}
|
||||
return 0;
|
||||
return new Dimensions(new Position2(minX, minZ), new Position2(maxX, maxZ), height * width, PregenTask.builder()
|
||||
.width((int) Math.ceil(width / 2d))
|
||||
.height((int) Math.ceil(height / 2d))
|
||||
.center(new Position2(oX, oZ))
|
||||
.build());
|
||||
}
|
||||
|
||||
public int[] getChunk(int position) {
|
||||
int p = -1;
|
||||
AtomicInteger xx = new AtomicInteger();
|
||||
AtomicInteger zz = new AtomicInteger();
|
||||
Spiraler s = new Spiraler(worldheightsize.get() * 2, worldwidthsize.get() * 2, (x, z) -> {
|
||||
xx.set(x);
|
||||
zz.set(z);
|
||||
});
|
||||
|
||||
while (s.hasNext() && p++ < position) {
|
||||
s.next();
|
||||
}
|
||||
int[] coords = new int[2];
|
||||
coords[0] = xx.get();
|
||||
coords[1] = zz.get();
|
||||
|
||||
return coords;
|
||||
}
|
||||
private record Dimensions(Position2 min, Position2 max, int count, PregenTask task) { }
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import com.volmit.iris.core.loader.IrisRegistrant;
|
||||
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||
import com.volmit.iris.core.nms.container.BlockPos;
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
import com.volmit.iris.core.pregenerator.ChunkUpdater;
|
||||
import com.volmit.iris.core.service.ExternalDataSVC;
|
||||
import com.volmit.iris.engine.IrisComplex;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
@ -58,6 +59,7 @@ import com.volmit.iris.util.matter.TileWrapper;
|
||||
import com.volmit.iris.util.matter.slices.container.JigsawPieceContainer;
|
||||
import com.volmit.iris.util.parallel.BurstExecutor;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.reflect.W;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
@ -79,6 +81,7 @@ import java.awt.Color;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
@ -276,33 +279,43 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
for (int z = -1; z <= 1; z++) {
|
||||
if (c.getWorld().isChunkLoaded(c.getX() + x, c.getZ() + z))
|
||||
continue;
|
||||
Iris.debug("Chunk %s, %s [%s, %s] is not loaded".formatted(c.getX() + x, c.getZ() + z, x, z));
|
||||
var msg = "Chunk %s, %s [%s, %s] is not loaded".formatted(c.getX() + x, c.getZ() + z, x, z);
|
||||
if (W.getStack().getCallerClass().equals(ChunkUpdater.class)) Iris.warn(msg);
|
||||
else Iris.debug(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!getMantle().getMantle().isLoaded(c)) {
|
||||
Iris.debug("Mantle Chunk " + c.getX() + c.getX() + " is not loaded");
|
||||
var mantle = getMantle().getMantle();
|
||||
if (!mantle.isLoaded(c)) {
|
||||
var msg = "Mantle Chunk " + c.getX() + c.getX() + " is not loaded";
|
||||
if (W.getStack().getCallerClass().equals(ChunkUpdater.class)) Iris.warn(msg);
|
||||
else Iris.debug(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.TILE, () -> J.s(() -> {
|
||||
getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), TileWrapper.class, (x, y, z, v) -> {
|
||||
var chunk = mantle.getChunk(c);
|
||||
if (chunk.isFlagged(MantleFlag.ETCHED)) return;
|
||||
chunk.flag(MantleFlag.ETCHED, true);
|
||||
|
||||
Semaphore semaphore = new Semaphore(3);
|
||||
chunk.raiseFlag(MantleFlag.TILE, run(semaphore, () -> J.s(() -> {
|
||||
mantle.iterateChunk(c.getX(), c.getZ(), TileWrapper.class, (x, y, z, v) -> {
|
||||
int betterY = y + getWorld().minHeight();
|
||||
if (!TileData.setTileState(c.getBlock(x, betterY, z), v.getData()))
|
||||
Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", x, betterY, z, c.getBlock(x, betterY, z).getBlockData().getMaterial().getKey(), v.getData().getMaterial().name());
|
||||
});
|
||||
}));
|
||||
getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.CUSTOM, () -> J.s(() -> {
|
||||
getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), Identifier.class, (x, y, z, v) -> {
|
||||
})));
|
||||
chunk.raiseFlag(MantleFlag.CUSTOM, run(semaphore, () -> J.s(() -> {
|
||||
mantle.iterateChunk(c.getX(), c.getZ(), Identifier.class, (x, y, z, v) -> {
|
||||
Iris.service(ExternalDataSVC.class).processUpdate(this, c.getBlock(x & 15, y + getWorld().minHeight(), z & 15), v);
|
||||
});
|
||||
}));
|
||||
})));
|
||||
|
||||
getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.UPDATE, () -> J.s(() -> {
|
||||
chunk.raiseFlag(MantleFlag.UPDATE, run(semaphore, () -> J.s(() -> {
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
KMap<Long, Integer> updates = new KMap<>();
|
||||
RNG r = new RNG(Cache.key(c.getX(), c.getZ()));
|
||||
getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), MatterCavern.class, (x, yf, z, v) -> {
|
||||
mantle.iterateChunk(c.getX(), c.getZ(), MatterCavern.class, (x, yf, z, v) -> {
|
||||
int y = yf + getWorld().minHeight();
|
||||
if (!B.isFluid(c.getBlock(x & 15, y, z & 15).getBlockData())) {
|
||||
return;
|
||||
@ -332,7 +345,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
});
|
||||
|
||||
updates.forEach((k, v) -> update(Cache.keyX(k), v, Cache.keyZ(k), c, r));
|
||||
getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), MatterUpdate.class, (x, yf, z, v) -> {
|
||||
mantle.iterateChunk(c.getX(), c.getZ(), MatterUpdate.class, (x, yf, z, v) -> {
|
||||
int y = yf + getWorld().minHeight();
|
||||
if (v != null && v.isUpdate()) {
|
||||
int vx = x & 15;
|
||||
@ -343,9 +356,25 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
}
|
||||
}
|
||||
});
|
||||
getMantle().getMantle().deleteChunkSlice(c.getX(), c.getZ(), MatterUpdate.class);
|
||||
mantle.deleteChunkSlice(c.getX(), c.getZ(), MatterUpdate.class);
|
||||
getMetrics().getUpdates().put(p.getMilliseconds());
|
||||
}, RNG.r.i(0, 20)));
|
||||
}, RNG.r.i(0, 20))));
|
||||
|
||||
try {
|
||||
semaphore.acquire(3);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
|
||||
private static Runnable run(Semaphore semaphore, Runnable runnable) {
|
||||
return () -> {
|
||||
if (!semaphore.tryAcquire())
|
||||
return;
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
semaphore.release();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@BlockCoordinates
|
||||
|
@ -0,0 +1,70 @@
|
||||
package com.volmit.iris.util.profile;
|
||||
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
@Getter
|
||||
public class LoadBalancer extends MsptTimings {
|
||||
private final Semaphore semaphore;
|
||||
private final int maxPermits;
|
||||
private final double range;
|
||||
@Setter
|
||||
private int minMspt, maxMspt;
|
||||
private int permits, lastMspt;
|
||||
private long lastTime = M.ms();
|
||||
|
||||
public LoadBalancer(Semaphore semaphore, int maxPermits, IrisSettings.MsRange range) {
|
||||
this(semaphore, maxPermits, range.getMin(), range.getMax());
|
||||
}
|
||||
|
||||
public LoadBalancer(Semaphore semaphore, int maxPermits, int minMspt, int maxMspt) {
|
||||
this.semaphore = semaphore;
|
||||
this.maxPermits = maxPermits;
|
||||
this.minMspt = minMspt;
|
||||
this.maxMspt = maxMspt;
|
||||
this.range = maxMspt - minMspt;
|
||||
setName("LoadBalancer");
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void update(int raw) {
|
||||
lastTime = M.ms();
|
||||
int mspt = raw;
|
||||
if (mspt < lastMspt) {
|
||||
int min = (int) Math.max(lastMspt * IrisSettings.get().getUpdater().getChunkLoadSensitivity(), 1);
|
||||
mspt = Math.max(mspt, min);
|
||||
}
|
||||
lastMspt = mspt;
|
||||
mspt = Math.max(mspt - minMspt, 0);
|
||||
double percent = mspt / range;
|
||||
|
||||
int target = (int) (maxPermits * percent);
|
||||
target = Math.min(target, maxPermits - 20);
|
||||
|
||||
int diff = target - permits;
|
||||
permits = target;
|
||||
|
||||
if (diff == 0) return;
|
||||
Iris.debug("Adjusting load to %s (%s) permits (%s mspt, %.2f)".formatted(target, diff, raw, percent));
|
||||
|
||||
if (diff > 0) semaphore.acquireUninterruptibly(diff);
|
||||
else semaphore.release(Math.abs(diff));
|
||||
}
|
||||
|
||||
public void close() {
|
||||
interrupt();
|
||||
semaphore.release(permits);
|
||||
}
|
||||
|
||||
public void setRange(IrisSettings.MsRange range) {
|
||||
minMspt = range.getMin();
|
||||
maxMspt = range.getMax();
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package com.volmit.iris.util.profile;
|
||||
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.Looper;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class MsptTimings extends Looper {
|
||||
private final AtomicInteger currentTick = new AtomicInteger(0);
|
||||
private int lastTick, lastMspt;
|
||||
private long lastTime;
|
||||
private int taskId = -1;
|
||||
|
||||
public MsptTimings() {
|
||||
setName("MsptTimings");
|
||||
setPriority(9);
|
||||
setDaemon(true);
|
||||
}
|
||||
|
||||
public static MsptTimings of(Consumer<Integer> update) {
|
||||
return new Simple(update);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final long loop() {
|
||||
if (startTickTask())
|
||||
return 200;
|
||||
|
||||
long now = M.ms();
|
||||
int tick = currentTick.get();
|
||||
int deltaTick = tick - lastTick;
|
||||
if (deltaTick == 0)
|
||||
return 200;
|
||||
lastTick = tick;
|
||||
int deltaTime = (int) (now - lastTime);
|
||||
lastTime = now;
|
||||
int mspt = deltaTime / deltaTick;
|
||||
mspt -= 50;
|
||||
mspt = Math.max(mspt, 0);
|
||||
lastMspt = mspt;
|
||||
update(mspt);
|
||||
return 200;
|
||||
}
|
||||
|
||||
public final int getMspt() {
|
||||
return lastMspt;
|
||||
}
|
||||
|
||||
protected abstract void update(int mspt);
|
||||
|
||||
private boolean startTickTask() {
|
||||
if (taskId != -1 && (Bukkit.getScheduler().isQueued(taskId) || Bukkit.getScheduler().isCurrentlyRunning(taskId)))
|
||||
return false;
|
||||
|
||||
taskId = J.sr(() -> {
|
||||
if (isInterrupted()) {
|
||||
J.csr(taskId);
|
||||
return;
|
||||
}
|
||||
|
||||
currentTick.incrementAndGet();
|
||||
}, 1);
|
||||
return taskId != -1;
|
||||
}
|
||||
|
||||
private static class Simple extends MsptTimings {
|
||||
private final Consumer<Integer> update;
|
||||
|
||||
private Simple(Consumer<Integer> update) {
|
||||
this.update = update;
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void update(int mspt) {
|
||||
if (update == null)
|
||||
return;
|
||||
update.accept(mspt);
|
||||
}
|
||||
}
|
||||
}
|
8
core/src/main/java/com/volmit/iris/util/reflect/W.java
Normal file
8
core/src/main/java/com/volmit/iris/util/reflect/W.java
Normal file
@ -0,0 +1,8 @@
|
||||
package com.volmit.iris.util.reflect;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class W {
|
||||
@Getter
|
||||
private static final StackWalker stack = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user