hollow-cube/respectful-chunk-sending

(cherry picked from commit 9f3ee89506)
This commit is contained in:
mworzala 2023-06-17 10:43:03 -04:00 committed by Matt Worzala
parent b08a526790
commit f019fe69d0
7 changed files with 95 additions and 17 deletions

View File

@ -22,6 +22,8 @@ import java.time.Duration;
public class Main {
public static void main(String[] args) {
System.setProperty("minestom.use-new-chunk-sending", "true");
MinecraftServer minecraftServer = MinecraftServer.init();
BlockManager blockManager = MinecraftServer.getBlockManager();

View File

@ -17,10 +17,7 @@ import net.minestom.server.event.item.ItemDropEvent;
import net.minestom.server.event.item.PickupItemEvent;
import net.minestom.server.event.player.*;
import net.minestom.server.event.server.ServerTickMonitorEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.instance.InstanceManager;
import net.minestom.server.instance.LightingChunk;
import net.minestom.server.instance.*;
import net.minestom.server.instance.block.Block;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.InventoryType;
@ -35,9 +32,8 @@ import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.world.DimensionType;
import java.time.Duration;
import java.util.Collection;
import java.util.Random;
import java.util.Set;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicReference;
@ -90,7 +86,7 @@ public class PlayerInit {
event.setSpawningInstance(instance);
int x = Math.abs(ThreadLocalRandom.current().nextInt()) % 500 - 250;
int z = Math.abs(ThreadLocalRandom.current().nextInt()) % 500 - 250;
player.setRespawnPoint(new Pos(0, 42f, 0));
player.setRespawnPoint(new Pos(0, 40f, 0));
})
.addListener(PlayerSpawnEvent.class, event -> {
final Player player = event.getPlayer();
@ -143,11 +139,14 @@ public class PlayerInit {
instanceContainer.setGenerator(unit -> unit.modifier().fillHeight(0, 40, Block.STONE));
instanceContainer.setChunkSupplier(LightingChunk::new);
if (false) {
System.out.println("start");
ChunkUtils.forChunksInRange(0, 0, 10, (x, z) -> instanceContainer.loadChunk(x, z).join());
System.out.println("start");
var chunks = new ArrayList<CompletableFuture<Chunk>>();
ChunkUtils.forChunksInRange(0, 0, 32, (x, z) -> chunks.add(instanceContainer.loadChunk(x, z)));
CompletableFuture.runAsync(() -> {
CompletableFuture.allOf(chunks.toArray(CompletableFuture[]::new)).join();
System.out.println("load end");
}
});
inventory = new Inventory(InventoryType.CHEST_1_ROW, Component.text("Test inventory"));
inventory.setItemStack(3, ItemStack.of(Material.DIAMOND, 34));

View File

@ -1,5 +1,6 @@
package net.minestom.server.entity;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.identity.Identified;
@ -71,6 +72,7 @@ import net.minestom.server.snapshot.SnapshotImpl;
import net.minestom.server.snapshot.SnapshotUpdater;
import net.minestom.server.statistic.PlayerStatistic;
import net.minestom.server.timer.Scheduler;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.async.AsyncUtils;
@ -99,6 +101,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
/**
@ -696,7 +699,27 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
chunksLoadedByClient = new Vec(chunkX, chunkZ);
chunkUpdateLimitChecker.addToHistory(getChunk());
sendPacket(new UpdateViewPositionPacket(chunkX, chunkZ));
ChunkUtils.forChunksInRange(spawnPosition, MinecraftServer.getChunkViewDistance(), chunkAdder);
if (ChunkUtils.USE_NEW_CHUNK_SENDING) {
// FIXME: Improve this queueing. It is pretty scuffed
var chunkQueue = new LongArrayList();
ChunkUtils.forChunksInRange(spawnPosition, MinecraftServer.getChunkViewDistance(),
(x, z) -> chunkQueue.add(ChunkUtils.getChunkIndex(x, z)));
var iter = chunkQueue.iterator();
Supplier<TaskSchedule> taskRunnable = () -> {
for (int i = 0; i < 50; i++) {
if (!iter.hasNext()) return TaskSchedule.stop();
var next = iter.next();
chunkAdder.accept(ChunkUtils.getChunkCoordX(next), ChunkUtils.getChunkCoordZ(next));
}
return TaskSchedule.nextTick();
};
scheduler().submitTask(taskRunnable);
} else {
ChunkUtils.forChunksInRange(spawnPosition, MinecraftServer.getChunkViewDistance(), chunkAdder);
}
}
synchronizePosition(true); // So the player doesn't get stuck

View File

@ -262,6 +262,11 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
return loaded;
}
/**
* Called when the chunk has been successfully loaded.
*/
protected void onLoad() {}
@Override
public String toString() {
return getClass().getSimpleName() + "[" + chunkX + ":" + chunkZ + "]";

View File

@ -277,6 +277,8 @@ public class InstanceContainer extends Instance {
.thenAccept(chunk -> {
// TODO run in the instance thread?
cacheChunk(chunk);
chunk.onLoad();
EventDispatcher.call(new InstanceChunkLoadEvent(this, chunk));
final CompletableFuture<Chunk> future = this.loadingChunks.remove(index);
assert future == completableFuture : "Invalid future: " + future;

View File

@ -18,6 +18,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@ -111,6 +112,12 @@ public class LightingChunk extends DynamicChunk {
sendPacketToViewers(lightCache);
}
@Override
protected void onLoad() {
// Prefetch the chunk packet so that lazy lighting is computed
chunkCache.body();
}
public int[] calculateHeightMap() {
if (this.heightmap != null) return this.heightmap;
var heightmap = new int[CHUNK_SIZE_X * CHUNK_SIZE_Z];
@ -203,6 +210,7 @@ public class LightingChunk extends DynamicChunk {
}
private static final Set<LightingChunk> sendQueue = ConcurrentHashMap.newKeySet();
private static final ReentrantLock sendQueueLock = new ReentrantLock();
private static Task sendingTask = null;
private static void updateAfterGeneration(LightingChunk chunk) {
@ -217,6 +225,7 @@ public class LightingChunk extends DynamicChunk {
}
}
sendQueueLock.lock();
if (sendingTask != null) sendingTask.cancel();
sendingTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> {
sendingTask = null;
@ -227,14 +236,15 @@ public class LightingChunk extends DynamicChunk {
s.blockLight().invalidate();
s.skyLight().invalidate();
});
sendQueue.remove(f);
f.chunkCache.invalidate();
f.lightCache.invalidate();
f.sendLighting();
}
}
sendQueue.clear();
}, TaskSchedule.tick(10), TaskSchedule.stop(), ExecutionType.ASYNC);
sendQueueLock.unlock();
}
private static void flushQueue(Instance instance, Set<Point> queue, LightType type) {

View File

@ -16,6 +16,8 @@ import java.util.function.Consumer;
@ApiStatus.Internal
public final class ChunkUtils {
public static final boolean USE_NEW_CHUNK_SENDING = Boolean.getBoolean("minestom.use-new-chunk-sending");
private ChunkUtils() {
}
@ -169,10 +171,45 @@ public final class ChunkUtils {
forDifferingChunksInRange(oldChunkX, oldChunkZ, newChunkX, newChunkZ, range, oldCallback);
}
/**
* New implementation comes from <a href="https://github.com/KryptonMC/Krypton/blob/a9eff5463328f34072cdaf37aae3e77b14fcac93/server/src/main/kotlin/org/kryptonmc/krypton/util/math/Maths.kt#L62">Krypton</a>
* which comes from kotlin port by <a href="https://github.com/Esophose">Esophose</a>, which comes from <a href="https://stackoverflow.com/questions/398299/looping-in-a-spiral">a stackoverflow answer</a>.
*/
public static void forChunksInRange(int chunkX, int chunkZ, int range, IntegerBiConsumer consumer) {
for (int x = -range; x <= range; ++x) {
for (int z = -range; z <= range; ++z) {
consumer.accept(chunkX + x, chunkZ + z);
if (!USE_NEW_CHUNK_SENDING) {
for (int x = -range; x <= range; ++x) {
for (int z = -range; z <= range; ++z) {
consumer.accept(chunkX + x, chunkZ + z);
}
}
return;
}
// Send in spiral around the center chunk
consumer.accept(chunkX, chunkZ);
for (int id = 1; id < (range * 2 + 1) * (range * 2 + 1); id++) {
var index = id - 1;
// compute radius (inverse arithmetic sum of 8 + 16 + 24 + ...)
var radius = (int) Math.floor((Math.sqrt(index + 1.0) - 1) / 2) + 1;
// compute total point on radius -1 (arithmetic sum of 8 + 16 + 24 + ...)
var p = 8 * radius * (radius - 1) / 2;
// points by face
var en = radius * 2;
// compute de position and shift it so the first is (-r, -r) but (-r + 1, -r)
// so the square can connect
var a = (1 + index - p) % (radius * 8);
switch (a / (radius * 2)) {
// find the face (0 = top, 1 = right, 2 = bottom, 3 = left)
case 0 -> consumer.accept(a - radius + chunkX, -radius + chunkZ);
case 1 -> consumer.accept(radius + chunkX, a % en - radius + chunkZ);
case 2 -> consumer.accept(radius - a % en + chunkX, radius + chunkZ);
case 3 -> consumer.accept(-radius + chunkX, radius - a % en + chunkZ);
default -> throw new IllegalStateException("unreachable");
}
}
}