Make PlayerChunkMap threadsafe
This commit is contained in:
@@ -214,6 +214,8 @@ public abstract class MixinMinecraftServer {
|
|||||||
WorldServer world = worlds.get(i < worlds.size() ? i : 0);
|
WorldServer world = worlds.get(i < worlds.size() ? i : 0);
|
||||||
synchronized (((IMixinLockProvider) world).lock()) {
|
synchronized (((IMixinLockProvider) world).lock()) {
|
||||||
tickEntities(world);
|
tickEntities(world);
|
||||||
|
world.getTracker().updatePlayers();
|
||||||
|
world.explosionDensityCache.clear(); // Paper - Optimize explosions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, null);
|
}, null);
|
||||||
@@ -222,7 +224,6 @@ public abstract class MixinMinecraftServer {
|
|||||||
WorldServer world = worlds.get(i);
|
WorldServer world = worlds.get(i);
|
||||||
synchronized (((IMixinLockProvider) world).lock()) {
|
synchronized (((IMixinLockProvider) world).lock()) {
|
||||||
tickWorld(world);
|
tickWorld(world);
|
||||||
world.explosionDensityCache.clear(); // Paper - Optimize explosions
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,11 +244,6 @@ public abstract class MixinMinecraftServer {
|
|||||||
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();
|
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();
|
||||||
Akari.callbackTiming.stopTiming();
|
Akari.callbackTiming.stopTiming();
|
||||||
|
|
||||||
for (int i = 0; i < worlds.size(); ++i) {
|
|
||||||
WorldServer world = worlds.get(i);
|
|
||||||
tickUnsafeSync(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
MinecraftTimings.connectionTimer.startTiming();
|
MinecraftTimings.connectionTimer.startTiming();
|
||||||
serverConnection().c();
|
serverConnection().c();
|
||||||
MinecraftTimings.connectionTimer.stopTiming();
|
MinecraftTimings.connectionTimer.stopTiming();
|
||||||
@@ -266,12 +262,4 @@ public abstract class MixinMinecraftServer {
|
|||||||
}
|
}
|
||||||
MinecraftTimings.tickablesTimer.stopTiming();
|
MinecraftTimings.tickablesTimer.stopTiming();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tickUnsafeSync(WorldServer world) {
|
|
||||||
world.timings.doChunkMap.startTiming();
|
|
||||||
world.manager.flush();
|
|
||||||
world.timings.doChunkMap.stopTiming();
|
|
||||||
|
|
||||||
world.getTracker().updatePlayers();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
package io.akarin.server.mixin.core;
|
package io.akarin.server.mixin.core;
|
||||||
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
|
||||||
|
|
||||||
import io.akarin.api.internal.mixin.IMixinLockProvider;
|
import io.akarin.api.internal.mixin.IMixinLockProvider;
|
||||||
import net.minecraft.server.WorldServer;
|
import net.minecraft.server.WorldServer;
|
||||||
|
|
||||||
@Mixin(value = WorldServer.class, remap = false)
|
@Mixin(value = WorldServer.class, remap = false)
|
||||||
public abstract class MixinWorldServer implements IMixinLockProvider {
|
public abstract class MixinWorldServer implements IMixinLockProvider {
|
||||||
@Redirect(method = "doTick()V", at = @At(
|
|
||||||
value = "INVOKE",
|
|
||||||
target = "net/minecraft/server/PlayerChunkMap.flush()V"
|
|
||||||
))
|
|
||||||
public void onFlush() {} // Migrated to main thread
|
|
||||||
|
|
||||||
private final Object tickLock = new Object();
|
private final Object tickLock = new Object();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ public abstract class MixinChunk implements IMixinChunk {
|
|||||||
|
|
||||||
this.ticked = true;
|
this.ticked = true;
|
||||||
|
|
||||||
if ((true || !this.isLightPopulated) && this.isTerrainPopulated && !neighbors.isEmpty()) {
|
if (!this.isLightPopulated && this.isTerrainPopulated && !neighbors.isEmpty()) {
|
||||||
lightExecutorService.execute(() -> {
|
lightExecutorService.execute(() -> {
|
||||||
this.checkLightAsync(neighbors);
|
this.checkLightAsync(neighbors);
|
||||||
});
|
});
|
||||||
@@ -170,47 +170,12 @@ public abstract class MixinChunk implements IMixinChunk {
|
|||||||
return this.checkWorldLightFor(enumSkyBlock, pos);
|
return this.checkWorldLightFor(enumSkyBlock, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "h(Z)V", at = @At("HEAD"), cancellable = true)
|
|
||||||
private void onRecheckGaps(CallbackInfo ci) {
|
|
||||||
if (this.world.getMinecraftServer().isStopped() || lightExecutorService.isShutdown()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isUnloading()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final List<Chunk> neighborChunks = this.getSurroundingChunks();
|
|
||||||
if (neighborChunks.isEmpty()) {
|
|
||||||
this.isGapLightingUpdated = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Akari.isPrimaryThread()) {
|
|
||||||
try {
|
|
||||||
lightExecutorService.execute(() -> {
|
|
||||||
this.recheckGapsAsync(neighborChunks);
|
|
||||||
});
|
|
||||||
} catch (RejectedExecutionException ex) {
|
|
||||||
// This could happen if ServerHangWatchdog kills the server
|
|
||||||
// between the start of the method and the execute() call.
|
|
||||||
if (!this.world.getMinecraftServer().isStopped() && !lightExecutorService.isShutdown()) {
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.recheckGapsAsync(neighborChunks);
|
|
||||||
}
|
|
||||||
ci.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rechecks chunk gaps async.
|
* Rechecks chunk gaps async.
|
||||||
*
|
*
|
||||||
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
* @param neighbors A thread-safe list of surrounding neighbor chunks
|
||||||
*/
|
*/
|
||||||
private void recheckGapsAsync(List<Chunk> neighbors) {
|
private void recheckGapsAsync(List<Chunk> neighbors) {
|
||||||
this.isLightPopulated = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < 16; ++i) {
|
for (int i = 0; i < 16; ++i) {
|
||||||
for (int j = 0; j < 16; ++j) {
|
for (int j = 0; j < 16; ++j) {
|
||||||
if (this.updateSkylightColumns[i + j * 16]) {
|
if (this.updateSkylightColumns[i + j * 16]) {
|
||||||
@@ -236,7 +201,7 @@ public abstract class MixinChunk implements IMixinChunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isGapLightingUpdated = false;
|
// this.isGapLightingUpdated = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,7 +293,11 @@ public abstract class MixinChunk implements IMixinChunk {
|
|||||||
chunk.a(enumfacing.opposite()); // OBFHELPER: checkLightSide
|
chunk.a(enumfacing.opposite()); // OBFHELPER: checkLightSide
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setSkylightUpdated();
|
for (int i = 0; i < this.updateSkylightColumns.length; ++i) {
|
||||||
|
this.updateSkylightColumns[i] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.recheckGapsAsync(neighbors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ public abstract class MixinWorldServer extends MixinWorld implements IMixinWorld
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentChunk == null) {
|
if (currentChunk == null) {
|
||||||
|
if (!Akari.isPrimaryThread()) return false;
|
||||||
currentChunk = MCUtil.getLoadedChunkWithoutMarkingActive(chunkProvider, pos.getX() >> 4, pos.getZ() >> 4);
|
currentChunk = MCUtil.getLoadedChunkWithoutMarkingActive(chunkProvider, pos.getX() >> 4, pos.getZ() >> 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
605
sources/src/main/java/net/minecraft/server/PlayerChunkMap.java
Normal file
605
sources/src/main/java/net/minecraft/server/PlayerChunkMap.java
Normal file
@@ -0,0 +1,605 @@
|
|||||||
|
package net.minecraft.server;
|
||||||
|
|
||||||
|
import co.aikar.timings.Timing;
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.AbstractIterator;
|
||||||
|
import com.google.common.collect.ComparisonChain;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
// CraftBukkit start
|
||||||
|
import java.util.LinkedList;
|
||||||
|
// CraftBukkit end
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Akarin Changes Note
|
||||||
|
* 1) Make whole class thread-safe (safety issue)
|
||||||
|
*/
|
||||||
|
@ThreadSafe // Akarin - idk why we need do so!!
|
||||||
|
public class PlayerChunkMap {
|
||||||
|
|
||||||
|
private static final Predicate<EntityPlayer> a = new Predicate() {
|
||||||
|
public boolean a(@Nullable EntityPlayer entityplayer) {
|
||||||
|
return entityplayer != null && !entityplayer.isSpectator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean apply(@Nullable Object object) {
|
||||||
|
return this.a((EntityPlayer) object);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final Predicate<EntityPlayer> b = new Predicate() {
|
||||||
|
public boolean a(@Nullable EntityPlayer entityplayer) {
|
||||||
|
return entityplayer != null && (!entityplayer.isSpectator() || entityplayer.x().getGameRules().getBoolean("spectatorsGenerateChunks"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean apply(@Nullable Object object) {
|
||||||
|
return this.a((EntityPlayer) object);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private final WorldServer world;
|
||||||
|
private final List<EntityPlayer> managedPlayers = Lists.newArrayList();
|
||||||
|
private final ReentrantReadWriteLock managedPlayersLock = new ReentrantReadWriteLock(); // Akarin - add lock
|
||||||
|
private final Long2ObjectMap<PlayerChunk> e = new Long2ObjectOpenHashMap(4096);
|
||||||
|
private final Set<PlayerChunk> f = Sets.newHashSet();
|
||||||
|
private final List<PlayerChunk> g = Lists.newLinkedList();
|
||||||
|
private final List<PlayerChunk> h = Lists.newLinkedList();
|
||||||
|
private final List<PlayerChunk> i = Lists.newArrayList();
|
||||||
|
private AtomicInteger j = new AtomicInteger(); public int getViewDistance() { return j.get(); } // Paper OBFHELPER // Akarin - atmoic
|
||||||
|
private AtomicLong k = new AtomicLong(); // Akarin - atmoic
|
||||||
|
private AtomicBoolean l = new AtomicBoolean(true); // Akarin - atmoic
|
||||||
|
private AtomicBoolean m = new AtomicBoolean(true); // Akarin - atmoic
|
||||||
|
private boolean wasNotEmpty; // CraftBukkit - add field
|
||||||
|
|
||||||
|
public PlayerChunkMap(WorldServer worldserver) {
|
||||||
|
this.world = worldserver;
|
||||||
|
this.a(worldserver.spigotConfig.viewDistance); // Spigot
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldServer getWorld() {
|
||||||
|
return this.world;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Iterator<Chunk> b() { // Akarin - synchronized
|
||||||
|
final Iterator iterator = this.i.iterator();
|
||||||
|
|
||||||
|
return new AbstractIterator() {
|
||||||
|
protected Chunk a() {
|
||||||
|
while (true) {
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
PlayerChunk playerchunk = (PlayerChunk) iterator.next();
|
||||||
|
Chunk chunk = playerchunk.f();
|
||||||
|
|
||||||
|
if (chunk == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chunk.v() && chunk.isDone()) {
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chunk.j()) {
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playerchunk.a(128.0D, PlayerChunkMap.a)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Chunk) this.endOfData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object computeNext() {
|
||||||
|
return this.a();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void flush() { // Akarin - synchronized
|
||||||
|
long i = this.world.getTime();
|
||||||
|
int j;
|
||||||
|
PlayerChunk playerchunk;
|
||||||
|
|
||||||
|
if (i - this.k.get() > 8000L) { // Akarin - atmoic
|
||||||
|
try (Timing ignored = world.timings.doChunkMapUpdate.startTiming()) { // Paper
|
||||||
|
this.k.getAndSet(i); // Akarin - atmoic
|
||||||
|
|
||||||
|
for (j = 0; j < this.i.size(); ++j) {
|
||||||
|
playerchunk = (PlayerChunk) this.i.get(j);
|
||||||
|
playerchunk.d();
|
||||||
|
playerchunk.c();
|
||||||
|
}
|
||||||
|
} // Paper timing
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.f.isEmpty()) {
|
||||||
|
try (Timing ignored = world.timings.doChunkMapToUpdate.startTiming()) { // Paper
|
||||||
|
Iterator iterator = this.f.iterator();
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
playerchunk = (PlayerChunk) iterator.next();
|
||||||
|
playerchunk.d();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.f.clear();
|
||||||
|
} // Paper timing
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.l.get() && i % 4L == 0L) {
|
||||||
|
this.l.getAndSet(false);
|
||||||
|
try (Timing ignored = world.timings.doChunkMapSortMissing.startTiming()) { // Paper
|
||||||
|
Collections.sort(this.h, new Comparator() {
|
||||||
|
public int a(PlayerChunk playerchunk, PlayerChunk playerchunk1) {
|
||||||
|
return ComparisonChain.start().compare(playerchunk.g(), playerchunk1.g()).result();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compare(Object object, Object object1) {
|
||||||
|
return this.a((PlayerChunk) object, (PlayerChunk) object1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} // Paper timing
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.m.get() && i % 4L == 2L) {
|
||||||
|
this.m.getAndSet(false);
|
||||||
|
try (Timing ignored = world.timings.doChunkMapSortSendToPlayers.startTiming()) { // Paper
|
||||||
|
Collections.sort(this.g, new Comparator() {
|
||||||
|
public int a(PlayerChunk playerchunk, PlayerChunk playerchunk1) {
|
||||||
|
return ComparisonChain.start().compare(playerchunk.g(), playerchunk1.g()).result();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compare(Object object, Object object1) {
|
||||||
|
return this.a((PlayerChunk) object, (PlayerChunk) object1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} // Paper timing
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.h.isEmpty()) {
|
||||||
|
try (Timing ignored = world.timings.doChunkMapPlayersNeedingChunks.startTiming()) { // Paper
|
||||||
|
// Spigot start
|
||||||
|
org.spigotmc.SlackActivityAccountant activityAccountant = this.world.getMinecraftServer().slackActivityAccountant;
|
||||||
|
activityAccountant.startActivity(0.5);
|
||||||
|
int chunkGensAllowed = world.paperConfig.maxChunkGensPerTick; // Paper
|
||||||
|
// Spigot end
|
||||||
|
|
||||||
|
Iterator iterator1 = this.h.iterator();
|
||||||
|
|
||||||
|
while (iterator1.hasNext()) {
|
||||||
|
PlayerChunk playerchunk1 = (PlayerChunk) iterator1.next();
|
||||||
|
|
||||||
|
if (playerchunk1.f() == null) {
|
||||||
|
boolean flag = playerchunk1.a(PlayerChunkMap.b);
|
||||||
|
// Paper start
|
||||||
|
if (flag && !playerchunk1.chunkExists && chunkGensAllowed-- <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Paper end
|
||||||
|
|
||||||
|
if (playerchunk1.a(flag)) {
|
||||||
|
iterator1.remove();
|
||||||
|
if (playerchunk1.b()) {
|
||||||
|
this.g.remove(playerchunk1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activityAccountant.activityTimeIsExhausted()) { // Spigot
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// CraftBukkit start - SPIGOT-2891: remove once chunk has been provided
|
||||||
|
} else {
|
||||||
|
iterator1.remove();
|
||||||
|
}
|
||||||
|
// CraftBukkit end
|
||||||
|
}
|
||||||
|
|
||||||
|
activityAccountant.endActivity(); // Spigot
|
||||||
|
} // Paper timing
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.g.isEmpty()) {
|
||||||
|
j = world.paperConfig.maxChunkSendsPerTick; // Paper
|
||||||
|
try (Timing ignored = world.timings.doChunkMapPendingSendToPlayers.startTiming()) { // Paper
|
||||||
|
Iterator iterator2 = this.g.iterator();
|
||||||
|
|
||||||
|
while (iterator2.hasNext()) {
|
||||||
|
PlayerChunk playerchunk2 = (PlayerChunk) iterator2.next();
|
||||||
|
|
||||||
|
if (playerchunk2.b()) {
|
||||||
|
iterator2.remove();
|
||||||
|
--j;
|
||||||
|
if (j < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // Paper timing
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean unlockRequired = true; // Akarin
|
||||||
|
managedPlayersLock.readLock().lock(); // Akarin
|
||||||
|
if (this.managedPlayers.isEmpty()) {
|
||||||
|
managedPlayersLock.readLock().unlock(); // Akarin
|
||||||
|
unlockRequired = false; // Akarin
|
||||||
|
try (Timing ignored = world.timings.doChunkMapUnloadChunks.startTiming()) { // Paper
|
||||||
|
WorldProvider worldprovider = this.world.worldProvider;
|
||||||
|
|
||||||
|
if (!worldprovider.e() && !this.world.savingDisabled) { // Paper - respect saving disabled setting
|
||||||
|
this.world.getChunkProviderServer().b();
|
||||||
|
}
|
||||||
|
} // Paper timing
|
||||||
|
}
|
||||||
|
if (unlockRequired) managedPlayersLock.readLock().unlock(); // Akarin
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean a(int i, int j) { // Akarin - synchronized
|
||||||
|
long k = d(i, j);
|
||||||
|
|
||||||
|
return this.e.get(k) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public synchronized PlayerChunk getChunk(int i, int j) { // Akarin - synchronized
|
||||||
|
return (PlayerChunk) this.e.get(d(i, j));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayerChunk c(int i, int j) {
|
||||||
|
long k = d(i, j);
|
||||||
|
PlayerChunk playerchunk = (PlayerChunk) this.e.get(k);
|
||||||
|
|
||||||
|
if (playerchunk == null) {
|
||||||
|
playerchunk = new PlayerChunk(this, i, j);
|
||||||
|
this.e.put(k, playerchunk);
|
||||||
|
this.i.add(playerchunk);
|
||||||
|
if (playerchunk.f() == null) {
|
||||||
|
this.h.add(playerchunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playerchunk.b()) {
|
||||||
|
this.g.add(playerchunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return playerchunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CraftBukkit start - add method
|
||||||
|
public final boolean isChunkInUse(int x, int z) {
|
||||||
|
PlayerChunk pi = getChunk(x, z);
|
||||||
|
if (pi != null) {
|
||||||
|
return (pi.c.size() > 0);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// CraftBukkit end
|
||||||
|
|
||||||
|
public void flagDirty(BlockPosition blockposition) {
|
||||||
|
int i = blockposition.getX() >> 4;
|
||||||
|
int j = blockposition.getZ() >> 4;
|
||||||
|
PlayerChunk playerchunk = this.getChunk(i, j);
|
||||||
|
|
||||||
|
if (playerchunk != null) {
|
||||||
|
playerchunk.a(blockposition.getX() & 15, blockposition.getY(), blockposition.getZ() & 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPlayer(EntityPlayer entityplayer) {
|
||||||
|
int i = (int) entityplayer.locX >> 4;
|
||||||
|
int j = (int) entityplayer.locZ >> 4;
|
||||||
|
|
||||||
|
entityplayer.d = entityplayer.locX;
|
||||||
|
entityplayer.e = entityplayer.locZ;
|
||||||
|
|
||||||
|
|
||||||
|
// CraftBukkit start - Load nearby chunks first
|
||||||
|
List<ChunkCoordIntPair> chunkList = new LinkedList<ChunkCoordIntPair>();
|
||||||
|
|
||||||
|
// Paper start - Player view distance API
|
||||||
|
int viewDistance = entityplayer.getViewDistance();
|
||||||
|
for (int k = i - viewDistance; k <= i + viewDistance; ++k) {
|
||||||
|
for (int l = j - viewDistance; l <= j + viewDistance; ++l) {
|
||||||
|
// Paper end
|
||||||
|
chunkList.add(new ChunkCoordIntPair(k, l));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(chunkList, new ChunkCoordComparator(entityplayer));
|
||||||
|
synchronized (this) { // Akarin - synchronized
|
||||||
|
for (ChunkCoordIntPair pair : chunkList) {
|
||||||
|
this.c(pair.x, pair.z).a(entityplayer);
|
||||||
|
}
|
||||||
|
} // Akarin
|
||||||
|
// CraftBukkit end
|
||||||
|
|
||||||
|
managedPlayersLock.writeLock().lock(); // Akarin
|
||||||
|
this.managedPlayers.add(entityplayer);
|
||||||
|
managedPlayersLock.writeLock().unlock(); // Akarin
|
||||||
|
this.e();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removePlayer(EntityPlayer entityplayer) {
|
||||||
|
int i = (int) entityplayer.d >> 4;
|
||||||
|
int j = (int) entityplayer.e >> 4;
|
||||||
|
|
||||||
|
// Paper start - Player view distance API
|
||||||
|
int viewDistance = entityplayer.getViewDistance();
|
||||||
|
for (int k = i - viewDistance; k <= i + viewDistance; ++k) {
|
||||||
|
for (int l = j - viewDistance; l <= j + viewDistance; ++l) {
|
||||||
|
// Paper end
|
||||||
|
PlayerChunk playerchunk = this.getChunk(k, l);
|
||||||
|
|
||||||
|
if (playerchunk != null) {
|
||||||
|
playerchunk.b(entityplayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
managedPlayersLock.writeLock().lock(); // Akarin
|
||||||
|
this.managedPlayers.remove(entityplayer);
|
||||||
|
managedPlayersLock.writeLock().unlock(); // Akarin
|
||||||
|
this.e();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean a(int i, int j, int k, int l, int i1) {
|
||||||
|
int j1 = i - k;
|
||||||
|
int k1 = j - l;
|
||||||
|
|
||||||
|
return j1 >= -i1 && j1 <= i1 ? k1 >= -i1 && k1 <= i1 : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void movePlayer(EntityPlayer entityplayer) {
|
||||||
|
int i = (int) entityplayer.locX >> 4;
|
||||||
|
int j = (int) entityplayer.locZ >> 4;
|
||||||
|
double d0 = entityplayer.d - entityplayer.locX;
|
||||||
|
double d1 = entityplayer.e - entityplayer.locZ;
|
||||||
|
double d2 = d0 * d0 + d1 * d1;
|
||||||
|
|
||||||
|
if (d2 >= 64.0D) {
|
||||||
|
int k = (int) entityplayer.d >> 4;
|
||||||
|
int l = (int) entityplayer.e >> 4;
|
||||||
|
final int viewDistance = entityplayer.getViewDistance(); // Paper - Player view distance API
|
||||||
|
int i1 = Math.max(getViewDistance(), viewDistance); // Paper - Player view distance API
|
||||||
|
|
||||||
|
int j1 = i - k;
|
||||||
|
int k1 = j - l;
|
||||||
|
|
||||||
|
List<ChunkCoordIntPair> chunksToLoad = new LinkedList<ChunkCoordIntPair>(); // CraftBukkit
|
||||||
|
|
||||||
|
if (j1 != 0 || k1 != 0) {
|
||||||
|
for (int l1 = i - i1; l1 <= i + i1; ++l1) {
|
||||||
|
for (int i2 = j - i1; i2 <= j + i1; ++i2) {
|
||||||
|
if (!this.a(l1, i2, k, l, viewDistance)) { // Paper - Player view distance API
|
||||||
|
// this.c(l1, i2).a(entityplayer);
|
||||||
|
chunksToLoad.add(new ChunkCoordIntPair(l1, i2)); // CraftBukkit
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.a(l1 - j1, i2 - k1, i, j, i1)) {
|
||||||
|
PlayerChunk playerchunk = this.getChunk(l1 - j1, i2 - k1);
|
||||||
|
|
||||||
|
if (playerchunk != null) {
|
||||||
|
playerchunk.b(entityplayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entityplayer.d = entityplayer.locX;
|
||||||
|
entityplayer.e = entityplayer.locZ;
|
||||||
|
this.e();
|
||||||
|
|
||||||
|
// CraftBukkit start - send nearest chunks first
|
||||||
|
Collections.sort(chunksToLoad, new ChunkCoordComparator(entityplayer));
|
||||||
|
synchronized (this) { // Akarin - synchronized
|
||||||
|
for (ChunkCoordIntPair pair : chunksToLoad) {
|
||||||
|
this.c(pair.x, pair.z).a(entityplayer);
|
||||||
|
}
|
||||||
|
} // Akarin
|
||||||
|
// CraftBukkit end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean a(EntityPlayer entityplayer, int i, int j) {
|
||||||
|
PlayerChunk playerchunk = this.getChunk(i, j);
|
||||||
|
|
||||||
|
return playerchunk != null && playerchunk.d(entityplayer) && playerchunk.e();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setViewDistanceForAll(int viewDistance) { this.a(viewDistance); } // Paper - OBFHELPER
|
||||||
|
// Paper start - Separate into two methods
|
||||||
|
public void a(int i) {
|
||||||
|
i = MathHelper.clamp(i, 3, 32);
|
||||||
|
if (i != this.j.get()) { // Akarin - atmoic
|
||||||
|
int j = i - this.j.get(); // Akarin - atmoic
|
||||||
|
managedPlayersLock.readLock().lock(); // Akarin
|
||||||
|
ArrayList arraylist = Lists.newArrayList(this.managedPlayers);
|
||||||
|
managedPlayersLock.readLock().unlock(); // Akarin
|
||||||
|
Iterator iterator = arraylist.iterator();
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
EntityPlayer entityplayer = (EntityPlayer) iterator.next();
|
||||||
|
this.setViewDistance(entityplayer, i, false); // Paper - Split, don't mark sort pending, we'll handle it after
|
||||||
|
}
|
||||||
|
|
||||||
|
this.j.getAndSet(i); // Akarin - atmoic
|
||||||
|
this.e();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setViewDistance(EntityPlayer entityplayer, int i) {
|
||||||
|
this.setViewDistance(entityplayer, i, true); // Mark sort pending by default so we don't have to remember to do so all the time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from above with minor changes
|
||||||
|
public void setViewDistance(EntityPlayer entityplayer, int i, boolean markSort) {
|
||||||
|
i = MathHelper.clamp(i, 3, 32);
|
||||||
|
int oldViewDistance = entityplayer.getViewDistance();
|
||||||
|
if (i != oldViewDistance) {
|
||||||
|
int j = i - oldViewDistance;
|
||||||
|
|
||||||
|
int k = (int) entityplayer.locX >> 4;
|
||||||
|
int l = (int) entityplayer.locZ >> 4;
|
||||||
|
int i1;
|
||||||
|
int j1;
|
||||||
|
|
||||||
|
if (j > 0) {
|
||||||
|
synchronized (this) { // Akarin - synchronized
|
||||||
|
for (i1 = k - i; i1 <= k + i; ++i1) {
|
||||||
|
for (j1 = l - i; j1 <= l + i; ++j1) {
|
||||||
|
PlayerChunk playerchunk = this.c(i1, j1);
|
||||||
|
|
||||||
|
if (!playerchunk.d(entityplayer)) {
|
||||||
|
playerchunk.a(entityplayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // Akarin
|
||||||
|
} else {
|
||||||
|
synchronized (this) { // Akarin - synchronized
|
||||||
|
for (i1 = k - oldViewDistance; i1 <= k + oldViewDistance; ++i1) {
|
||||||
|
for (j1 = l - oldViewDistance; j1 <= l + oldViewDistance; ++j1) {
|
||||||
|
if (!this.a(i1, j1, k, l, i)) {
|
||||||
|
this.c(i1, j1).b(entityplayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // Akarin
|
||||||
|
if (markSort) {
|
||||||
|
this.e();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Paper end
|
||||||
|
|
||||||
|
private void e() {
|
||||||
|
this.l.getAndSet(true); // Akarin - atmoic
|
||||||
|
this.m.getAndSet(true); // Akarin - atmoic
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getFurthestViewableBlock(int i) {
|
||||||
|
return i * 16 - 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long d(int i, int j) {
|
||||||
|
return (long) i + 2147483647L | (long) j + 2147483647L << 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void a(PlayerChunk playerchunk) { // Akarin - synchronized
|
||||||
|
org.spigotmc.AsyncCatcher.catchOp("Async Player Chunk Add"); // Paper // Akarin
|
||||||
|
this.f.add(playerchunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void b(PlayerChunk playerchunk) { // Akarin - synchronized
|
||||||
|
// org.spigotmc.AsyncCatcher.catchOp("Async Player Chunk Remove"); // Paper // Akarin
|
||||||
|
ChunkCoordIntPair chunkcoordintpair = playerchunk.a();
|
||||||
|
long i = d(chunkcoordintpair.x, chunkcoordintpair.z);
|
||||||
|
|
||||||
|
playerchunk.c();
|
||||||
|
this.e.remove(i);
|
||||||
|
this.i.remove(playerchunk);
|
||||||
|
this.f.remove(playerchunk);
|
||||||
|
this.g.remove(playerchunk);
|
||||||
|
this.h.remove(playerchunk);
|
||||||
|
Chunk chunk = playerchunk.f();
|
||||||
|
|
||||||
|
if (chunk != null) {
|
||||||
|
// Paper start - delay chunk unloads
|
||||||
|
if (world.paperConfig.delayChunkUnloadsBy <= 0) {
|
||||||
|
this.getWorld().getChunkProviderServer().unload(chunk);
|
||||||
|
} else {
|
||||||
|
chunk.scheduledForUnload = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
// Paper end
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// CraftBukkit start - Sorter to load nearby chunks first
|
||||||
|
private static class ChunkCoordComparator implements java.util.Comparator<ChunkCoordIntPair> {
|
||||||
|
private int x;
|
||||||
|
private int z;
|
||||||
|
|
||||||
|
public ChunkCoordComparator (EntityPlayer entityplayer) {
|
||||||
|
x = (int) entityplayer.locX >> 4;
|
||||||
|
z = (int) entityplayer.locZ >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compare(ChunkCoordIntPair a, ChunkCoordIntPair b) {
|
||||||
|
if (a.equals(b)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtract current position to set center point
|
||||||
|
int ax = a.x - this.x;
|
||||||
|
int az = a.z - this.z;
|
||||||
|
int bx = b.x - this.x;
|
||||||
|
int bz = b.z - this.z;
|
||||||
|
|
||||||
|
int result = ((ax - bx) * (ax + bx)) + ((az - bz) * (az + bz));
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ax < 0) {
|
||||||
|
if (bx < 0) {
|
||||||
|
return bz - az;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (bx < 0) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return az - bz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// CraftBukkit end
|
||||||
|
|
||||||
|
// Paper start - Player view distance API
|
||||||
|
public void updateViewDistance(EntityPlayer player, int distanceIn) {
|
||||||
|
final int oldViewDistance = player.getViewDistance();
|
||||||
|
|
||||||
|
// This represents the view distance that we will set on the player
|
||||||
|
// It can exist as a negative value
|
||||||
|
int playerViewDistance = MathHelper.clamp(distanceIn, 3, 32);
|
||||||
|
|
||||||
|
// This value is the one we actually use to update the chunk map
|
||||||
|
// We don't ever want this to be a negative
|
||||||
|
int toSet = playerViewDistance;
|
||||||
|
|
||||||
|
if (distanceIn < 0) {
|
||||||
|
playerViewDistance = -1;
|
||||||
|
toSet = world.getPlayerChunkMap().getViewDistance();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toSet != oldViewDistance) {
|
||||||
|
// Order matters
|
||||||
|
this.setViewDistance(player, toSet);
|
||||||
|
player.setViewDistance(playerViewDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Paper end
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user