mirror of
https://github.com/Minestom/Minestom.git
synced 2025-02-21 06:41:43 +01:00
hollow-cube/respectful-chunk-sending
(cherry picked from commit 9f3ee89506
)
This commit is contained in:
parent
b08a526790
commit
f019fe69d0
@ -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();
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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 + "]";
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user