mirror of https://github.com/YatopiaMC/Yatopia.git
1874 lines
103 KiB
Diff
1874 lines
103 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Spottedleaf <spottedleaf@spottedleaf.dev>
|
|
Date: Sun, 24 Jan 2021 20:27:32 -0800
|
|
Subject: [PATCH] Replace player chunk loader system
|
|
|
|
The old one has undebuggable problems. Rewriting seems
|
|
the most sensible option.
|
|
|
|
This new player chunk manager will also strictly rate limit
|
|
chunk sends so that netty threads do not get overloaded, whether
|
|
it be from the anti-xray logic or the compression itself.
|
|
|
|
Chunk loading is also rate limited in the same manner, so this
|
|
will result in a maximum responsiveness for change.
|
|
|
|
Config:
|
|
```
|
|
player-chunks:
|
|
autoconfig-send-distance: true
|
|
min-load-radius: 3
|
|
max-concurrent-sends: 12.0
|
|
max-concurrent-loads: 5.0
|
|
```
|
|
|
|
autoconfig-send-distance - Whether to try to use the client's
|
|
view distance for the send view distance in the server. In the
|
|
case that no plugin has explicitly set the send distance and
|
|
the client view distance is less-than the server's send distance,
|
|
the client's view distance will be used. This will not affect
|
|
tick view distance or no-tick view distance.
|
|
|
|
min-load-radius - The radius of chunks around a player that
|
|
are not throttled for loading. The number of chunks
|
|
affected is actually the configured value plus one as this
|
|
config controls the chunks the client will be able to render.
|
|
|
|
max-concurrent-sends - The maximum number of chunks that
|
|
can be queued to send at any given time. Low values
|
|
are generally going to solve server-sided networking
|
|
bottlenecks like anti-xray and chunk compression. Client
|
|
side networking is unlikely to be helped (i.e this wont help
|
|
people running off McDonald's wifi). Setting this
|
|
value to negative will make the server dynamically scale it
|
|
with players. i.e -5 will use 5 * online players for the max sends.
|
|
|
|
max-concurrent-loads - The maxmium number of chunks
|
|
that can be queued to be loaded at any given time. Lower
|
|
values help the responsitivity to player movement and
|
|
higher values help loading when the server is at a low TPS.
|
|
|
|
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
index 5dfa0658838c4801cdf260eae8b98163f729e5af..35810f42d7a0cd50a4cbe90e8d698fe57914c889 100644
|
|
--- a/src/main/java/co/aikar/timings/TimingsExport.java
|
|
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
@@ -154,7 +154,7 @@ public class TimingsExport extends Thread {
|
|
return pair(rule, world.getWorld().getGameRuleValue(rule));
|
|
})),
|
|
pair("ticking-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance()),
|
|
- pair("notick-viewdistance", world.getChunkProvider().playerChunkMap.getEffectiveNoTickViewDistance())
|
|
+ pair("notick-viewdistance", world.getChunkProvider().playerChunkMap.playerChunkManager.getTargetNoTickViewDistance()) // Tuinity - replace old player chunk management
|
|
));
|
|
}));
|
|
|
|
diff --git a/src/main/java/com/tuinity/tuinity/chunk/PlayerChunkLoader.java b/src/main/java/com/tuinity/tuinity/chunk/PlayerChunkLoader.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0d577aa1c7868ce89c3902535adcb554b1f47551
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/tuinity/tuinity/chunk/PlayerChunkLoader.java
|
|
@@ -0,0 +1,964 @@
|
|
+package com.tuinity.tuinity.chunk;
|
|
+
|
|
+import com.destroystokyo.paper.util.misc.PlayerAreaMap;
|
|
+import com.destroystokyo.paper.util.misc.PooledLinkedHashSets;
|
|
+import com.tuinity.tuinity.config.TuinityConfig;
|
|
+import com.tuinity.tuinity.util.TickThread;
|
|
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
|
|
+import net.minecraft.network.protocol.game.PacketPlayOutViewCentre;
|
|
+import net.minecraft.network.protocol.game.PacketPlayOutViewDistance;
|
|
+import net.minecraft.server.level.EntityPlayer;
|
|
+import net.minecraft.world.level.ChunkCoordIntPair;
|
|
+import net.minecraft.world.level.chunk.Chunk;
|
|
+import net.minecraft.server.MCUtil;
|
|
+import net.minecraft.util.MathHelper;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.network.protocol.Packet;
|
|
+import net.minecraft.server.level.PlayerChunk;
|
|
+import net.minecraft.server.level.PlayerChunkMap;
|
|
+import net.minecraft.server.level.TicketType;
|
|
+import java.util.ArrayDeque;
|
|
+import java.util.ArrayList;
|
|
+import java.util.List;
|
|
+import java.util.TreeSet;
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
+
|
|
+public final class PlayerChunkLoader {
|
|
+
|
|
+ public static final int MIN_VIEW_DISTANCE = 2;
|
|
+ public static final int MAX_VIEW_DISTANCE = 32;
|
|
+
|
|
+ public static final int TICK_TICKET_LEVEL = 31;
|
|
+ public static final int LOADED_TICKET_LEVEL = 33;
|
|
+
|
|
+ protected final PlayerChunkMap chunkMap;
|
|
+ protected final Reference2ObjectLinkedOpenHashMap<EntityPlayer, PlayerLoaderData> playerMap = new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f);
|
|
+ protected final ReferenceLinkedOpenHashSet<PlayerLoaderData> chunkSendQueue = new ReferenceLinkedOpenHashSet<>(512, 0.7f);
|
|
+
|
|
+ protected final TreeSet<PlayerLoaderData> chunkLoadQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> {
|
|
+ if (p1 == p2) {
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ final ChunkPriorityHolder holder1 = p1.loadQueue.peekFirst();
|
|
+ final ChunkPriorityHolder holder2 = p2.loadQueue.peekFirst();
|
|
+
|
|
+ final int priorityCompare = Double.compare(holder1 == null ? Double.MAX_VALUE : holder1.priority, holder2 == null ? Double.MAX_VALUE : holder2.priority);
|
|
+
|
|
+ if (priorityCompare != 0) {
|
|
+ return priorityCompare;
|
|
+ }
|
|
+
|
|
+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId());
|
|
+
|
|
+ if (idCompare != 0) {
|
|
+ return idCompare;
|
|
+ }
|
|
+
|
|
+ // last resort
|
|
+ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2));
|
|
+ });
|
|
+
|
|
+ protected final TreeSet<PlayerLoaderData> chunkSendWaitQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> {
|
|
+ if (p1 == p2) {
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ final int timeCompare = Long.compare(p1.nextChunkSendTarget, p2.nextChunkSendTarget);
|
|
+ if (timeCompare != 0) {
|
|
+ return timeCompare;
|
|
+ }
|
|
+
|
|
+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId());
|
|
+
|
|
+ if (idCompare != 0) {
|
|
+ return idCompare;
|
|
+ }
|
|
+
|
|
+ // last resort
|
|
+ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2));
|
|
+ });
|
|
+
|
|
+
|
|
+ // no throttling is applied below this VD for loading
|
|
+
|
|
+ /**
|
|
+ * The chunks to be sent to players, provided they're send-ready. Send-ready means the chunk and its 1 radius neighbours are loaded.
|
|
+ */
|
|
+ public final PlayerAreaMap broadcastMap;
|
|
+
|
|
+ /**
|
|
+ * The chunks to be brought up to send-ready status. Send-ready means the chunk and its 1 radius neighbours are loaded.
|
|
+ */
|
|
+ public final PlayerAreaMap loadMap;
|
|
+
|
|
+ /**
|
|
+ * Areamap used only to remove tickets for send-ready chunks. View distance is always + 1 of load view distance. Thus,
|
|
+ * this map is always representing the chunks we are actually going to load.
|
|
+ */
|
|
+ public final PlayerAreaMap loadTicketCleanup;
|
|
+
|
|
+ /**
|
|
+ * The chunks to brought to ticking level. Each chunk must have 2 radius neighbours loaded before this can happen.
|
|
+ */
|
|
+ public final PlayerAreaMap tickMap;
|
|
+
|
|
+ /**
|
|
+ * -1 if defaulting to [load distance], else always in [2, load distance]
|
|
+ */
|
|
+ protected int rawSendDistance = -1;
|
|
+
|
|
+ /**
|
|
+ * -1 if defaulting to [tick view distance + 1], else always in [tick view distance + 1, 32 + 1]
|
|
+ */
|
|
+ protected int rawLoadDistance = -1;
|
|
+
|
|
+ /**
|
|
+ * Never -1, always in [2, 32]
|
|
+ */
|
|
+ protected int rawTickDistance = -1;
|
|
+
|
|
+ // methods to bridge for API
|
|
+
|
|
+ public int getTargetViewDistance() {
|
|
+ return this.getTickDistance();
|
|
+ }
|
|
+
|
|
+ public void setTargetViewDistance(final int distance) {
|
|
+ this.setTickDistance(distance);
|
|
+ }
|
|
+
|
|
+ public int getTargetNoTickViewDistance() {
|
|
+ return this.getLoadDistance() - 1;
|
|
+ }
|
|
+
|
|
+ public void setTargetNoTickViewDistance(final int distance) {
|
|
+ this.setLoadDistance(distance == -1 ? -1 : distance + 1);
|
|
+ }
|
|
+
|
|
+ public int getTargetSendDistance() {
|
|
+ return this.rawSendDistance == -1 ? this.getLoadDistance() : this.rawSendDistance;
|
|
+ }
|
|
+
|
|
+ public void setTargetSendDistance(final int distance) {
|
|
+ this.setSendDistance(distance);
|
|
+ }
|
|
+
|
|
+ // internal methods
|
|
+
|
|
+ public int getSendDistance() {
|
|
+ final int loadDistance = this.getLoadDistance();
|
|
+ return this.rawSendDistance == -1 ? loadDistance : Math.min(this.rawSendDistance, loadDistance);
|
|
+ }
|
|
+
|
|
+ public void setSendDistance(final int distance) {
|
|
+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) {
|
|
+ throw new IllegalArgumentException(Integer.toString(distance));
|
|
+ }
|
|
+ this.rawSendDistance = distance;
|
|
+ }
|
|
+
|
|
+ public int getLoadDistance() {
|
|
+ final int tickDistance = this.getTickDistance();
|
|
+ return this.rawLoadDistance == -1 ? tickDistance + 1 : Math.max(tickDistance + 1, this.rawLoadDistance);
|
|
+ }
|
|
+
|
|
+ public void setLoadDistance(final int distance) {
|
|
+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) {
|
|
+ throw new IllegalArgumentException(Integer.toString(distance));
|
|
+ }
|
|
+ this.rawLoadDistance = distance;
|
|
+ }
|
|
+
|
|
+ public int getTickDistance() {
|
|
+ return this.rawTickDistance;
|
|
+ }
|
|
+
|
|
+ public void setTickDistance(final int distance) {
|
|
+ if (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE) {
|
|
+ throw new IllegalArgumentException(Integer.toString(distance));
|
|
+ }
|
|
+ this.rawTickDistance = distance;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ Players have 3 different types of view distance:
|
|
+ 1. Sending view distance
|
|
+ 2. Loading view distance
|
|
+ 3. Ticking view distance
|
|
+
|
|
+ But for configuration purposes (and API) there are:
|
|
+ 1. No-tick view distance
|
|
+ 2. Tick view distance
|
|
+ 3. Broadcast view distance
|
|
+
|
|
+ These aren't always the same as the types we represent internally.
|
|
+
|
|
+ Loading view distance is always max(no-tick + 1, tick + 1)
|
|
+ - no-tick has 1 added because clients need an extra radius to render chunks
|
|
+ - tick has 1 added because it needs an extra radius of chunks to load before they can be marked ticking
|
|
+
|
|
+ Loading view distance is defined as the radius of chunks that will be brought to send-ready status, which means
|
|
+ it loads chunks in radius load-view-distance + 1.
|
|
+
|
|
+ The maximum value for send view distance is the load view distance. API can set it lower.
|
|
+ */
|
|
+
|
|
+ public PlayerChunkLoader(final PlayerChunkMap chunkMap, final PooledLinkedHashSets<EntityPlayer> pooledHashSets) {
|
|
+ this.chunkMap = chunkMap;
|
|
+ this.broadcastMap = new PlayerAreaMap(pooledHashSets,
|
|
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
|
|
+ if (player.needsChunkCenterUpdate) {
|
|
+ player.needsChunkCenterUpdate = false;
|
|
+ player.playerConnection.sendPacket(new PacketPlayOutViewCentre(currPosX, currPosZ));
|
|
+ }
|
|
+ PlayerChunkLoader.this.onChunkEnter(player, rangeX, rangeZ);
|
|
+ },
|
|
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
|
|
+ PlayerChunkLoader.this.onChunkLeave(player, rangeX, rangeZ);
|
|
+ });
|
|
+ this.loadMap = new PlayerAreaMap(pooledHashSets,
|
|
+ null,
|
|
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
|
|
+ if (newState != null) {
|
|
+ return;
|
|
+ }
|
|
+ PlayerChunkLoader.this.isTargetedForPlayerLoad.remove(MCUtil.getCoordinateKey(rangeX, rangeZ));
|
|
+ });
|
|
+ this.loadTicketCleanup = new PlayerAreaMap(pooledHashSets,
|
|
+ null,
|
|
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
|
|
+ if (newState != null) {
|
|
+ return;
|
|
+ }
|
|
+ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ);
|
|
+ PlayerChunkLoader.this.chunkMap.world.getChunkProvider().removeTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos);
|
|
+ if (PlayerChunkLoader.this.chunkTicketTracker.remove(chunkPos.pair())) {
|
|
+ --PlayerChunkLoader.this.concurrentChunkLoads;
|
|
+ }
|
|
+ });
|
|
+ this.tickMap = new PlayerAreaMap(pooledHashSets,
|
|
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
|
|
+ if (newState.size() != 1) {
|
|
+ return;
|
|
+ }
|
|
+ Chunk chunk = PlayerChunkLoader.this.chunkMap.world.getChunkProvider().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ);
|
|
+ if (chunk == null || !chunk.areNeighboursLoaded(2)) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ);
|
|
+ PlayerChunkLoader.this.chunkMap.world.getChunkProvider().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos);
|
|
+ },
|
|
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
|
|
+ if (newState != null) {
|
|
+ return;
|
|
+ }
|
|
+ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ);
|
|
+ PlayerChunkLoader.this.chunkMap.world.getChunkProvider().removeTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos);
|
|
+ });
|
|
+ }
|
|
+
|
|
+ protected final LongOpenHashSet isTargetedForPlayerLoad = new LongOpenHashSet();
|
|
+ protected final LongOpenHashSet chunkTicketTracker = new LongOpenHashSet();
|
|
+
|
|
+ // rets whether the chunk is at a loaded stage that is ready to be sent to players
|
|
+ public boolean isChunkPlayerLoaded(final int chunkX, final int chunkZ) {
|
|
+ final long key = MCUtil.getCoordinateKey(chunkX, chunkZ);
|
|
+ final PlayerChunk chunk = this.chunkMap.getVisibleChunk(key);
|
|
+
|
|
+ if (chunk == null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return chunk.getSendingChunk() != null && this.isTargetedForPlayerLoad.contains(key);
|
|
+ }
|
|
+
|
|
+ public boolean isChunkSent(final EntityPlayer player, final int chunkX, final int chunkZ) {
|
|
+ final PlayerLoaderData data = this.playerMap.get(player);
|
|
+ if (data == null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return data.hasSentChunk(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ protected int getMaxConcurrentChunkSends() {
|
|
+ double config = TuinityConfig.playerMaxConcurrentChunkSends;
|
|
+ return Math.max(1, config <= 0 ? (int)Math.ceil(-config * this.chunkMap.world.getPlayers().size()) : (int)config);
|
|
+ }
|
|
+
|
|
+ protected int getMaxChunkLoads() {
|
|
+ double config = TuinityConfig.playerMaxConcurrentChunkLoads;
|
|
+ return Math.max(1, (config <= 0 ? (int)Math.ceil(-config * MinecraftServer.getServer().getPlayerCount()) : (int)config) * 9);
|
|
+ }
|
|
+
|
|
+ protected double getTargetSendRatePerPlayer() {
|
|
+ double config = TuinityConfig.playerTargetChunkSendRate;
|
|
+ return config <= 0 ? -config : config / MinecraftServer.getServer().getPlayerCount();
|
|
+ }
|
|
+
|
|
+ public void onChunkPlayerTickReady(final int chunkX, final int chunkZ) {
|
|
+ final ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(chunkX, chunkZ);
|
|
+ this.chunkMap.world.getChunkProvider().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos);
|
|
+ }
|
|
+
|
|
+ public void onChunkSendReady(final int chunkX, final int chunkZ) {
|
|
+ final long chunkKey = MCUtil.getCoordinateKey(chunkX, chunkZ);
|
|
+
|
|
+ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ);
|
|
+
|
|
+ if (playersInSendRange == null) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final Object[] rawData = playersInSendRange.getBackingSet();
|
|
+ for (int i = 0, len = rawData.length; i < len; ++i) {
|
|
+ final Object raw = rawData[i];
|
|
+
|
|
+ if (!(raw instanceof EntityPlayer)) {
|
|
+ continue;
|
|
+ }
|
|
+ this.onChunkEnter((EntityPlayer)raw, chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ // now let's try and queue mid tick logic again
|
|
+ }
|
|
+
|
|
+ public void onChunkEnter(final EntityPlayer player, final int chunkX, final int chunkZ) {
|
|
+ final PlayerLoaderData data = this.playerMap.get(player);
|
|
+
|
|
+ if (data == null) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (data.hasSentChunk(chunkX, chunkZ) || !this.isChunkPlayerLoaded(chunkX, chunkZ)) {
|
|
+ // if we don't have player tickets, then the load logic will pick this up and queue to send
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final long playerPos = this.broadcastMap.getLastCoordinate(player);
|
|
+ final int playerChunkX = MCUtil.getCoordinateX(playerPos);
|
|
+ final int playerChunkZ = MCUtil.getCoordinateZ(playerPos);
|
|
+ final int manhattanDistance = Math.abs(playerChunkX - chunkX) + Math.abs(playerChunkZ - chunkZ);
|
|
+
|
|
+ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, 0.0);
|
|
+ data.sendQueue.add(holder);
|
|
+ }
|
|
+
|
|
+ public void onChunkLoad(final int chunkX, final int chunkZ) {
|
|
+ if (this.chunkTicketTracker.remove(MCUtil.getCoordinateKey(chunkX, chunkZ))) {
|
|
+ --this.concurrentChunkLoads;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void onChunkLeave(final EntityPlayer player, final int chunkX, final int chunkZ) {
|
|
+ final PlayerLoaderData data = this.playerMap.get(player);
|
|
+
|
|
+ if (data == null) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ data.unloadChunk(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ public void addPlayer(final EntityPlayer player) {
|
|
+ TickThread.ensureTickThread("Cannot add player async");
|
|
+ if (!player.isRealPlayer) {
|
|
+ return;
|
|
+ }
|
|
+ final PlayerLoaderData data = new PlayerLoaderData(player, this);
|
|
+ if (this.playerMap.putIfAbsent(player, data) == null) {
|
|
+ data.update();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void removePlayer(final EntityPlayer player) {
|
|
+ TickThread.ensureTickThread("Cannot remove player async");
|
|
+ if (!player.isRealPlayer) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final PlayerLoaderData loaderData = this.playerMap.remove(player);
|
|
+ if (loaderData == null) {
|
|
+ return;
|
|
+ }
|
|
+ loaderData.remove();
|
|
+ this.chunkLoadQueue.remove(loaderData);
|
|
+ this.chunkSendQueue.remove(loaderData);
|
|
+ this.chunkSendWaitQueue.remove(loaderData);
|
|
+ synchronized (this.sendingChunkCounts) {
|
|
+ final int count = this.sendingChunkCounts.removeInt(loaderData);
|
|
+ if (count != 0) {
|
|
+ concurrentChunkSends.getAndAdd(-count);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void updatePlayer(final EntityPlayer player) {
|
|
+ TickThread.ensureTickThread("Cannot update player async");
|
|
+ if (!player.isRealPlayer) {
|
|
+ return;
|
|
+ }
|
|
+ final PlayerLoaderData loaderData = this.playerMap.get(player);
|
|
+ if (loaderData != null) {
|
|
+ loaderData.update();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public PlayerLoaderData getData(final EntityPlayer player) {
|
|
+ return this.playerMap.get(player);
|
|
+ }
|
|
+
|
|
+ public void tick() {
|
|
+ TickThread.ensureTickThread("Cannot tick async");
|
|
+ for (final PlayerLoaderData data : this.playerMap.values()) {
|
|
+ data.update();
|
|
+ }
|
|
+ this.tickMidTick();
|
|
+ }
|
|
+
|
|
+ protected static final AtomicInteger concurrentChunkSends = new AtomicInteger();
|
|
+ protected final Reference2IntOpenHashMap<PlayerLoaderData> sendingChunkCounts = new Reference2IntOpenHashMap<>();
|
|
+ private void trySendChunks() {
|
|
+ final long time = System.nanoTime();
|
|
+ // drain entries from wait queue
|
|
+ while (!this.chunkSendWaitQueue.isEmpty()) {
|
|
+ final PlayerLoaderData data = this.chunkSendWaitQueue.first();
|
|
+
|
|
+ if (data.nextChunkSendTarget > time) {
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ this.chunkSendWaitQueue.pollFirst();
|
|
+
|
|
+ this.chunkSendQueue.add(data);
|
|
+ }
|
|
+
|
|
+ if (this.chunkSendQueue.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final int maxSends = this.getMaxConcurrentChunkSends();
|
|
+ final double sendRate = this.getTargetSendRatePerPlayer();
|
|
+ final long nextDeadline = (long)((1 / sendRate) * 1.0e9) + time;
|
|
+ for (;;) {
|
|
+ if (this.chunkSendQueue.isEmpty()) {
|
|
+ break;
|
|
+ }
|
|
+ final int currSends = concurrentChunkSends.get();
|
|
+ if (currSends >= maxSends) {
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (!concurrentChunkSends.compareAndSet(currSends, currSends + 1)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // send chunk
|
|
+
|
|
+ final PlayerLoaderData data = this.chunkSendQueue.removeFirst();
|
|
+
|
|
+ final ChunkPriorityHolder queuedSend = data.sendQueue.pollFirst();
|
|
+ if (queuedSend == null) {
|
|
+ concurrentChunkSends.getAndDecrement(); // we never sent, so decrease
|
|
+ // stop iterating over players who have nothing to send
|
|
+ if (this.chunkSendQueue.isEmpty()) {
|
|
+ // nothing left
|
|
+ break;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (!this.isChunkPlayerLoaded(queuedSend.chunkX, queuedSend.chunkZ)) {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+
|
|
+ data.nextChunkSendTarget = nextDeadline;
|
|
+ this.chunkSendWaitQueue.add(data);
|
|
+
|
|
+ synchronized (this.sendingChunkCounts) {
|
|
+ this.sendingChunkCounts.addTo(data, 1);
|
|
+ }
|
|
+
|
|
+ data.sendChunk(queuedSend.chunkX, queuedSend.chunkZ, () -> {
|
|
+ synchronized (this.sendingChunkCounts) {
|
|
+ final int count = this.sendingChunkCounts.getInt(data);
|
|
+ if (count == 0) {
|
|
+ // disconnected, so we don't need to decrement: it will be decremented for us
|
|
+ return;
|
|
+ }
|
|
+ if (count == 1) {
|
|
+ this.sendingChunkCounts.removeInt(data);
|
|
+ } else {
|
|
+ this.sendingChunkCounts.put(data, count - 1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ concurrentChunkSends.getAndDecrement();
|
|
+ });
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected int concurrentChunkLoads;
|
|
+ private void tryLoadChunks() {
|
|
+ if (this.chunkLoadQueue.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final int maxLoads = this.getMaxChunkLoads();
|
|
+ for (;;) {
|
|
+ final PlayerLoaderData data = this.chunkLoadQueue.pollFirst();
|
|
+
|
|
+ final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst();
|
|
+ if (queuedLoad == null) {
|
|
+ if (this.chunkLoadQueue.isEmpty()) {
|
|
+ break;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) {
|
|
+ // already loaded!
|
|
+ data.loadQueue.pollFirst(); // already loaded so we just skip
|
|
+ this.chunkLoadQueue.add(data);
|
|
+
|
|
+ // ensure the chunk is queued to send
|
|
+ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final long chunkKey = MCUtil.getCoordinateKey(queuedLoad.chunkX, queuedLoad.chunkZ);
|
|
+
|
|
+ final double priority = queuedLoad.priority;
|
|
+ // while we do need to rate limit chunk loads, the logic for sending chunks requires that tickets are present.
|
|
+ // when chunks are loaded (i.e spawn) but do not have this player's tickets, they have to wait behind the
|
|
+ // load queue. To avoid this problem, we check early here if tickets are required to load the chunk - if they
|
|
+ // aren't required, it bypasses the limiter system.
|
|
+ boolean unloadedTargetChunk = false;
|
|
+ unloaded_check:
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ final int offX = queuedLoad.chunkX + dx;
|
|
+ final int offZ = queuedLoad.chunkZ + dz;
|
|
+ if (this.chunkMap.world.getChunkProvider().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) == null) {
|
|
+ unloadedTargetChunk = true;
|
|
+ break unloaded_check;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (unloadedTargetChunk && priority > 0.0) {
|
|
+ // priority > 0.0 implies rate limited chunks
|
|
+
|
|
+ final int currentChunkLoads = this.concurrentChunkLoads;
|
|
+ if (currentChunkLoads >= maxLoads) {
|
|
+ // don't poll, we didn't load it
|
|
+ this.chunkLoadQueue.add(data);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // can only poll after we decide to load
|
|
+ data.loadQueue.pollFirst();
|
|
+
|
|
+ // now that we've polled we can re-add to load queue
|
|
+ this.chunkLoadQueue.add(data);
|
|
+
|
|
+ // add necessary tickets to load chunk up to send-ready
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ final int offX = queuedLoad.chunkX + dx;
|
|
+ final int offZ = queuedLoad.chunkZ + dz;
|
|
+ final ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(offX, offZ);
|
|
+
|
|
+ this.chunkMap.world.getChunkProvider().addTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos);
|
|
+ if (this.chunkMap.world.getChunkProvider().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) != null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (priority > 0.0 && this.chunkTicketTracker.add(MCUtil.getCoordinateKey(offX, offZ))) {
|
|
+ // wont reach here if unloadedTargetChunk is false
|
|
+ ++this.concurrentChunkLoads;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // mark that we've added tickets here
|
|
+ this.isTargetedForPlayerLoad.add(chunkKey);
|
|
+
|
|
+ // it's possible all we needed was the player tickets to queue up the send.
|
|
+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) {
|
|
+ // yup, all we needed.
|
|
+ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void tickMidTick() {
|
|
+ // try to send more chunks
|
|
+ this.trySendChunks();
|
|
+
|
|
+ // try to queue more chunks to load
|
|
+ this.tryLoadChunks();
|
|
+ }
|
|
+
|
|
+ static final class ChunkPriorityHolder {
|
|
+ public final int chunkX;
|
|
+ public final int chunkZ;
|
|
+ public final int manhattanDistanceToPlayer;
|
|
+ public final double priority;
|
|
+
|
|
+ public ChunkPriorityHolder(final int chunkX, final int chunkZ, final int manhattanDistanceToPlayer, final double priority) {
|
|
+ this.chunkX = chunkX;
|
|
+ this.chunkZ = chunkZ;
|
|
+ this.manhattanDistanceToPlayer = manhattanDistanceToPlayer;
|
|
+ this.priority = priority;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static final class PlayerLoaderData {
|
|
+
|
|
+ protected static final float FOV = 110.0f;
|
|
+ protected static final double PRIORITISED_DISTANCE = 12.0 * 16.0;
|
|
+
|
|
+ // Player max sprint speed is approximately 8m/s
|
|
+ protected static final double LOOK_PRIORITY_SPEED_THRESHOLD = (10.0/20.0) * (10.0/20.0);
|
|
+ protected static final double LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD = 3.0f;
|
|
+
|
|
+ protected double lastLocX = Double.NEGATIVE_INFINITY;
|
|
+ protected double lastLocZ = Double.NEGATIVE_INFINITY;
|
|
+
|
|
+ protected int lastChunkX;
|
|
+ protected int lastChunkZ;
|
|
+
|
|
+ // this is corrected so that 0 is along the positive x-axis
|
|
+ protected float lastYaw = Float.NEGATIVE_INFINITY;
|
|
+
|
|
+ protected int lastSendDistance = Integer.MIN_VALUE;
|
|
+ protected int lastLoadDistance = Integer.MIN_VALUE;
|
|
+ protected int lastTickDistance = Integer.MIN_VALUE;
|
|
+ protected boolean usingLookingPriority;
|
|
+
|
|
+ protected final EntityPlayer player;
|
|
+ protected final PlayerChunkLoader loader;
|
|
+
|
|
+ // warning: modifications of this field must be aware that the loadQueue inside PlayerChunkLoader uses this field
|
|
+ // in a comparator!
|
|
+ protected final ArrayDeque<ChunkPriorityHolder> loadQueue = new ArrayDeque<>();
|
|
+ protected final LongOpenHashSet sentChunks = new LongOpenHashSet();
|
|
+
|
|
+ protected final TreeSet<ChunkPriorityHolder> sendQueue = new TreeSet<>((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> {
|
|
+ final int distanceCompare = Integer.compare(p1.manhattanDistanceToPlayer, p2.manhattanDistanceToPlayer);
|
|
+ if (distanceCompare != 0) {
|
|
+ return distanceCompare;
|
|
+ }
|
|
+
|
|
+ final int coordinateXCompare = Integer.compare(p1.chunkX, p2.chunkX);
|
|
+ if (coordinateXCompare != 0) {
|
|
+ return coordinateXCompare;
|
|
+ }
|
|
+
|
|
+ return Integer.compare(p1.chunkZ, p2.chunkZ);
|
|
+ });
|
|
+
|
|
+ protected int sendViewDistance = -1;
|
|
+ protected int loadViewDistance = -1;
|
|
+ protected int tickViewDistance = -1;
|
|
+
|
|
+ protected long nextChunkSendTarget;
|
|
+
|
|
+ public PlayerLoaderData(final EntityPlayer player, final PlayerChunkLoader loader) {
|
|
+ this.player = player;
|
|
+ this.loader = loader;
|
|
+ }
|
|
+
|
|
+ // these view distance methods are for api
|
|
+ public int getTargetSendViewDistance() {
|
|
+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance;
|
|
+ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance);
|
|
+ final int clientViewDistance = this.getClientViewDistance();
|
|
+ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!TuinityConfig.playerAutoConfigureSendViewDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance);
|
|
+ return sendViewDistance;
|
|
+ }
|
|
+
|
|
+ public void setTargetSendViewDistance(final int distance) {
|
|
+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) {
|
|
+ throw new IllegalArgumentException(Integer.toString(distance));
|
|
+ }
|
|
+ this.sendViewDistance = distance;
|
|
+ }
|
|
+
|
|
+ public int getTargetNoTickViewDistance() {
|
|
+ return (this.loadViewDistance == -1 ? this.getLoadDistance() : this.loadViewDistance) - 1;
|
|
+ }
|
|
+
|
|
+ public void setTargetNoTickViewDistance(final int distance) {
|
|
+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE)) {
|
|
+ throw new IllegalArgumentException(Integer.toString(distance));
|
|
+ }
|
|
+ this.loadViewDistance = distance == -1 ? -1 : distance + 1;
|
|
+ }
|
|
+
|
|
+ public int getTargetTickViewDistance() {
|
|
+ return this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance;
|
|
+ }
|
|
+
|
|
+ public void setTargetTickViewDistance(final int distance) {
|
|
+ if (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE) {
|
|
+ throw new IllegalArgumentException(Integer.toString(distance));
|
|
+ }
|
|
+ this.tickViewDistance = distance;
|
|
+ }
|
|
+
|
|
+ protected int getLoadDistance() {
|
|
+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance;
|
|
+
|
|
+ return Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance);
|
|
+ }
|
|
+
|
|
+ public boolean hasSentChunk(final int chunkX, final int chunkZ) {
|
|
+ return this.sentChunks.contains(MCUtil.getCoordinateKey(chunkX, chunkZ));
|
|
+ }
|
|
+
|
|
+ public void sendChunk(final int chunkX, final int chunkZ, final Runnable onChunkSend) {
|
|
+ if (this.sentChunks.add(MCUtil.getCoordinateKey(chunkX, chunkZ))) {
|
|
+ this.player.getWorldServer().getChunkProvider().playerChunkMap.sendChunk(this.player,
|
|
+ new ChunkCoordIntPair(chunkX, chunkZ), new Packet[2], false, true); // unloaded, loaded
|
|
+ this.player.playerConnection.networkManager.execute(onChunkSend);
|
|
+ } else {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void unloadChunk(final int chunkX, final int chunkZ) {
|
|
+ if (this.sentChunks.remove(MCUtil.getCoordinateKey(chunkX, chunkZ))) {
|
|
+ this.player.getWorldServer().getChunkProvider().playerChunkMap.sendChunk(this.player,
|
|
+ new ChunkCoordIntPair(chunkX, chunkZ), null, true, false); // unloaded, loaded
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected static boolean triangleIntersects(final double p1x, final double p1z, // triangle point
|
|
+ final double p2x, final double p2z, // triangle point
|
|
+ final double p3x, final double p3z, // triangle point
|
|
+
|
|
+ final double targetX, final double targetZ) { // point
|
|
+ // from barycentric coordinates:
|
|
+ // targetX = a*p1x + b*p2x + c*p3x
|
|
+ // targetZ = a*p1z + b*p2z + c*p3z
|
|
+ // 1.0 = a*1.0 + b*1.0 + c*1.0
|
|
+ // where a, b, c >= 0.0
|
|
+ // so, if any of a, b, c are less-than zero then there is no intersection.
|
|
+
|
|
+ // d = ((p2z - p3z)(p1x - p3x) + (p3x - p2x)(p1z - p3z))
|
|
+ // a = ((p2z - p3z)(targetX - p3x) + (p3x - p2x)(targetZ - p3z)) / d
|
|
+ // b = ((p3z - p1z)(targetX - p3x) + (p1x - p3x)(targetZ - p3z)) / d
|
|
+ // c = 1.0 - a - b
|
|
+
|
|
+ final double d = (p2z - p3z)*(p1x - p3x) + (p3x - p2x)*(p1z - p3z);
|
|
+ final double a = ((p2z - p3z)*(targetX - p3x) + (p3x - p2x)*(targetZ - p3z)) / d;
|
|
+
|
|
+ if (a < 0.0 || a > 1.0) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ final double b = ((p3z - p1z)*(targetX - p3x) + (p1x - p3x)*(targetZ - p3z)) / d;
|
|
+ if (b < 0.0 || b > 1.0) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ final double c = 1.0 - a - b;
|
|
+
|
|
+ return c >= 0.0 && c <= 1.0;
|
|
+ }
|
|
+
|
|
+ public void remove() {
|
|
+ this.loader.broadcastMap.remove(this.player);
|
|
+ this.loader.loadMap.remove(this.player);
|
|
+ this.loader.loadTicketCleanup.remove(this.player);
|
|
+ this.loader.tickMap.remove(this.player);
|
|
+ }
|
|
+
|
|
+ protected int getClientViewDistance() {
|
|
+ return this.player.clientViewDistance == null ? -1 : this.player.clientViewDistance.intValue();
|
|
+ }
|
|
+
|
|
+ public void update() {
|
|
+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance;
|
|
+ // load view cannot be less-than tick view + 1
|
|
+ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance);
|
|
+ // send view cannot be greater-than load view
|
|
+ final int clientViewDistance = this.getClientViewDistance();
|
|
+ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!TuinityConfig.playerAutoConfigureSendViewDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance);
|
|
+
|
|
+ final double posX = this.player.locX();
|
|
+ final double posZ = this.player.locZ();
|
|
+ final float yaw = MCUtil.normalizeYaw(this.player.yaw + 90.0f); // mc yaw 0 is along the positive z axis, but obviously this is really dumb - offset so we are at positive x-axis
|
|
+
|
|
+ // in general, we really only want to prioritise chunks in front if we know we're moving pretty fast into them.
|
|
+ final boolean useLookPriority = TuinityConfig.playerFrustumPrioritisation && (this.player.getMot().magnitudeXZSquared() > LOOK_PRIORITY_SPEED_THRESHOLD ||
|
|
+ this.player.abilities.isFlying);
|
|
+
|
|
+ // make sure we're in the send queue
|
|
+ this.loader.chunkSendWaitQueue.add(this);
|
|
+
|
|
+ if (
|
|
+ // has view distance stayed the same?
|
|
+ sendViewDistance == this.lastSendDistance
|
|
+ && loadViewDistance == this.lastLoadDistance
|
|
+ && tickViewDistance == this.lastTickDistance
|
|
+
|
|
+ && (this.usingLookingPriority ? (
|
|
+ // has our block stayed the same (this also accounts for chunk change)?
|
|
+ MathHelper.floor(this.lastLocX) == MathHelper.floor(posX)
|
|
+ && MathHelper.floor(this.lastLocZ) == MathHelper.floor(posZ)
|
|
+ ) : (
|
|
+ // has our chunk stayed the same
|
|
+ (MathHelper.floor(this.lastLocX) >> 4) == (MathHelper.floor(posX) >> 4)
|
|
+ && (MathHelper.floor(this.lastLocZ) >> 4) == (MathHelper.floor(posZ) >> 4)
|
|
+ ))
|
|
+
|
|
+ // has our decision about look priority changed?
|
|
+ && this.usingLookingPriority == useLookPriority
|
|
+
|
|
+ // if we are currently using look priority, has our yaw stayed within recalc threshold?
|
|
+ && (!this.usingLookingPriority || Math.abs(yaw - this.lastYaw) <= LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD)
|
|
+ ) {
|
|
+ // nothing we care about changed, so we're not re-calculating
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final int centerChunkX = MathHelper.floor(posX) >> 4;
|
|
+ final int centerChunkZ = MathHelper.floor(posZ) >> 4;
|
|
+
|
|
+ this.player.needsChunkCenterUpdate = true;
|
|
+ this.loader.broadcastMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, sendViewDistance);
|
|
+ this.player.needsChunkCenterUpdate = false;
|
|
+ this.loader.loadMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance);
|
|
+ this.loader.loadTicketCleanup.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance + 1);
|
|
+ this.loader.tickMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, tickViewDistance);
|
|
+
|
|
+ if (sendViewDistance != this.lastSendDistance) {
|
|
+ // update the view radius for client
|
|
+ // note that this should be after the map calls because the client wont expect unload calls not in its VD
|
|
+ // and it's possible we decreased VD here
|
|
+ this.player.playerConnection.sendPacket(new PacketPlayOutViewDistance(sendViewDistance - 1)); // client already expects the 1 radius neighbours, so subtract 1.
|
|
+ }
|
|
+
|
|
+ this.lastLocX = posX;
|
|
+ this.lastLocZ = posZ;
|
|
+ this.lastYaw = yaw;
|
|
+ this.lastSendDistance = sendViewDistance;
|
|
+ this.lastLoadDistance = loadViewDistance;
|
|
+ this.lastTickDistance = tickViewDistance;
|
|
+ this.usingLookingPriority = useLookPriority;
|
|
+
|
|
+ this.lastChunkX = centerChunkX;
|
|
+ this.lastChunkZ = centerChunkZ;
|
|
+
|
|
+ // points for player "view" triangle:
|
|
+
|
|
+ // obviously, the player pos is a vertex
|
|
+ final double p1x = posX;
|
|
+ final double p1z = posZ;
|
|
+
|
|
+ // to the left of the looking direction
|
|
+ final double p2x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector
|
|
+ + p1x; // offset vector
|
|
+ final double p2z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector
|
|
+ + p1z; // offset vector
|
|
+
|
|
+ // to the right of the looking direction
|
|
+ final double p3x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector
|
|
+ + p1x; // offset vector
|
|
+ final double p3z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector
|
|
+ + p1z; // offset vector
|
|
+
|
|
+ // now that we have all of our points, we can recalculate the load queue
|
|
+
|
|
+ final List<ChunkPriorityHolder> loadQueue = new ArrayList<>();
|
|
+
|
|
+ // clear send queue, we are re-sorting
|
|
+ this.sendQueue.clear();
|
|
+
|
|
+ final int searchViewDistance = Math.max(loadViewDistance, sendViewDistance);
|
|
+
|
|
+ for (int dx = -searchViewDistance; dx <= searchViewDistance; ++dx) {
|
|
+ for (int dz = -searchViewDistance; dz <= searchViewDistance; ++dz) {
|
|
+ final int chunkX = dx + centerChunkX;
|
|
+ final int chunkZ = dz + centerChunkZ;
|
|
+ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz));
|
|
+
|
|
+ if (this.hasSentChunk(chunkX, chunkZ)) {
|
|
+ // already sent (which means it is also loaded)
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final boolean loadChunk = squareDistance <= loadViewDistance;
|
|
+ final boolean sendChunk = squareDistance <= sendViewDistance;
|
|
+
|
|
+ final boolean prioritised = useLookPriority && triangleIntersects(
|
|
+ // prioritisation triangle
|
|
+ p1x, p1z, p2x, p2z, p3x, p3z,
|
|
+
|
|
+ // center of chunk
|
|
+ (double)((chunkX << 4) | 8), (double)((chunkZ << 4) | 8)
|
|
+ );
|
|
+
|
|
+
|
|
+ final int manhattanDistance = (Math.abs(dx) + Math.abs(dz));
|
|
+
|
|
+ final double priority;
|
|
+
|
|
+ if (squareDistance <= TuinityConfig.playerMinChunkLoadRadius) {
|
|
+ // priority should be negative, and we also want to order it from center outwards
|
|
+ // so we want (0,0) to be the smallest, and (minLoadedRadius,minLoadedRadius) to be the greatest
|
|
+ priority = -((2 * TuinityConfig.playerMinChunkLoadRadius + 1) - (dx + dz));
|
|
+ } else {
|
|
+ if (prioritised) {
|
|
+ // we don't prioritise these chunks above others because we also want to make sure some chunks
|
|
+ // will be loaded if the player changes direction
|
|
+ priority = (double)manhattanDistance / 6.0;
|
|
+ } else {
|
|
+ priority = (double)manhattanDistance;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, priority);
|
|
+
|
|
+ if (!this.loader.isChunkPlayerLoaded(chunkX, chunkZ)) {
|
|
+ if (loadChunk) {
|
|
+ loadQueue.add(holder);
|
|
+ }
|
|
+ } else {
|
|
+ // loaded but not sent: so queue it!
|
|
+ if (sendChunk) {
|
|
+ this.sendQueue.add(holder);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ loadQueue.sort((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> {
|
|
+ return Double.compare(p1.priority, p2.priority);
|
|
+ });
|
|
+
|
|
+ // we're modifying loadQueue, must remove
|
|
+ this.loader.chunkLoadQueue.remove(this);
|
|
+
|
|
+ this.loadQueue.clear();
|
|
+ this.loadQueue.addAll(loadQueue);
|
|
+
|
|
+ // must re-add
|
|
+ this.loader.chunkLoadQueue.add(this);
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java
|
|
index edb9b246bf327f67a8500f77f81b46eb9e04c9b0..e1dc7c1025e19f7393a45719af8fe7aae016184d 100644
|
|
--- a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java
|
|
+++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java
|
|
@@ -244,6 +244,25 @@ public final class TuinityConfig {
|
|
sendFullPosForHardCollidingEntities = TuinityConfig.getBoolean("send-full-pos-for-hard-colliding-entities", true);
|
|
}
|
|
|
|
+ public static int playerMinChunkLoadRadius;
|
|
+ public static double playerMaxConcurrentChunkSends;
|
|
+ public static double playerMaxConcurrentChunkLoads;
|
|
+ public static boolean playerAutoConfigureSendViewDistance;
|
|
+ public static boolean enableMC162253Workaround;
|
|
+ public static double playerTargetChunkSendRate;
|
|
+ public static boolean playerFrustumPrioritisation;
|
|
+
|
|
+ private static void newPlayerChunkManagement() {
|
|
+ playerMinChunkLoadRadius = TuinityConfig.getInt("player-chunks.min-load-radius", 2);
|
|
+ playerMaxConcurrentChunkSends = TuinityConfig.getDouble("player-chunks.max-concurrent-sends", 5.0);
|
|
+ playerMaxConcurrentChunkLoads = TuinityConfig.getDouble("player-chunks.max-concurrent-loads", -6.0);
|
|
+ playerAutoConfigureSendViewDistance = TuinityConfig.getBoolean("player-chunks.autoconfig-send-distance", true);
|
|
+ // this costs server bandwidth. latest phosphor or starlight on the client fixes mc162253 anyways.
|
|
+ enableMC162253Workaround = TuinityConfig.getBoolean("player-chunks.enable-mc162253-workaround", true);
|
|
+ playerTargetChunkSendRate = TuinityConfig.getDouble("player-chunks.target-chunk-send-rate", -35.0);
|
|
+ playerFrustumPrioritisation = TuinityConfig.getBoolean("player-chunks.enable-frustum-priority", false);
|
|
+ }
|
|
+
|
|
public static final class WorldConfig {
|
|
|
|
public final String worldName;
|
|
diff --git a/src/main/java/net/minecraft/network/NetworkManager.java b/src/main/java/net/minecraft/network/NetworkManager.java
|
|
index 02ad0611836160a1d0ba6b26476e6bd336d55681..0f8cbe1f656b46f71c6494bd2e0057be63017272 100644
|
|
--- a/src/main/java/net/minecraft/network/NetworkManager.java
|
|
+++ b/src/main/java/net/minecraft/network/NetworkManager.java
|
|
@@ -123,6 +123,28 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
|
}
|
|
}
|
|
// Tuinity end - allow controlled flushing
|
|
+ // Tuinity start - add pending task queue
|
|
+ private final Queue<Runnable> pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>();
|
|
+ public void execute(final Runnable run) {
|
|
+ if (this.channel == null || !this.channel.isRegistered()) {
|
|
+ run.run();
|
|
+ return;
|
|
+ }
|
|
+ final boolean queue = !this.packetQueue.isEmpty();
|
|
+ if (!queue) {
|
|
+ this.channel.eventLoop().execute(run);
|
|
+ } else {
|
|
+ this.pendingTasks.add(run);
|
|
+ if (this.packetQueue.isEmpty()) {
|
|
+ // something flushed async, dump tasks now
|
|
+ Runnable r;
|
|
+ while ((r = this.pendingTasks.poll()) != null) {
|
|
+ this.channel.eventLoop().execute(r);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Tuinity end - add pending task queue
|
|
|
|
public NetworkManager(EnumProtocolDirection enumprotocoldirection) {
|
|
this.h = enumprotocoldirection;
|
|
@@ -505,6 +527,7 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
|
return false;
|
|
}
|
|
private boolean processQueue() {
|
|
+ try { // Tuinity - add pending task queue
|
|
if (this.packetQueue.isEmpty()) return true;
|
|
final boolean needsFlush = this.canFlush; // Tuinity - make only one flush call per sendPacketQueue() call
|
|
boolean hasWrotePacket = false;
|
|
@@ -534,6 +557,12 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
|
|
}
|
|
}
|
|
return true;
|
|
+ } finally { // Tuinity start - add pending task queue
|
|
+ Runnable r;
|
|
+ while ((r = this.pendingTasks.poll()) != null) {
|
|
+ this.channel.eventLoop().execute(r);
|
|
+ }
|
|
+ } // Tuinity end - add pending task queue
|
|
}
|
|
// Paper end
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
|
|
index e832ea0497b2d6af7556bda7f6728e72c48d80a8..b7399d17dd64ca8b1f1fab405cb0ac914dc53b98 100644
|
|
--- a/src/main/java/net/minecraft/server/MCUtil.java
|
|
+++ b/src/main/java/net/minecraft/server/MCUtil.java
|
|
@@ -697,7 +697,7 @@ public final class MCUtil {
|
|
|
|
worldData.addProperty("name", world.getWorld().getName());
|
|
worldData.addProperty("view-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance());
|
|
- worldData.addProperty("no-view-distance", world.getChunkProvider().playerChunkMap.getRawNoTickViewDistance());
|
|
+ worldData.addProperty("no-view-distance", world.getChunkProvider().playerChunkMap.playerChunkManager.getTargetNoTickViewDistance()); // Tuinity - replace old player chunk management
|
|
worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
|
|
worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange);
|
|
worldData.addProperty("visible-chunk-count", visibleChunks.size());
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java
|
|
index c474ee61d98772a2852c44dec1c4a1037472ed2c..ad90735b5daa658cdd5467eadcb29183d018b1fd 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java
|
|
@@ -42,7 +42,7 @@ public abstract class ChunkMapDistance {
|
|
public final Long2ObjectOpenHashMap<ArraySetSorted<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
|
|
private final ChunkMapDistance.a ticketLevelTracker = new ChunkMapDistance.a(); final ChunkMapDistance.a getTicketTracker() { return this.ticketLevelTracker; } // Tuinity - OBFHELPER
|
|
public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used
|
|
- private final ChunkMapDistance.c g = new ChunkMapDistance.c(33);
|
|
+ //private final ChunkMapDistance.c g = new ChunkMapDistance.c(33); // Tuinity - no longer used
|
|
// Paper start use a queue, but still keep unique requirement
|
|
public final java.util.Queue<PlayerChunk> pendingChunkUpdates = new java.util.ArrayDeque<PlayerChunk>() {
|
|
@Override
|
|
@@ -175,7 +175,7 @@ public abstract class ChunkMapDistance {
|
|
com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot tick ChunkMapDistance off of the main-thread");// Tuinity
|
|
//this.f.a(); // Paper - no longer used
|
|
AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
|
|
- this.g.a();
|
|
+ //this.g.a(); // Tuinity - no longer used
|
|
int i = Integer.MAX_VALUE - this.ticketLevelTracker.a(Integer.MAX_VALUE);
|
|
boolean flag = i != 0;
|
|
|
|
@@ -322,7 +322,7 @@ public abstract class ChunkMapDistance {
|
|
AsyncCatcher.catchOp("ChunkMapDistance::addPriorityTicket");
|
|
long pair = coords.pair();
|
|
PlayerChunk chunk = chunkMap.getUpdatingChunk(pair);
|
|
- boolean needsTicket = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(pair) != null && !hasPlayerTicket(coords, 33);
|
|
+ boolean needsTicket = false; // Tuinity - replace old loader system
|
|
|
|
if (needsTicket) {
|
|
Ticket<?> ticket = new Ticket<>(TicketType.PLAYER, 33, coords);
|
|
@@ -454,7 +454,7 @@ public abstract class ChunkMapDistance {
|
|
return new ObjectOpenHashSet();
|
|
})).add(entityplayer);
|
|
//this.f.update(i, 0, true); // Paper - no longer used
|
|
- this.g.update(i, 0, true);
|
|
+ //this.g.update(i, 0, true); // Tuinity - no longer used
|
|
}
|
|
|
|
public void b(SectionPosition sectionposition, EntityPlayer entityplayer) {
|
|
@@ -467,7 +467,7 @@ public abstract class ChunkMapDistance {
|
|
if (objectset == null || objectset.isEmpty()) { // Paper
|
|
this.c.remove(i);
|
|
//this.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
|
|
- this.g.update(i, Integer.MAX_VALUE, false);
|
|
+ //this.g.update(i, Integer.MAX_VALUE, false); // Tuinity - no longer used
|
|
}
|
|
|
|
}
|
|
@@ -486,7 +486,7 @@ public abstract class ChunkMapDistance {
|
|
}
|
|
|
|
protected void setNoTickViewDistance(int i) { // Paper - force abi breakage on usage change
|
|
- this.g.a(i);
|
|
+ throw new UnsupportedOperationException(); // Tuinity - no longer relevant
|
|
}
|
|
|
|
public int b() {
|
|
@@ -573,6 +573,7 @@ public abstract class ChunkMapDistance {
|
|
}
|
|
}
|
|
|
|
+ /* Tuinity - replace old loader system
|
|
class c extends ChunkMapDistance.b {
|
|
|
|
private int e = 0; private int getViewDistance() { return e; } private void setViewDistance(int value) { this.e = value; } // Paper - OBFHELPER
|
|
@@ -791,6 +792,7 @@ public abstract class ChunkMapDistance {
|
|
return i <= this.e - 2;
|
|
}
|
|
}
|
|
+ */ // Tuinity - replace old loader system
|
|
|
|
class b extends ChunkMap {
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
|
|
index 9e96376a1d710c0ba7a763868b23fc586253e1a8..fe040615ff03478a20cdf8376f89a6b7d100ba61 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
|
|
@@ -946,6 +946,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.world.timings.doChunkMap.stopTiming(); // Spigot
|
|
this.world.getMethodProfiler().exitEnter("chunks");
|
|
this.world.timings.chunks.startTiming(); // Paper - timings
|
|
+ this.playerChunkMap.playerChunkManager.tick(); // Tuinity - this is mostly is to account for view distance changes
|
|
this.tickChunks();
|
|
this.world.timings.chunks.stopTiming(); // Paper - timings
|
|
this.world.timings.doChunkUnload.startTiming(); // Spigot
|
|
@@ -1241,6 +1242,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
@Override
|
|
protected boolean executeNext() {
|
|
com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot execute chunk tasks off-main thread");// Tuinity
|
|
+ ChunkProviderServer.this.playerChunkMap.playerChunkManager.tickMidTick(); // Tuinity
|
|
// CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
|
|
try {
|
|
boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask(); // Paper
|
|
diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java
|
|
index bffc0824151a6597d91cd854b343b2586f6b9322..62b95dcba8606330fbb3239e74c5eaf8baa3c51d 100644
|
|
--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java
|
|
@@ -261,7 +261,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
|
|
|
|
double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks
|
|
|
|
- boolean needsChunkCenterUpdate; // Paper - no-tick view distance
|
|
+ public boolean needsChunkCenterUpdate; // Paper - no-tick view distance // Tuinity - package-private -> public
|
|
public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - there are a lot of changes to do if we change all methods leading to the event
|
|
|
|
public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) {
|
|
diff --git a/src/main/java/net/minecraft/server/level/PlayerChunk.java b/src/main/java/net/minecraft/server/level/PlayerChunk.java
|
|
index a8edac296cbdb053bca4b56a890c3e9e5544d3a6..d9b134302f739efd93f50e93c8730b474f9a8ccf 100644
|
|
--- a/src/main/java/net/minecraft/server/level/PlayerChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/level/PlayerChunk.java
|
|
@@ -501,7 +501,7 @@ public class PlayerChunk {
|
|
// Paper start - per player view distance
|
|
// there can be potential desync with player's last mapped section and the view distance map, so use the
|
|
// view distance map here.
|
|
- com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerViewDistanceBroadcastMap;
|
|
+ com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerChunkManager.broadcastMap; // Tuinity - replace old player chunk manager
|
|
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> players = viewDistanceMap.getObjectsInRange(this.location);
|
|
if (players == null) {
|
|
return;
|
|
@@ -515,6 +515,7 @@ public class PlayerChunk {
|
|
continue;
|
|
}
|
|
EntityPlayer player = (EntityPlayer)temp;
|
|
+ if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.location.x, this.location.z)) continue; // Tuinity - replace player chunk management
|
|
|
|
int viewDistance = viewDistanceMap.getLastViewDistance(player);
|
|
long lastPosition = viewDistanceMap.getLastCoordinate(player);
|
|
@@ -534,6 +535,7 @@ public class PlayerChunk {
|
|
continue;
|
|
}
|
|
EntityPlayer player = (EntityPlayer)temp;
|
|
+ if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.location.x, this.location.z)) continue; // Tuinity - replace player chunk management
|
|
player.playerConnection.sendPacket(packet);
|
|
}
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
|
|
index 1e627159cbb35fac47d4d158299ec3da7c1f9bf5..d7eede51f1c4ebbe8e00b16efd6331c87db53bb4 100644
|
|
--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
|
|
@@ -236,20 +236,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap;
|
|
// Paper end - optimise PlayerChunkMap#isOutsideRange
|
|
// Paper start - no-tick view distance
|
|
- int noTickViewDistance;
|
|
- public final int getRawNoTickViewDistance() {
|
|
- return this.noTickViewDistance;
|
|
- }
|
|
- public final int getEffectiveNoTickViewDistance() {
|
|
- return this.noTickViewDistance == -1 ? this.getEffectiveViewDistance() : this.noTickViewDistance;
|
|
- }
|
|
- public final int getLoadViewDistance() {
|
|
- return Math.max(this.getEffectiveViewDistance(), this.getEffectiveNoTickViewDistance());
|
|
- }
|
|
-
|
|
- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceBroadcastMap;
|
|
- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceTickMap;
|
|
- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceNoTickMap;
|
|
+ public final com.tuinity.tuinity.chunk.PlayerChunkLoader playerChunkManager = new com.tuinity.tuinity.chunk.PlayerChunkLoader(this, this.pooledLinkedPlayerHashSets); // Tuinity - replace chunk loader
|
|
// Paper end - no-tick view distance
|
|
// Tuinity start - optimise checkDespawn
|
|
public static final int GENERAL_AREA_MAP_SQUARE_RADIUS = 38;
|
|
@@ -268,24 +255,14 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i];
|
|
int trackRange = this.entityTrackerTrackRanges[i];
|
|
|
|
- trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance()));
|
|
+ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, player.getBukkitEntity().getViewDistance())); // Tuinity - per player view distances
|
|
}
|
|
// Paper end - use distance map to optimise entity tracker
|
|
// Paper start - optimise PlayerChunkMap#isOutsideRange
|
|
this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE);
|
|
// Paper end - optimise PlayerChunkMap#isOutsideRange
|
|
// Paper start - no-tick view distance
|
|
- int effectiveTickViewDistance = this.getEffectiveViewDistance();
|
|
- int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance);
|
|
-
|
|
- if (!this.cannotLoadChunks(player)) {
|
|
- this.playerViewDistanceTickMap.add(player, chunkX, chunkZ, effectiveTickViewDistance);
|
|
- this.playerViewDistanceNoTickMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send)
|
|
- }
|
|
-
|
|
- player.needsChunkCenterUpdate = true;
|
|
- this.playerViewDistanceBroadcastMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured
|
|
- player.needsChunkCenterUpdate = false;
|
|
+ this.playerChunkManager.addPlayer(player); // Tuinity - replace chunk loader
|
|
// Paper end - no-tick view distance
|
|
// Tuinity start - optimise checkDespawn
|
|
this.playerGeneralAreaMap.add(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS);
|
|
@@ -304,9 +281,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
this.playerChunkTickRangeMap.remove(player);
|
|
// Paper end - optimise PlayerChunkMap#isOutsideRange
|
|
// Paper start - no-tick view distance
|
|
- this.playerViewDistanceBroadcastMap.remove(player);
|
|
- this.playerViewDistanceTickMap.remove(player);
|
|
- this.playerViewDistanceNoTickMap.remove(player);
|
|
+ this.playerChunkManager.removePlayer(player); // Tuinity - replace chunk loader
|
|
// Paper end - no-tick view distance
|
|
// Tuinity start - optimise checkDespawn
|
|
this.playerGeneralAreaMap.remove(player);
|
|
@@ -323,24 +298,14 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i];
|
|
int trackRange = this.entityTrackerTrackRanges[i];
|
|
|
|
- trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance()));
|
|
+ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, player.getBukkitEntity().getViewDistance())); // Tuinity - per player view distances
|
|
}
|
|
// Paper end - use distance map to optimise entity tracker
|
|
// Paper start - optimise PlayerChunkMap#isOutsideRange
|
|
this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE);
|
|
// Paper end - optimise PlayerChunkMap#isOutsideRange
|
|
// Paper start - no-tick view distance
|
|
- int effectiveTickViewDistance = this.getEffectiveViewDistance();
|
|
- int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance);
|
|
-
|
|
- if (!this.cannotLoadChunks(player)) {
|
|
- this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveTickViewDistance);
|
|
- this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send)
|
|
- }
|
|
-
|
|
- player.needsChunkCenterUpdate = true;
|
|
- this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured
|
|
- player.needsChunkCenterUpdate = false;
|
|
+ this.playerChunkManager.updatePlayer(player); // Tuinity - replace chunk loader
|
|
// Paper end - no-tick view distance
|
|
// Tuinity start - optimise checkDespawn
|
|
this.playerGeneralAreaMap.update(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS);
|
|
@@ -578,47 +543,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
// Paper end - optimise PlayerChunkMap#isOutsideRange
|
|
// Paper start - no-tick view distance
|
|
this.setNoTickViewDistance(this.world.paperConfig.noTickViewDistance);
|
|
- this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
|
|
- (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
|
|
- checkHighPriorityChunks(player);
|
|
- if (newState.size() != 1) {
|
|
- return;
|
|
- }
|
|
- Chunk chunk = PlayerChunkMap.this.world.getChunkProvider().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ);
|
|
- if (chunk == null || !chunk.areNeighboursLoaded(2)) {
|
|
- return;
|
|
- }
|
|
-
|
|
- ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ);
|
|
- PlayerChunkMap.this.world.getChunkProvider().addTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
|
|
- },
|
|
- (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
|
|
- if (newState != null) {
|
|
- return;
|
|
- }
|
|
- ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ);
|
|
- PlayerChunkMap.this.world.getChunkProvider().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
|
|
- PlayerChunkMap.this.world.getChunkProvider().clearPriorityTickets(chunkPos);
|
|
- }, (player, prevPos, newPos) -> {
|
|
- player.lastHighPriorityChecked = -1; // reset and recheck
|
|
- checkHighPriorityChunks(player);
|
|
- });
|
|
- this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
|
|
- this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
|
|
- (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
|
|
- if (player.needsChunkCenterUpdate) {
|
|
- player.needsChunkCenterUpdate = false;
|
|
- player.playerConnection.sendPacket(new PacketPlayOutViewCentre(currPosX, currPosZ));
|
|
- }
|
|
- PlayerChunkMap.this.sendChunk(player, new ChunkCoordIntPair(rangeX, rangeZ), new Packet[2], false, true); // unloaded, loaded
|
|
- },
|
|
- (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
|
|
- PlayerChunkMap.this.sendChunk(player, new ChunkCoordIntPair(rangeX, rangeZ), null, true, false); // unloaded, loaded
|
|
- });
|
|
+ // Tuinity - replace chunk loading system
|
|
// Paper end - no-tick view distance
|
|
// Tuinity start
|
|
this.dataRegionManager = new com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager(this.world, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new);
|
|
@@ -673,6 +598,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
|
|
public void checkHighPriorityChunks(EntityPlayer player) {
|
|
+ if (true) return; // Tuinity - replace player chunk loader
|
|
int currentTick = MinecraftServer.currentTick;
|
|
if (currentTick - player.lastHighPriorityChecked < 20 || !player.isRealPlayer) { // weed out fake players
|
|
return;
|
|
@@ -680,7 +606,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
player.lastHighPriorityChecked = currentTick;
|
|
Long2IntOpenHashMap priorities = new Long2IntOpenHashMap();
|
|
|
|
- int viewDistance = getEffectiveNoTickViewDistance();
|
|
+ int viewDistance = 10;//int viewDistance = getEffectiveNoTickViewDistance(); // Tuinity - replace player chunk loader
|
|
BlockPosition.MutableBlockPosition pos = new BlockPosition.MutableBlockPosition();
|
|
|
|
// Prioritize circular near
|
|
@@ -746,7 +672,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
|
|
private boolean shouldSkipPrioritization(ChunkCoordIntPair coord) {
|
|
- if (playerViewDistanceNoTickMap.getObjectsInRange(coord.pair()) == null) return true;
|
|
+ if (true) return true; // Tuinity - replace player chunk loader - unused outside paper player loader logic
|
|
PlayerChunk chunk = getUpdatingChunk(coord.pair());
|
|
return chunk != null && (chunk.isFullChunkReady());
|
|
}
|
|
@@ -1738,7 +1664,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
int k = this.viewDistance;
|
|
|
|
this.viewDistance = j;
|
|
- this.setNoTickViewDistance(this.getRawNoTickViewDistance()); //Paper - no-tick view distance - propagate changes to no-tick, which does the actual chunk loading/sending
|
|
+ this.playerChunkManager.setTickDistance(MathHelper.clamp(i, 2, 32)); // Tuinity - replace player loader system
|
|
}
|
|
|
|
}
|
|
@@ -1747,25 +1673,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
public final void setNoTickViewDistance(int viewDistance) {
|
|
com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update view distance off of the main thread"); // Tuinity
|
|
viewDistance = viewDistance == -1 ? -1 : MathHelper.clamp(viewDistance, 2, 32);
|
|
-
|
|
- this.noTickViewDistance = viewDistance;
|
|
- int loadViewDistance = this.getLoadViewDistance();
|
|
- this.chunkDistanceManager.setNoTickViewDistance(loadViewDistance + 2 + 2); // add 2 to account for the change to 31 -> 33 tickets // see notes in the distance map updating for the other + 2
|
|
-
|
|
- if (this.world != null && this.world.players != null) { // this can be called from constructor, where these aren't set
|
|
- for (EntityPlayer player : this.world.players) {
|
|
- PlayerConnection connection = player.playerConnection;
|
|
- if (connection != null) {
|
|
- // moved in from PlayerList
|
|
- connection.sendPacket(new PacketPlayOutViewDistance(loadViewDistance));
|
|
- }
|
|
- this.updateMaps(player);
|
|
- }
|
|
- }
|
|
+ this.playerChunkManager.setLoadDistance(viewDistance == -1 ? -1 : viewDistance + 1); // Tuinity - replace player loader system - add 1 here, we need an extra one to send to clients for chunks in this viewDistance to render
|
|
}
|
|
// Paper end - no-tick view distance
|
|
|
|
- protected void sendChunk(EntityPlayer entityplayer, ChunkCoordIntPair chunkcoordintpair, Packet<?>[] apacket, boolean flag, boolean flag1) {
|
|
+ public void sendChunk(EntityPlayer entityplayer, ChunkCoordIntPair chunkcoordintpair, Packet<?>[] apacket, boolean flag, boolean flag1) { // Tuinity - protected -> public
|
|
if (entityplayer.world == this.world) {
|
|
if (flag1 && !flag) {
|
|
PlayerChunk playerchunk = this.getVisibleChunk(chunkcoordintpair.pair());
|
|
@@ -2159,6 +2071,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}*/ // Paper end - replaced by distance map
|
|
|
|
this.updateMaps(entityplayer); // Paper - distance maps
|
|
+ this.playerChunkManager.updatePlayer(entityplayer); // Tuinity - respond to movement immediately
|
|
|
|
}
|
|
|
|
@@ -2167,7 +2080,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
// Paper start - per player view distance
|
|
// there can be potential desync with player's last mapped section and the view distance map, so use the
|
|
// view distance map here.
|
|
- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> inRange = this.playerViewDistanceBroadcastMap.getObjectsInRange(chunkcoordintpair);
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> inRange = this.playerChunkManager.broadcastMap.getObjectsInRange(chunkcoordintpair); // Tuinity - replace player chunk loader system
|
|
|
|
if (inRange == null) {
|
|
return Stream.empty();
|
|
@@ -2183,8 +2096,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
continue;
|
|
}
|
|
EntityPlayer player = (EntityPlayer)temp;
|
|
- int viewDistance = this.playerViewDistanceBroadcastMap.getLastViewDistance(player);
|
|
- long lastPosition = this.playerViewDistanceBroadcastMap.getLastCoordinate(player);
|
|
+ if (!this.playerChunkManager.isChunkSent(player, chunkcoordintpair.x, chunkcoordintpair.z)) continue; // Tuinity - replace player chunk management
|
|
+ int viewDistance = this.playerChunkManager.broadcastMap.getLastViewDistance(player); // Tuinity - replace player chunk loader system
|
|
+ long lastPosition = this.playerChunkManager.broadcastMap.getLastCoordinate(player); // Tuinity - replace player chunk loader system
|
|
|
|
int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - chunkcoordintpair.x);
|
|
int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - chunkcoordintpair.z);
|
|
@@ -2199,6 +2113,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
continue;
|
|
}
|
|
EntityPlayer player = (EntityPlayer)temp;
|
|
+ if (!this.playerChunkManager.isChunkSent(player, chunkcoordintpair.x, chunkcoordintpair.z)) continue; // Tuinity - replace player chunk management
|
|
players.add(player);
|
|
}
|
|
}
|
|
@@ -2417,6 +2332,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
|
|
apacket[1] = new PacketPlayOutLightUpdate(chunk.getPos(), this.lightEngine, true);
|
|
|
|
// Paper start - Fix MC-162253
|
|
+ if (com.tuinity.tuinity.config.TuinityConfig.enableMC162253Workaround) { // Tuinity - hide behind config option, this logic has undesirable network effects
|
|
final int lightMask = getLightMask(chunk);
|
|
int i = 1;
|
|
for (int x = -1; x <= 1; x++) {
|
|
@@ -2441,10 +2357,12 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
|
|
apacket[i] = new PacketPlayOutLightUpdate(new ChunkCoordIntPair(chunk.getPos().x + x, chunk.getPos().z + z), lightEngine, updateLightMask, 0, true);
|
|
}
|
|
}
|
|
+ } // Tuinity - hide behind config option, this logic has undesirable network effects
|
|
}
|
|
|
|
- final int viewDistance = playerViewDistanceBroadcastMap.getLastViewDistance(entityplayer);
|
|
- final long lastPosition = playerViewDistanceBroadcastMap.getLastCoordinate(entityplayer);
|
|
+ if (com.tuinity.tuinity.config.TuinityConfig.enableMC162253Workaround) { // Tuinity - hide behind config option, this logic has undesirable network effects
|
|
+ final int viewDistance = this.playerChunkManager.broadcastMap.getLastViewDistance(entityplayer); // Tuinity - replace player chunk loader
|
|
+ final long lastPosition = this.playerChunkManager.broadcastMap.getLastCoordinate(entityplayer); // Tuinity - replace player chunk loader
|
|
|
|
int j = 1;
|
|
for (int x = -1; x <= 1; x++) {
|
|
@@ -2469,6 +2387,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
|
|
entityplayer.playerConnection.sendPacket(packet);
|
|
}
|
|
}
|
|
+ } // Tuinity - hide behind config option, this logic has undesirable network effects
|
|
// Paper end - Fix MC-162253
|
|
|
|
entityplayer.a(chunk.getPos(), apacket[0], apacket[1]);
|
|
@@ -2645,7 +2564,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
|
|
double vec3d_dy = entityplayer.locY() - this.tracker.locY();
|
|
double vec3d_dz = entityplayer.locZ() - this.tracker.locZ();
|
|
// Paper end - remove allocation of Vec3D here
|
|
- int i = Math.min(this.b(), (PlayerChunkMap.this.viewDistance - 1) * 16);
|
|
+ int i = Math.min(this.b(), (entityplayer.getBukkitEntity().getViewDistance()) * 16); // Tuinity - per player view distance
|
|
boolean flag = vec3d_dx >= (double) (-i) && vec3d_dx <= (double) i && vec3d_dz >= (double) (-i) && vec3d_dz <= (double) i && this.tracker.a(entityplayer); // Paper - remove allocation of Vec3D here
|
|
|
|
if (flag) {
|
|
@@ -2655,7 +2574,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
|
|
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(this.tracker.chunkX, this.tracker.chunkZ);
|
|
PlayerChunk playerchunk = PlayerChunkMap.this.getVisibleChunk(chunkcoordintpair.pair());
|
|
|
|
- if (playerchunk != null && playerchunk.getSendingChunk() != null) { // Paper - no-tick view distance
|
|
+ if (playerchunk != null && playerchunk.getSendingChunk() != null && PlayerChunkMap.this.playerChunkManager.isChunkSent(entityplayer, MathHelper.floor(this.tracker.locX()) >> 4, MathHelper.floor(this.tracker.locZ()) >> 4)) { // Paper - no-tick view distance // Tuinity - don't broadcast in chunks the player hasn't received
|
|
flag1 = PlayerChunkMap.b(chunkcoordintpair, entityplayer, false) <= PlayerChunkMap.this.viewDistance;
|
|
}
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
index e9ff47b239372bd628097e6e7f43fa9bd5f4d73d..0b5cf23932c3c626d8805d4db97d2bbab63634cf 100644
|
|
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
@@ -270,7 +270,7 @@ public abstract class PlayerList {
|
|
boolean flag1 = gamerules.getBoolean(GameRules.REDUCED_DEBUG_INFO);
|
|
|
|
// Spigot - view distance
|
|
- playerconnection.sendPacket(new PacketPlayOutLogin(entityplayer.getId(), entityplayer.playerInteractManager.getGameMode(), entityplayer.playerInteractManager.c(), BiomeManager.a(worldserver1.getSeed()), worlddata.isHardcore(), this.server.F(), this.s, worldserver1.getDimensionManager(), worldserver1.getDimensionKey(), this.getMaxPlayers(), worldserver1.getChunkProvider().playerChunkMap.getLoadViewDistance(), flag1, !flag, worldserver1.isDebugWorld(), worldserver1.isFlatWorld())); // Paper - no-tick view distance
|
|
+ playerconnection.sendPacket(new PacketPlayOutLogin(entityplayer.getId(), entityplayer.playerInteractManager.getGameMode(), entityplayer.playerInteractManager.c(), BiomeManager.a(worldserver1.getSeed()), worlddata.isHardcore(), this.server.F(), this.s, worldserver1.getDimensionManager(), worldserver1.getDimensionKey(), this.getMaxPlayers(), worldserver1.getChunkProvider().playerChunkMap.playerChunkManager.getLoadDistance(), flag1, !flag, worldserver1.isDebugWorld(), worldserver1.isFlatWorld())); // Paper - no-tick view distance // Tuinity - replace old player chunk management
|
|
entityplayer.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
|
|
playerconnection.sendPacket(new PacketPlayOutCustomPayload(PacketPlayOutCustomPayload.a, (new PacketDataSerializer(Unpooled.buffer())).a(this.getServer().getServerModName())));
|
|
playerconnection.sendPacket(new PacketPlayOutServerDifficulty(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
|
|
@@ -950,7 +950,7 @@ public abstract class PlayerList {
|
|
// CraftBukkit start
|
|
WorldData worlddata = worldserver1.getWorldData();
|
|
entityplayer1.playerConnection.sendPacket(new PacketPlayOutRespawn(worldserver1.getDimensionManager(), worldserver1.getDimensionKey(), BiomeManager.a(worldserver1.getSeed()), entityplayer1.playerInteractManager.getGameMode(), entityplayer1.playerInteractManager.c(), worldserver1.isDebugWorld(), worldserver1.isFlatWorld(), flag));
|
|
- entityplayer1.playerConnection.sendPacket(new PacketPlayOutViewDistance(worldserver1.getChunkProvider().playerChunkMap.getLoadViewDistance())); // Spigot // Paper - no-tick view distance
|
|
+ entityplayer1.playerConnection.sendPacket(new PacketPlayOutViewDistance(worldserver1.getChunkProvider().playerChunkMap.playerChunkManager.getLoadDistance())); // Spigot // Paper - no-tick view distance // Tuinity - replace old player chunk management
|
|
entityplayer1.spawnIn(worldserver1);
|
|
entityplayer1.dead = false;
|
|
entityplayer1.playerConnection.teleport(new Location(worldserver1.getWorld(), entityplayer1.locX(), entityplayer1.locY(), entityplayer1.locZ(), entityplayer1.yaw, entityplayer1.pitch));
|
|
@@ -1219,7 +1219,7 @@ public abstract class PlayerList {
|
|
// Really shouldn't happen...
|
|
backingSet = world != null ? world.players.toArray() : players.toArray();
|
|
} else {
|
|
- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> nearbyPlayers = chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(MCUtil.fastFloor(d0) >> 4, MCUtil.fastFloor(d2) >> 4);
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> nearbyPlayers = chunkMap.playerChunkManager.broadcastMap.getObjectsInRange(MCUtil.fastFloor(d0) >> 4, MCUtil.fastFloor(d2) >> 4); // Tuinity - replace old player chunk management
|
|
if (nearbyPlayers == null) {
|
|
return;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/world/entity/EntityLightning.java b/src/main/java/net/minecraft/world/entity/EntityLightning.java
|
|
index 85f571a791bce63989890f277857bc7bdeec0cb5..9e4137768c7d8966759324a4b368330c35faa8a5 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/EntityLightning.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/EntityLightning.java
|
|
@@ -81,8 +81,9 @@ public class EntityLightning extends Entity {
|
|
// CraftBukkit start - Use relative location for far away sounds
|
|
// this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_LIGHTNING_BOLT_THUNDER, SoundCategory.WEATHER, 10000.0F, 0.8F + this.random.nextFloat() * 0.2F);
|
|
float pitch = 0.8F + this.random.nextFloat() * 0.2F;
|
|
- int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16;
|
|
+ //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Tuinity - apply view distance patch
|
|
for (EntityPlayer player : (List<EntityPlayer>) (List) this.world.getPlayers()) {
|
|
+ final int viewDistance = player.getBukkitEntity().getViewDistance(); // Tuinity - apply view distance patch
|
|
double deltaX = this.locX() - player.locX();
|
|
double deltaZ = this.locZ() - player.locZ();
|
|
double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
|
|
diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java
|
|
index c296fcf80c2f3f210fa020416973ec8d5db541ba..07160de8725787551df327c0790b2d6e0876524f 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java
|
|
@@ -625,9 +625,9 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster {
|
|
if (this.deathAnimationTicks == 1 && !this.isSilent()) {
|
|
// CraftBukkit start - Use relative location for far away sounds
|
|
// this.world.b(1028, this.getChunkCoordinates(), 0);
|
|
- int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API
|
|
+ //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API // Tuinity - apply view distance patch
|
|
for (net.minecraft.server.level.EntityPlayer player : (List<net.minecraft.server.level.EntityPlayer>) ((WorldServer)world).getPlayers()) {
|
|
- // final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch
|
|
+ final int viewDistance = player.getBukkitEntity().getViewDistance(); // Tuinity - apply view distance patch
|
|
// Paper end
|
|
double deltaX = this.locX() - player.locX();
|
|
double deltaZ = this.locZ() - player.locZ();
|
|
diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java b/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java
|
|
index 229eabe0510e6c3660236ed0fb3e80d41074642c..930fc752ec0a988b17bc556cd428161638a14f27 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/boss/wither/EntityWither.java
|
|
@@ -264,9 +264,9 @@ public class EntityWither extends EntityMonster implements IRangedEntity {
|
|
if (!this.isSilent()) {
|
|
// CraftBukkit start - Use relative location for far away sounds
|
|
// this.world.b(1023, new BlockPosition(this), 0);
|
|
- int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API
|
|
+ //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API // Tuinity - apply view distance patch
|
|
for (EntityPlayer player : (List<EntityPlayer>)this.world.getPlayers()) {
|
|
- // final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch
|
|
+ final int viewDistance = player.getBukkitEntity().getViewDistance(); // Tuinity - apply view distance patch
|
|
double deltaX = this.locX() - player.locX();
|
|
double deltaZ = this.locZ() - player.locZ();
|
|
double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
|
|
diff --git a/src/main/java/net/minecraft/world/item/ItemEnderEye.java b/src/main/java/net/minecraft/world/item/ItemEnderEye.java
|
|
index f74685a7cdb905af8e9712ca8597e7ed3dc8b120..feedbd8c97c1b1c56eaff359e6a940696d416906 100644
|
|
--- a/src/main/java/net/minecraft/world/item/ItemEnderEye.java
|
|
+++ b/src/main/java/net/minecraft/world/item/ItemEnderEye.java
|
|
@@ -60,9 +60,10 @@ public class ItemEnderEye extends Item {
|
|
|
|
// CraftBukkit start - Use relative location for far away sounds
|
|
// world.b(1038, blockposition1.b(1, 0, 1), 0);
|
|
- int viewDistance = world.getServer().getViewDistance() * 16;
|
|
+ //int viewDistance = world.getServer().getViewDistance() * 16; // Tuinity - apply view distance patch
|
|
BlockPosition soundPos = blockposition1.b(1, 0, 1);
|
|
for (EntityPlayer player : world.getServer().getServer().getPlayerList().players) {
|
|
+ final int viewDistance = player.getBukkitEntity().getViewDistance(); // Tuinity - apply view distance patch
|
|
double deltaX = soundPos.getX() - player.locX();
|
|
double deltaZ = soundPos.getZ() - player.locZ();
|
|
double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
|
|
diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java
|
|
index a802c45538943b570ca0daa3a40607dc80e696eb..af01f5d635eada7175b9d7fdb47a65530686a539 100644
|
|
--- a/src/main/java/net/minecraft/world/level/World.java
|
|
+++ b/src/main/java/net/minecraft/world/level/World.java
|
|
@@ -606,7 +606,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
this.notify(blockposition, iblockdata1, iblockdata, i);
|
|
// Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance
|
|
// if copied from above
|
|
- } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((WorldServer)this).getChunkProvider().playerChunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) {
|
|
+ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((WorldServer)this).getChunkProvider().playerChunkMap.playerChunkManager.broadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) { // Tuinity - replace old player chunk management
|
|
((WorldServer)this).getChunkProvider().flagDirty(blockposition);
|
|
// Paper end - per player view distance
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java
|
|
index d6b34c6abebeac8445da3e76f341066952182e2b..fc07e2014e961da5d97095c4ee6f972e2ece3ec3 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java
|
|
@@ -406,12 +406,12 @@ public class Chunk implements IChunkAccess {
|
|
PlayerChunkMap chunkMap = chunkProviderServer.playerChunkMap;
|
|
// this code handles the addition of ticking tickets - the distance map handles the removal
|
|
if (!areNeighboursLoaded(bitsetBefore, 2) && areNeighboursLoaded(bitsetAfter, 2)) {
|
|
- if (chunkMap.playerViewDistanceTickMap.getObjectsInRange(this.coordinateKey) != null) {
|
|
+ if (chunkMap.playerChunkManager.tickMap.getObjectsInRange(this.coordinateKey) != null) { // Tuinity - replace old player chunk loading system
|
|
// now we're ready for entity ticking
|
|
chunkProviderServer.serverThreadQueue.execute(() -> {
|
|
// double check that this condition still holds.
|
|
- if (Chunk.this.areNeighboursLoaded(2) && chunkMap.playerViewDistanceTickMap.getObjectsInRange(Chunk.this.coordinateKey) != null) {
|
|
- chunkProviderServer.addTicketAtLevel(TicketType.PLAYER, Chunk.this.loc, 31, Chunk.this.loc); // 31 -> entity ticking, TODO check on update
|
|
+ if (Chunk.this.areNeighboursLoaded(2) && chunkMap.playerChunkManager.tickMap.getObjectsInRange(Chunk.this.coordinateKey) != null) { // Tuinity - replace old player chunk loading system
|
|
+ chunkMap.playerChunkManager.onChunkPlayerTickReady(this.loc.x, this.loc.z); // Tuinity - replace old player chunk loading system
|
|
}
|
|
});
|
|
}
|
|
@@ -419,31 +419,18 @@ public class Chunk implements IChunkAccess {
|
|
|
|
// this code handles the chunk sending
|
|
if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) {
|
|
- if (chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(this.coordinateKey) != null) {
|
|
- // now we're ready to send
|
|
- chunkMap.mailboxMain.a(ChunkTaskQueueSorter.a(chunkMap.getUpdatingChunk(this.coordinateKey), (() -> { // Copied frm PlayerChunkMap
|
|
- // double check that this condition still holds.
|
|
- if (!Chunk.this.areNeighboursLoaded(1)) {
|
|
- return;
|
|
- }
|
|
- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> inRange = chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(Chunk.this.coordinateKey);
|
|
- if (inRange == null) {
|
|
- return;
|
|
- }
|
|
-
|
|
- // broadcast
|
|
- Object[] backingSet = inRange.getBackingSet();
|
|
- Packet[] chunkPackets = new Packet[10];
|
|
- for (int index = 0, len = backingSet.length; index < len; ++index) {
|
|
- Object temp = backingSet[index];
|
|
- if (!(temp instanceof EntityPlayer)) {
|
|
- continue;
|
|
- }
|
|
- EntityPlayer player = (EntityPlayer)temp;
|
|
- chunkMap.sendChunk(player, chunkPackets, Chunk.this);
|
|
- }
|
|
- })));
|
|
- }
|
|
+ // Tuinity start - replace old player chunk loading system
|
|
+ chunkProviderServer.serverThreadQueue.execute(() -> {
|
|
+ if (!Chunk.this.areNeighboursLoaded(1)) {
|
|
+ return;
|
|
+ }
|
|
+ Chunk.this.A();
|
|
+ if (!Chunk.this.areNeighboursLoaded(1)) {
|
|
+ return;
|
|
+ }
|
|
+ chunkMap.playerChunkManager.onChunkSendReady(this.loc.x, this.loc.z);
|
|
+ });
|
|
+ // Tuinity end - replace old player chunk loading system
|
|
}
|
|
// Paper end - no-tick view distance
|
|
}
|
|
@@ -1027,6 +1014,7 @@ public class Chunk implements IChunkAccess {
|
|
// Paper end - neighbour cache
|
|
org.bukkit.Server server = this.world.getServer();
|
|
((WorldServer)this.world).getChunkProvider().addLoadedChunk(this); // Paper
|
|
+ ((WorldServer)this.world).getChunkProvider().playerChunkMap.playerChunkManager.onChunkLoad(this.loc.x, this.loc.z); // Tuinity - rewrite player chunk management
|
|
if (server != null) {
|
|
/*
|
|
* If it's a new world, the first few chunks are generated inside
|
|
diff --git a/src/main/java/net/minecraft/world/phys/Vec3D.java b/src/main/java/net/minecraft/world/phys/Vec3D.java
|
|
index 3fe1508b091e1fd0325eae50138d02fa6445c9ff..a19a26a88f247d359354902efeece9923f3e0e0b 100644
|
|
--- a/src/main/java/net/minecraft/world/phys/Vec3D.java
|
|
+++ b/src/main/java/net/minecraft/world/phys/Vec3D.java
|
|
@@ -14,6 +14,12 @@ public class Vec3D implements IPosition {
|
|
public final double y;
|
|
public final double z;
|
|
|
|
+ // Tuinity start
|
|
+ public final double magnitudeXZSquared() {
|
|
+ return (this.x * this.x) + (this.z * this.z);
|
|
+ }
|
|
+ // Tuinity end
|
|
+
|
|
public static Vec3D a(BaseBlockPosition baseblockposition) {
|
|
return new Vec3D((double) baseblockposition.getX() + 0.5D, (double) baseblockposition.getY() + 0.5D, (double) baseblockposition.getZ() + 0.5D);
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
index 0128a24f1de7468ff60ba2de0ed825ff62363f6b..09cef73a92679e47c8670e4e4c1ba988878aee24 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -2631,14 +2631,14 @@ public class CraftWorld implements World {
|
|
throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
|
|
}
|
|
PlayerChunkMap chunkMap = getHandle().getChunkProvider().playerChunkMap;
|
|
- if (viewDistance != chunkMap.getEffectiveViewDistance()) {
|
|
+ if (true) { // Tuinity - replace old player chunk management
|
|
chunkMap.setViewDistance(viewDistance);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getNoTickViewDistance() {
|
|
- return getHandle().getChunkProvider().playerChunkMap.getEffectiveNoTickViewDistance();
|
|
+ return getHandle().getChunkProvider().playerChunkMap.playerChunkManager.getTargetNoTickViewDistance(); // Tuinity - replace old player chunk management
|
|
}
|
|
|
|
@Override
|
|
@@ -2647,11 +2647,22 @@ public class CraftWorld implements World {
|
|
throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
|
|
}
|
|
PlayerChunkMap chunkMap = getHandle().getChunkProvider().playerChunkMap;
|
|
- if (viewDistance != chunkMap.getRawNoTickViewDistance()) {
|
|
+ if (true) { // Tuinity - replace old player chunk management
|
|
chunkMap.setNoTickViewDistance(viewDistance);
|
|
}
|
|
}
|
|
// Paper end - per player view distance
|
|
+ // Tuinity start - add view distances
|
|
+ @Override
|
|
+ public int getSendViewDistance() {
|
|
+ return getHandle().getChunkProvider().playerChunkMap.playerChunkManager.getTargetSendDistance();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setSendViewDistance(int viewDistance) {
|
|
+ getHandle().getChunkProvider().playerChunkMap.playerChunkManager.setTargetSendDistance(viewDistance);
|
|
+ }
|
|
+ // Tuinity end - add view distances
|
|
|
|
// Spigot start
|
|
private final org.bukkit.World.Spigot spigot = new org.bukkit.World.Spigot()
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
index e5549439b3d4d608cf37dd33b6c8c9e10dfe9328..45e786565ac988abadffda2e7ba3ff1e2880b786 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
@@ -2257,15 +2257,70 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
|
}
|
|
}
|
|
|
|
+ // Tuinity start - implement view distances
|
|
+ @Override
|
|
+ public int getSendViewDistance() {
|
|
+ net.minecraft.server.level.PlayerChunkMap chunkMap = this.getHandle().getWorldServer().getChunkProvider().playerChunkMap;
|
|
+ com.tuinity.tuinity.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
|
|
+ if (data == null) {
|
|
+ return chunkMap.playerChunkManager.getTargetSendDistance();
|
|
+ }
|
|
+ return data.getTargetSendViewDistance();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setSendViewDistance(int viewDistance) {
|
|
+ net.minecraft.server.level.PlayerChunkMap chunkMap = this.getHandle().getWorldServer().getChunkProvider().playerChunkMap;
|
|
+ com.tuinity.tuinity.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
|
|
+ if (data == null) {
|
|
+ throw new IllegalStateException("Player is not attached to world");
|
|
+ }
|
|
+
|
|
+ data.setTargetSendViewDistance(viewDistance);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int getNoTickViewDistance() {
|
|
+ net.minecraft.server.level.PlayerChunkMap chunkMap = this.getHandle().getWorldServer().getChunkProvider().playerChunkMap;
|
|
+ com.tuinity.tuinity.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
|
|
+ if (data == null) {
|
|
+ return chunkMap.playerChunkManager.getTargetNoTickViewDistance();
|
|
+ }
|
|
+ return data.getTargetNoTickViewDistance();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setNoTickViewDistance(int viewDistance) {
|
|
+ net.minecraft.server.level.PlayerChunkMap chunkMap = this.getHandle().getWorldServer().getChunkProvider().playerChunkMap;
|
|
+ com.tuinity.tuinity.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
|
|
+ if (data == null) {
|
|
+ throw new IllegalStateException("Player is not attached to world");
|
|
+ }
|
|
+
|
|
+ data.setTargetNoTickViewDistance(viewDistance);
|
|
+ }
|
|
+
|
|
@Override
|
|
public int getViewDistance() {
|
|
- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
|
+ net.minecraft.server.level.PlayerChunkMap chunkMap = this.getHandle().getWorldServer().getChunkProvider().playerChunkMap;
|
|
+ com.tuinity.tuinity.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
|
|
+ if (data == null) {
|
|
+ return chunkMap.playerChunkManager.getTargetViewDistance();
|
|
+ }
|
|
+ return data.getTargetTickViewDistance();
|
|
}
|
|
|
|
@Override
|
|
public void setViewDistance(int viewDistance) {
|
|
- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
|
+ net.minecraft.server.level.PlayerChunkMap chunkMap = this.getHandle().getWorldServer().getChunkProvider().playerChunkMap;
|
|
+ com.tuinity.tuinity.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
|
|
+ if (data == null) {
|
|
+ throw new IllegalStateException("Player is not attached to world");
|
|
+ }
|
|
+
|
|
+ data.setTargetTickViewDistance(viewDistance);
|
|
}
|
|
+ // Tuinity end - implement view distances
|
|
|
|
@Override
|
|
public <T> T getClientOption(ClientOption<T> type) {
|