Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -525,14 +525,15 @@ index fcc172a2aeee3fe6bfd526411e1e123d0f80c3e3..4c09d75b824293728ccc6367cdcd6b84
try {
this.isUpdatingNavigations = true;
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 32a2819db5a929f0a8cde7f77f371bcd7890fd9c..5a9ce8cde62b738ebda64cdf63535299cee1f640 100644
index 32a2819db5a929f0a8cde7f77f371bcd7890fd9c..fed602181a0013d7f75759da8c697f806bfb0467 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -437,6 +437,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
@@ -437,6 +437,8 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
private boolean compassBar = false; // Purpur - Add compass command
private boolean ramBar = false; // Purpur - Implement rambar commands
public boolean hasTickedAtLeastOnceInNewWorld = false; // DivineMC - Parallel world ticking
+ public final org.bxteam.divinemc.async.rct.RollingLongBuffer avgTickTimeNanos = new org.bxteam.divinemc.async.rct.RollingLongBuffer(100); // DivineMC - Region tick time tracking
+ public int lastRegionChunkSize, lastRegionEntityAmount, regionHash; // DivineMC - Region tracking

// Paper start - rewrite chunk system
private ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package org.bxteam.divinemc.async.rct;

import com.mojang.logging.LogUtils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import org.slf4j.Logger;

import java.io.*;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import java.util.zip.GZIPOutputStream;

public final class AvgTimeLogger {
private static final Logger LOGGER = LogUtils.getLogger();
private static final String LOG_DIR = "tracking";
private static final int MAX_LOG_AGE_DAYS = 30;
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
private final String levelName;
Expand All @@ -22,16 +23,65 @@ public AvgTimeLogger(String levelName) {
this.levelName = levelName;
try {
File logDir = new File(LOG_DIR + "/" + levelName);
if (!logDir.exists()) {
logDir.mkdirs();
if (!logDir.exists() && !logDir.mkdirs()) {
LOGGER.warn("Failed to create log directory {}", logDir.getAbsolutePath());
}
currentDate = LocalDate.now();
initializeLogWriter();
cleanupOldLogs();
} catch (IOException e) {
LOGGER.error("Failed to initialize region tick time log file", e);
}
}

private void cleanupOldLogs() {
File logDir = new File(LOG_DIR + "/" + levelName);
File[] files = logDir.listFiles();
if (files == null) return;

LocalDate cutoffDate = LocalDate.now().minusDays(MAX_LOG_AGE_DAYS);

for (File file : files) {
String name = file.getName();
if (name.startsWith("region-tick-") && name.endsWith(".log")) {
String dateStr = name.substring("region-tick-".length(), name.length() - ".log".length());
try {
LocalDate fileDate = LocalDate.parse(dateStr, DATE_FORMATTER);
if (!fileDate.equals(LocalDate.now())) {
compressLogFile(file);
}
} catch (Exception ignored) {}
} else if (name.startsWith("region-tick-") && name.endsWith(".log.gz")) {
String dateStr = name.substring("region-tick-".length(), name.length() - ".log.gz".length());
try {
LocalDate fileDate = LocalDate.parse(dateStr, DATE_FORMATTER);
if (fileDate.isBefore(cutoffDate) && !file.delete()) {
LOGGER.warn("Failed to delete old log file: {}", file.getName());
}
} catch (Exception ignored) {}
}
}
}

private void compressLogFile(File file) {
File gzFile = new File(file.getAbsolutePath() + ".gz");
try (FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(gzFile);
GZIPOutputStream gzipOS = new GZIPOutputStream(fos)) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
gzipOS.write(buffer, 0, len);
}
} catch (IOException e) {
LOGGER.error("Failed to compress old log: {}", file.getName(), e);
return;
}
if (!file.delete()) {
LOGGER.warn("Failed to delete original log file after compression: {}", file.getName());
}
}

private void initializeLogWriter() throws IOException {
String filename = "region-tick-" + currentDate.format(DATE_FORMATTER) + ".log";
File logFile = new File(LOG_DIR + "/" + levelName, filename);
Expand All @@ -46,6 +96,7 @@ public void logTickTime(String data) {
if (regionTickLogWriter != null) {
regionTickLogWriter.close();
}
cleanupOldLogs();
initializeLogWriter();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,12 @@
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.common.util.TickThread;
import com.mojang.datafixers.DataFixer;
import com.mojang.logging.LogUtils;
import io.papermc.paper.entity.activation.ActivationRange;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
Expand All @@ -39,13 +25,18 @@
import org.bxteam.divinemc.config.DivineConfig;
import org.bxteam.divinemc.util.NamedAgnosticThreadFactory;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Supplier;

public final class RegionizedChunkTicking extends ServerChunkCache {
public static final Executor REGION_EXECUTOR = Executors.newFixedThreadPool(DivineConfig.AsyncCategory.regionizedChunkTickingExecutorThreadCount,
new NamedAgnosticThreadFactory<>("Region Ticking", TickThread::new, DivineConfig.AsyncCategory.regionizedChunkTickingExecutorThreadPriority));
private static final int LOG_INTERVAL = 18000;
private final AvgTimeLogger avgTimeLogger;
public final RollingLongBuffer avgTime = new RollingLongBuffer(100);
private final LongOpenHashSet tickedChunkKeys = new LongOpenHashSet(8192);
private int i = 0;

public RegionizedChunkTicking(
Expand All @@ -67,6 +58,7 @@ public RegionizedChunkTicking(

@Override
protected void iterateTickingChunksFaster(final @NotNull CompletableFuture<Void> spawns) {
final long start = System.nanoTime();
final ServerLevel world = this.level;
final int randomTickSpeed = world.getGameRules().get(GameRules.RANDOM_TICK_SPEED);
final LevelChunk[] raw = world.moonrise$getEntityTickingChunks().toArray(new LevelChunk[0]);
Expand All @@ -84,6 +76,8 @@ protected void iterateTickingChunksFaster(final @NotNull CompletableFuture<Void>

finishTicking(futures, randomTickSpeed, raw, tickPair);
spawns.join();
final long end = System.nanoTime();
avgTime.add(end - start);
}

private CompletableFuture<LongOpenHashSet> tick(RegionData region, int randomTickSpeed) {
Expand All @@ -102,20 +96,34 @@ private CompletableFuture<LongOpenHashSet> tick(RegionData region, int randomTic
tickEntity(entity);
}

final long end = System.nanoTime();
region.players().forEach(player -> player.avgTickTimeNanos.add(end - start));
final long time = System.nanoTime() - start;
final int regionHash = region.hashCode();
final int chunks = regionChunksIDs.size();
final int entities = region.entities().size();
for (ServerPlayer player : region.players()) {
player.avgTickTimeNanos.add(time);
player.lastRegionChunkSize = chunks;
player.lastRegionEntityAmount = entities;
player.regionHash = regionHash;
}
return regionChunksIDs;
}, REGION_EXECUTOR);
}

private void finishTicking(final ObjectArrayList<CompletableFuture<LongOpenHashSet>> ticked, final int randomTickSpeed, final LevelChunk[] raw, final TickPair tickPair) {
try {
CompletableFuture.allOf(ticked.toArray(new CompletableFuture[0])).join();
} catch (CompletionException ex) {
LOGGER.error("Error during region chunk ticking", ex.getCause());
tickedChunkKeys.clear();
for (CompletableFuture<LongOpenHashSet> future : ticked) {
try {
LongOpenHashSet result = future.join();
if (result != null) {
tickedChunkKeys.addAll(result);
}
} catch (Exception e) {
LOGGER.error("Exception retrieving region ticking result", e);
}
}

if (false && i % 100 == 0 && tickPair.regions().length > 0) {
if (i++ % 100 == 0 && tickPair.regions().length > 0) {
REGION_EXECUTOR.execute(() -> {
StringBuilder sb = new StringBuilder();
for (RegionData regionData : tickPair.regions()) {
Expand All @@ -132,18 +140,6 @@ private void finishTicking(final ObjectArrayList<CompletableFuture<LongOpenHashS
});
}

LongOpenHashSet tickedChunkKeys = new LongOpenHashSet(raw.length);

for (CompletableFuture<LongOpenHashSet> future : ticked) {
if (!future.isCompletedExceptionally()) {
try {
tickedChunkKeys.addAll(future.join());
} catch (Exception e) {
LOGGER.error("Exception retrieving region ticking result", e);
}
}
}

for (LevelChunk chunk : raw) {
if (!tickedChunkKeys.contains(chunk.coordinateKey)) {
level.tickChunk(chunk, randomTickSpeed);
Expand All @@ -155,6 +151,53 @@ private void finishTicking(final ObjectArrayList<CompletableFuture<LongOpenHashS
}
}

// private void finishTicking2(final ObjectArrayList<CompletableFuture<LongOpenHashSet>> ticked, final int randomTickSpeed, final LevelChunk[] raw, final TickPair tickPair) {
// try {
// CompletableFuture.allOf(ticked.toArray(new CompletableFuture[0])).join();
// } catch (CompletionException ex) {
// LOGGER.error("Error during region chunk ticking", ex.getCause());
// }
//
// if (i++ % 100 == 0 && tickPair.regions().length > 0) {
// REGION_EXECUTOR.execute(() -> {
// StringBuilder sb = new StringBuilder();
// for (RegionData regionData : tickPair.regions()) {
// sb.append("Region with ").append(regionData.chunks().size()).append(" chunks and ").append(regionData.entities().size()).append(" entities ticked for Players:\n");
// for (ServerPlayer player : regionData.players()) {
// long avgNanos = Math.round(player.avgTickTimeNanos.average().orElse(0));
// long ms = avgNanos / 1_000_000;
// long us = (avgNanos % 1_000_000) / 1_000;
// long ns = avgNanos % 1_000;
// sb.append("- ").append(player.displayName).append(" avg region tick time: ").append(ms).append(" ms ").append(us).append(" us ").append(ns).append(" ns").append("\n");
// }
// }
// avgTimeLogger.logTickTime(sb.toString());
// });
// }
//
// LongOpenHashSet tickedChunkKeys = new LongOpenHashSet(raw.length);
//
// for (CompletableFuture<LongOpenHashSet> future : ticked) {
// if (!future.isCompletedExceptionally()) {
// try {
// tickedChunkKeys.addAll(future.join());
// } catch (Exception e) {
// LOGGER.error("Exception retrieving region ticking result", e);
// }
// }
// }
//
// for (LevelChunk chunk : raw) {
// if (!tickedChunkKeys.contains(chunk.coordinateKey)) {
// level.tickChunk(chunk, randomTickSpeed);
// }
// }
//
// for (Entity entity : tickPair.entities()) {
// tickEntity(entity);
// }
// }

private TickPair computePlayerRegions() {
List<ServerPlayer> players = new ArrayList<>(level.players());
final int defaultTickDist = level.moonrise$getViewDistanceHolder().getViewDistances().tickViewDistance();
Expand Down Expand Up @@ -237,12 +280,6 @@ private TickPair computePlayerRegions() {
}
}

i++;
if (false && i % LOG_INTERVAL == 0) {
LOGGER.info("Computed {} regions for {} players", regions.size(), players.size());
LOGGER.info("region sizes for each region: {}", Arrays.toString(regions.stream().mapToInt(r -> r.chunks().size()).toArray()));
}

final Set<Entity> firstTick = ConcurrentHashMap.newKeySet();

IteratorSafeOrderedReferenceSet<Entity> entities;
Expand Down Expand Up @@ -273,7 +310,7 @@ private TickPair computePlayerRegions() {
}
}

regions.sort(Comparator.comparingDouble(r -> ((RegionData) r).players().stream().map(p -> p.avgTickTimeNanos.average().orElse(-1)).max(Comparator.naturalOrder()).orElse(-1d)).reversed());
regions.sort(Comparator.<RegionData>comparingDouble(r -> r.players().stream().map(p -> p.avgTickTimeNanos.average().orElse(-1)).max(Comparator.naturalOrder()).orElse(-1d)).reversed());
return new TickPair(regions.toArray(new RegionData[0]), firstTick);
}

Expand Down Expand Up @@ -313,9 +350,9 @@ public boolean isEmpty() {
record Rectangle(int minX, int minZ, int maxX, int maxZ) {
boolean intersects(Rectangle other) {
return !(this.maxX < other.minX ||
this.minX > other.maxX ||
this.maxZ < other.minZ ||
this.minZ > other.maxZ);
this.minX > other.maxX ||
this.maxZ < other.minZ ||
this.minZ > other.maxZ);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Arrays;
import java.util.OptionalDouble;
import java.util.OptionalLong;
import java.util.stream.LongStream;

public final class RollingLongBuffer {
Expand Down Expand Up @@ -70,6 +71,38 @@ public LongStream longStream() {
return Arrays.stream(snapshot, 0, snapshot.length);
}

/**
* Returns the arithmetic average of the last {@code n} entries as OptionalDouble.
* If the buffer is empty, returns OptionalDouble.empty().
* If n > size(), averages over the available entries.
*
* Time: O(n) where n is the requested number of entries (bounded by buffer size).
*
* @throws IllegalArgumentException if n <= 0
*/
public synchronized OptionalDouble averageLast(int n) {
if (n <= 0) throw new IllegalArgumentException("n must be > 0");
if (count == 0) return OptionalDouble.empty();

int use = Math.min(n, count);
// accumulate in double to reduce risk of overflow for large sums
double acc = 0.0;
int start = (head + count - use) % capacity;
for (int i = 0; i < use; i++) {
acc += buffer[(start + i) % capacity];
}
return OptionalDouble.of(acc / (double) use);
}

/**
* Returns the last (newest) entry as OptionalLong, or OptionalLong.empty() if buffer is empty.
*/
public synchronized OptionalLong last() {
if (count == 0) return OptionalLong.empty();
int idx = (head + count - 1) % capacity;
return OptionalLong.of(buffer[idx]);
}

/**
* Returns the arithmetic average as OptionalDouble; O(1).
*/
Expand Down
Loading
Loading