Added ChunkUtils#isLoaded + optimization of the thread providers

This commit is contained in:
Felix Cravic 2020-08-10 11:32:03 +02:00
parent 13275eb534
commit c2165abe1f
11 changed files with 123 additions and 159 deletions

View File

@ -142,7 +142,7 @@ public class CollisionUtils {
blockPos.setZ((int) Math.floor(corner.getZ()));
final Chunk chunk = instance.getChunkAt(blockPos);
if (ChunkUtils.isChunkUnloaded(chunk)) {
if (!ChunkUtils.isLoaded(chunk)) {
// Collision at chunk border
return false;
}

View File

@ -339,7 +339,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
return;
}
boolean chunkUnloaded = ChunkUtils.isChunkUnloaded(instance, position.getX(), position.getZ());
boolean chunkUnloaded = !ChunkUtils.isLoaded(instance, position.getX(), position.getZ());
if (chunkUnloaded) {
// No update for entities in unloaded chunk
return;
@ -461,7 +461,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
for (int y = minY; y <= maxY; y++) {
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
chunkUnloaded = ChunkUtils.isChunkUnloaded(instance, x, z);
chunkUnloaded = !ChunkUtils.isLoaded(instance, x, z);
if (chunkUnloaded)
continue;
final CustomBlock customBlock = instance.getCustomBlock(x, y, z);

View File

@ -368,7 +368,7 @@ public abstract class EntityCreature extends LivingEntity {
// Can't path in an unloaded chunk
final Chunk chunk = instance.getChunkAt(position);
if (ChunkUtils.isChunkUnloaded(chunk)) {
if (!ChunkUtils.isLoaded(chunk)) {
this.pathLock.unlock();
return false;
}

View File

@ -91,7 +91,7 @@ public class ClosestEntityTarget extends TargetSelector {
final int targetX = chunkX + x;
final int targetZ = chunkZ + z;
final Chunk chunk = instance.getChunk(targetX, targetZ);
if (!ChunkUtils.isChunkUnloaded(chunk)) {
if (ChunkUtils.isLoaded(chunk)) {
// Chunk is loaded, add it
chunks.add(chunk);
}

View File

@ -455,7 +455,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
* if {@code chunk} is unloaded, return an empty {@link HashSet}
*/
public Set<Entity> getChunkEntities(Chunk chunk) {
if (ChunkUtils.isChunkUnloaded(chunk))
if (!ChunkUtils.isLoaded(chunk))
return new HashSet<>();
final long index = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());

View File

@ -236,7 +236,7 @@ public class InstanceContainer extends Instance {
final Chunk chunk = getChunkAt(blockPosition);
// Chunk unloaded, stop here
if (ChunkUtils.isChunkUnloaded(chunk))
if (!ChunkUtils.isLoaded(chunk))
return false;
final int x = blockPosition.getX();
@ -316,7 +316,8 @@ public class InstanceContainer extends Instance {
@Override
public void unloadChunk(Chunk chunk) {
if (ChunkUtils.isChunkUnloaded(chunk)) {
// Already unloaded chunk
if (!ChunkUtils.isLoaded(chunk)) {
return;
}
// Schedule the chunk removal
@ -328,7 +329,7 @@ public class InstanceContainer extends Instance {
@Override
public Chunk getChunk(int chunkX, int chunkZ) {
final Chunk chunk = chunks.get(ChunkUtils.getChunkIndex(chunkX, chunkZ));
return ChunkUtils.isChunkUnloaded(chunk) ? null : chunk;
return ChunkUtils.isLoaded(chunk) ? chunk : null;
}
/**

View File

@ -51,7 +51,7 @@ public class PlayerPositionListener {
float yaw, float pitch, boolean onGround) {
// Try to move in an unloaded chunk, prevent it
if (ChunkUtils.isChunkUnloaded(player.getInstance(), x, z)) {
if (!ChunkUtils.isLoaded(player.getInstance(), x, z)) {
player.teleport(player.getPosition());
return;
}

View File

@ -1,12 +1,15 @@
package net.minestom.server.thread;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.utils.chunk.ChunkUtils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@ -19,28 +22,28 @@ public class PerGroupChunkProvider extends ThreadProvider {
/**
* Chunk -> its chunk group
*/
private Map<Instance, Map<ChunkCoordinate, Set<ChunkCoordinate>>> instanceChunksGroupMap = new ConcurrentHashMap<>();
private Map<Instance, Long2ObjectMap<LongSet>> instanceChunksGroupMap = new ConcurrentHashMap<>();
/**
* Used to know to which instance is linked a Set of chunks
*/
private Map<Instance, Map<Set<ChunkCoordinate>, Instance>> instanceInstanceMap = new ConcurrentHashMap<>();
private Map<Instance, Map<LongSet, Instance>> instanceInstanceMap = new ConcurrentHashMap<>();
@Override
public void onChunkLoad(Instance instance, int chunkX, int chunkZ) {
Map<ChunkCoordinate, Set<ChunkCoordinate>> chunksGroupMap = getChunksGroupMap(instance);
Map<Set<ChunkCoordinate>, Instance> instanceMap = getInstanceMap(instance);
Long2ObjectMap<LongSet> chunksGroupMap = getChunksGroupMap(instance);
Map<LongSet, Instance> instanceMap = getInstanceMap(instance);
// List of groups which are neighbours
List<Set<ChunkCoordinate>> neighboursGroups = new ArrayList<>();
List<LongSet> neighboursGroups = new ArrayList<>();
final List<ChunkCoordinate> chunks = getNeighbours(instance, chunkX, chunkZ);
final long[] chunks = ChunkUtils.getNeighbours(instance, chunkX, chunkZ);
boolean findGroup = false;
for (ChunkCoordinate chunkCoordinate : chunks) {
if (chunksGroupMap.containsKey(chunkCoordinate)) {
final Set<ChunkCoordinate> group = chunksGroupMap.get(chunkCoordinate);
for (long chunkIndex : chunks) {
if (chunksGroupMap.containsKey(chunkIndex)) {
final LongSet group = chunksGroupMap.get(chunkIndex);
neighboursGroups.add(group);
chunksGroupMap.remove(chunkCoordinate);
chunksGroupMap.remove(chunkIndex);
instanceMap.remove(group);
findGroup = true;
}
@ -48,30 +51,31 @@ public class PerGroupChunkProvider extends ThreadProvider {
if (!findGroup) {
// Create group of one chunk
final ChunkCoordinate chunkCoordinate = new ChunkCoordinate(chunkX, chunkZ);
Set<ChunkCoordinate> chunkCoordinates = new CopyOnWriteArraySet<>();
chunkCoordinates.add(chunkCoordinate);
final long chunkIndex = ChunkUtils.getChunkIndex(chunkX, chunkZ);
LongSet chunkIndexes = new LongArraySet();
chunkIndexes.add(chunkIndex);
chunksGroupMap.put(chunkCoordinate, chunkCoordinates);
instanceMap.put(chunkCoordinates, instance);
chunksGroupMap.put(chunkIndex, chunkIndexes);
instanceMap.put(chunkIndexes, instance);
return;
}
// Represent the merged group of all the neighbours
Set<ChunkCoordinate> finalGroup = new CopyOnWriteArraySet<>();
LongSet finalGroup = new LongArraySet();
// Add the newly loaded chunk to the group
finalGroup.add(new ChunkCoordinate(chunkX, chunkZ));
final long chunkIndex = ChunkUtils.getChunkIndex(chunkX, chunkZ);
finalGroup.add(chunkIndex);
// Add all the neighbours groups to the final one
for (Set<ChunkCoordinate> chunkCoordinates : neighboursGroups) {
for (LongSet chunkCoordinates : neighboursGroups) {
finalGroup.addAll(chunkCoordinates);
}
// Complete maps
for (ChunkCoordinate chunkCoordinate : finalGroup) {
chunksGroupMap.put(chunkCoordinate, finalGroup);
for (long index : finalGroup) {
chunksGroupMap.put(index, finalGroup);
}
instanceMap.put(finalGroup, instance);
@ -80,15 +84,15 @@ public class PerGroupChunkProvider extends ThreadProvider {
@Override
public void onChunkUnload(Instance instance, int chunkX, int chunkZ) {
Map<ChunkCoordinate, Set<ChunkCoordinate>> chunksGroupMap = getChunksGroupMap(instance);
Map<Set<ChunkCoordinate>, Instance> instanceMap = getInstanceMap(instance);
Long2ObjectMap<LongSet> chunksGroupMap = getChunksGroupMap(instance);
Map<LongSet, Instance> instanceMap = getInstanceMap(instance);
final ChunkCoordinate chunkCoordinate = new ChunkCoordinate(chunkX, chunkZ);
if (chunksGroupMap.containsKey(chunkCoordinate)) {
final long chunkIndex = ChunkUtils.getChunkIndex(chunkX, chunkZ);
if (chunksGroupMap.containsKey(chunkIndex)) {
// The unloaded chunk is part of a group, remove it from the group
Set<ChunkCoordinate> chunkCoordinates = chunksGroupMap.get(chunkCoordinate);
chunkCoordinates.remove(chunkCoordinate);
chunksGroupMap.remove(chunkCoordinate);
LongSet chunkCoordinates = chunksGroupMap.get(chunkIndex);
chunkCoordinates.remove(chunkIndex);
chunksGroupMap.remove(chunkIndex);
if (chunkCoordinates.isEmpty()) {
// The chunk group is empty, remove it entirely
@ -102,17 +106,16 @@ public class PerGroupChunkProvider extends ThreadProvider {
// Set of already-updated instances this tick
final Set<Instance> updatedInstance = new HashSet<>();
getLock().lock();
instanceInstanceMap.entrySet().forEach(entry -> {
final Instance instance = entry.getKey();
final Map<Set<ChunkCoordinate>, Instance> instanceMap = entry.getValue();
final Map<LongSet, Instance> instanceMap = entry.getValue();
// True if the instance ended its tick call
AtomicBoolean instanceUpdated = new AtomicBoolean(false);
// Update all the chunks + instances
for (Map.Entry<Set<ChunkCoordinate>, Instance> ent : instanceMap.entrySet()) {
final Set<ChunkCoordinate> chunks = ent.getKey();
for (Map.Entry<LongSet, Instance> ent : instanceMap.entrySet()) {
final LongSet chunksIndexes = ent.getKey();
final boolean shouldUpdateInstance = updatedInstance.add(instance);
pool.execute(() -> {
@ -127,9 +130,10 @@ public class PerGroupChunkProvider extends ThreadProvider {
while (!instanceUpdated.get()) {
}
for (ChunkCoordinate chunkCoordinate : chunks) {
final Chunk chunk = instance.getChunk(chunkCoordinate.chunkX, chunkCoordinate.chunkZ);
if (ChunkUtils.isChunkUnloaded(chunk)) {
for (long chunkIndex : chunksIndexes) {
final int[] chunkCoordinates = ChunkUtils.getChunkCoord(chunkIndex);
final Chunk chunk = instance.getChunk(chunkCoordinates[0], chunkCoordinates[1]);
if (!ChunkUtils.isLoaded(chunk)) {
continue;
}
@ -142,49 +146,14 @@ public class PerGroupChunkProvider extends ThreadProvider {
}
});
getLock().unlock();
}
/**
* Get all the neighbours of a chunk and itself, no diagonals
*
* @param instance the instance of the chunks
* @param chunkX the chunk X
* @param chunkZ the chunk Z
* @return the loaded neighbours of the chunk
*/
private List<ChunkCoordinate> getNeighbours(Instance instance, int chunkX, int chunkZ) {
List<ChunkCoordinate> chunks = new ArrayList<>();
// Constants used to loop through the neighbors
final int[] posX = {1, 0, -1};
final int[] posZ = {1, 0, -1};
for (int x : posX) {
for (int z : posZ) {
// No diagonal check
if ((Math.abs(x) + Math.abs(z)) == 2)
continue;
final int targetX = chunkX + x;
final int targetZ = chunkZ + z;
final Chunk chunk = instance.getChunk(targetX, targetZ);
if (!ChunkUtils.isChunkUnloaded(chunk)) {
// Chunk is loaded, add it
chunks.add(toChunkCoordinate(chunk));
}
}
}
return chunks;
private Long2ObjectMap<LongSet> getChunksGroupMap(Instance instance) {
return instanceChunksGroupMap.computeIfAbsent(instance, inst -> new Long2ObjectOpenHashMap<>());
}
private Map<ChunkCoordinate, Set<ChunkCoordinate>> getChunksGroupMap(Instance instance) {
return instanceChunksGroupMap.computeIfAbsent(instance, inst -> new ConcurrentHashMap<>());
}
private Map<Set<ChunkCoordinate>, Instance> getInstanceMap(Instance instance) {
return instanceInstanceMap.computeIfAbsent(instance, inst -> new ConcurrentHashMap<>());
private Map<LongSet, Instance> getInstanceMap(Instance instance) {
return instanceInstanceMap.computeIfAbsent(instance, inst -> new HashMap<>());
}
}

View File

@ -1,50 +1,51 @@
package net.minestom.server.thread;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.utils.chunk.ChunkUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Separate work between instance (1 instance = 1 thread execution)
*/
public class PerInstanceThreadProvider extends ThreadProvider {
private Map<Instance, Set<ChunkCoordinate>> instanceChunkMap = new HashMap<>();
private Map<Instance, LongSet> instanceChunkMap = new HashMap<>();
@Override
public void onChunkLoad(Instance instance, int chunkX, int chunkZ) {
// Add the loaded chunk to the instance chunks list
Set<ChunkCoordinate> chunkCoordinates = getChunkCoordinates(instance);
chunkCoordinates.add(new ChunkCoordinate(chunkX, chunkZ));
LongSet chunkCoordinates = getChunkCoordinates(instance);
final long index = ChunkUtils.getChunkIndex(chunkX, chunkZ);
chunkCoordinates.add(index);
}
@Override
public void onChunkUnload(Instance instance, int chunkX, int chunkZ) {
Set<ChunkCoordinate> chunkCoordinates = getChunkCoordinates(instance);
LongSet chunkCoordinates = getChunkCoordinates(instance);
final long index = ChunkUtils.getChunkIndex(chunkX, chunkZ);
// Remove the unloaded chunk from the instance list
chunkCoordinates.removeIf(chunkCoordinate -> chunkCoordinate.chunkX == chunkX &&
chunkCoordinate.chunkZ == chunkZ);
chunkCoordinates.remove(index);
}
@Override
public void update(long time) {
getLock().lock();
for (Map.Entry<Instance, Set<ChunkCoordinate>> entry : instanceChunkMap.entrySet()) {
for (Map.Entry<Instance, LongSet> entry : instanceChunkMap.entrySet()) {
final Instance instance = entry.getKey();
final Set<ChunkCoordinate> chunkCoordinates = entry.getValue();
final LongSet chunkIndexes = entry.getValue();
pool.execute(() -> {
updateInstance(instance, time);
for (ChunkCoordinate chunkCoordinate : chunkCoordinates) {
final Chunk chunk = instance.getChunk(chunkCoordinate.chunkX, chunkCoordinate.chunkZ);
if (ChunkUtils.isChunkUnloaded(chunk))
for (long chunkIndex : chunkIndexes) {
final int[] chunkCoordinates = ChunkUtils.getChunkCoord(chunkIndex);
final Chunk chunk = instance.getChunk(chunkCoordinates[0], chunkCoordinates[1]);
if (!ChunkUtils.isLoaded(chunk))
continue;
updateChunk(instance, chunk, time);
@ -53,12 +54,11 @@ public class PerInstanceThreadProvider extends ThreadProvider {
}
});
getLock().unlock();
}
}
private Set<ChunkCoordinate> getChunkCoordinates(Instance instance) {
return instanceChunkMap.computeIfAbsent(instance, inst -> new HashSet<>());
private LongSet getChunkCoordinates(Instance instance) {
return instanceChunkMap.computeIfAbsent(instance, inst -> new LongArraySet());
}
}

View File

@ -6,10 +6,8 @@ import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.utils.thread.MinestomThread;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
/**
@ -27,8 +25,6 @@ public abstract class ThreadProvider {
*/
private int threadCount;
private ReentrantLock lock = new ReentrantLock();
{
// Default thread count in the pool
setThreadCount(5);
@ -78,15 +74,6 @@ public abstract class ThreadProvider {
refreshPool();
}
/**
* Get the lock of this thread provider
*
* @return the thread provider lock
*/
public ReentrantLock getLock() {
return lock;
}
private void refreshPool() {
this.pool = new MinestomThread(threadCount, MinecraftServer.THREAD_NAME_TICK);
}
@ -195,41 +182,4 @@ public abstract class ThreadProvider {
}
}
/**
* Convert a {@link Chunk} to a {@link ChunkCoordinate}
*
* @param chunk the chunk to convert
* @return the converted {@link ChunkCoordinate}
*/
protected ChunkCoordinate toChunkCoordinate(Chunk chunk) {
return new ChunkCoordinate(chunk.getChunkX(), chunk.getChunkZ());
}
/**
* Represent the coordinates of a {@link Chunk}
* Used so the chunks objects can be cleared by the garbage collector properly¬
*/
protected static class ChunkCoordinate {
public int chunkX, chunkZ;
public ChunkCoordinate(int chunkX, int chunkZ) {
this.chunkX = chunkX;
this.chunkZ = chunkZ;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ChunkCoordinate that = (ChunkCoordinate) o;
return chunkX == that.chunkX &&
chunkZ == that.chunkZ;
}
@Override
public int hashCode() {
return Objects.hash(chunkX, chunkZ);
}
}
}

View File

@ -1,5 +1,7 @@
package net.minestom.server.utils.chunk;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongList;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.utils.BlockPosition;
@ -13,29 +15,29 @@ public final class ChunkUtils {
}
/**
* Get if a chunk is unloaded
* Get if a chunk is loaded
*
* @param chunk the chunk to check
* @return true if the chunk is unloaded, false otherwise
* @return true if the chunk is loaded, false otherwise
*/
public static boolean isChunkUnloaded(Chunk chunk) {
return chunk == null || !chunk.isLoaded();
public static boolean isLoaded(Chunk chunk) {
return chunk != null && chunk.isLoaded();
}
/**
* Get if a chunk is unloaded
* Get if a chunk is loaded
*
* @param instance the instance to check
* @param x instance X coordinate
* @param z instance Z coordinate
* @return true if the chunk is unloaded, false otherwise
* @return true if the chunk is loaded, false otherwise
*/
public static boolean isChunkUnloaded(Instance instance, float x, float z) {
public static boolean isLoaded(Instance instance, float x, float z) {
final int chunkX = getChunkCoordinate((int) x);
final int chunkZ = getChunkCoordinate((int) z);
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
return isChunkUnloaded(chunk);
return isLoaded(chunk);
}
/**
@ -48,6 +50,8 @@ public final class ChunkUtils {
}
/**
* Get the chunk index of chunk coordinates
*
* @param chunkX the chunk X
* @param chunkZ the chunk Z
* @return a number storing the chunk X and Z
@ -71,6 +75,8 @@ public final class ChunkUtils {
}
/**
* Get the chunks in range of a position
*
* @param position the initial position
* @param range how far should it retrieves chunk
* @return an array containing chunks index which can be converted using {@link #getChunkCoord(long)}
@ -92,6 +98,44 @@ public final class ChunkUtils {
}
/**
* Get all the loaded neighbours of a chunk and itself, no diagonals
*
* @param instance the instance of the chunks
* @param chunkX the chunk X
* @param chunkZ the chunk Z
* @return an array containing all the loaded neighbours
* can be deserialized using {@link #indexToChunkPosition(int)}
*/
public static long[] getNeighbours(Instance instance, int chunkX, int chunkZ) {
LongList chunks = new LongArrayList();
// Constants used to loop through the neighbors
final int[] posX = {1, 0, -1};
final int[] posZ = {1, 0, -1};
for (int x : posX) {
for (int z : posZ) {
// No diagonal check
if ((Math.abs(x) + Math.abs(z)) == 2)
continue;
final int targetX = chunkX + x;
final int targetZ = chunkZ + z;
final Chunk chunk = instance.getChunk(targetX, targetZ);
if (ChunkUtils.isLoaded(chunk)) {
// Chunk is loaded, add it
final long index = getChunkIndex(targetX, targetZ);
chunks.add(index);
}
}
}
return chunks.toArray(new long[0]);
}
/**
* Get the block index of a position
*
* @param x the block X
* @param y the block Y
* @param z the block Z