diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 7aa3e7243..d89324880 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -63,14 +63,15 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { protected float cacheX, cacheY, cacheZ; // Used to synchronize with #getPosition protected float lastYaw, lastPitch; protected float cacheYaw, cachePitch; - // Synchronization - private static final long SYNCHRONIZATION_DELAY = 1500; // In ms private BoundingBox boundingBox; protected Entity vehicle; + // Velocity protected Vector velocity = new Vector(); // Movement in block per second + protected long lastVelocityUpdateTime; // Reset velocity to 0 after countdown + protected float gravityDragPerTick; protected float eyeHeight; @@ -84,13 +85,17 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { private boolean removed; private boolean shouldRemove; private long scheduledRemoveTime; + private final Set passengers = new CopyOnWriteArraySet<>(); private long lastUpdate; private final EntityType entityType; - protected long lastVelocityUpdateTime; // Reset velocity to 0 after countdown - private final Map, List> eventCallbacks = new ConcurrentHashMap<>(); + + // Synchronization + private static final long SYNCHRONIZATION_DELAY = 1500; // In ms private long lastSynchronizationTime; + private final Map, List> eventCallbacks = new ConcurrentHashMap<>(); + // Metadata protected boolean onFire; protected boolean crouched; @@ -990,8 +995,10 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { final int[] oldChunksEntity = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunksEntity, updatedVisibleChunksEntity); for (int index : oldChunksEntity) { - final int[] chunkPos = ChunkUtils.getChunkCoord(lastVisibleChunksEntity[index]); - final Chunk chunk = instance.getChunk(chunkPos[0], chunkPos[1]); + final long chunkIndex = lastVisibleChunksEntity[index]; + final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex); + final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex); + final Chunk chunk = instance.getChunk(chunkX, chunkZ); if (chunk == null) continue; instance.getChunkEntities(chunk).forEach(ent -> { @@ -1010,8 +1017,10 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { final int[] newChunksEntity = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunksEntity, lastVisibleChunksEntity); for (int index : newChunksEntity) { - final int[] chunkPos = ChunkUtils.getChunkCoord(updatedVisibleChunksEntity[index]); - final Chunk chunk = instance.getChunk(chunkPos[0], chunkPos[1]); + final long chunkIndex = updatedVisibleChunksEntity[index]; + final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex); + final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex); + final Chunk chunk = instance.getChunk(chunkX, chunkZ); if (chunk == null) continue; instance.getChunkEntities(chunk).forEach(ent -> { diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 4252c298a..19e5f780a 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -551,9 +551,9 @@ public class Player extends LivingEntity implements CommandSender { AtomicInteger counter = new AtomicInteger(0); for (long visibleChunk : visibleChunks) { - final int[] chunkPos = ChunkUtils.getChunkCoord(visibleChunk); - final int chunkX = chunkPos[0]; - final int chunkZ = chunkPos[1]; + final int chunkX = ChunkUtils.getChunkCoordX(visibleChunk); + final int chunkZ = ChunkUtils.getChunkCoordZ(visibleChunk); + Consumer callback = (chunk) -> { if (chunk != null) { chunk.addViewer(this); @@ -1200,13 +1200,16 @@ public class Player extends LivingEntity implements CommandSender { // Unload old chunks for (int index : oldChunks) { - final int[] chunkPos = ChunkUtils.getChunkCoord(lastVisibleChunks[index]); + final long chunkIndex = lastVisibleChunks[index]; + final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex); + final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex); + UnloadChunkPacket unloadChunkPacket = new UnloadChunkPacket(); - unloadChunkPacket.chunkX = chunkPos[0]; - unloadChunkPacket.chunkZ = chunkPos[1]; + unloadChunkPacket.chunkX = chunkX; + unloadChunkPacket.chunkZ = chunkZ; playerConnection.sendPacket(unloadChunkPacket); - final Chunk chunk = instance.getChunk(chunkPos[0], chunkPos[1]); + final Chunk chunk = instance.getChunk(chunkX, chunkZ); if (chunk != null) chunk.removeViewer(this); } @@ -1215,8 +1218,11 @@ public class Player extends LivingEntity implements CommandSender { // Load new chunks for (int index : newChunks) { - final int[] chunkPos = ChunkUtils.getChunkCoord(updatedVisibleChunks[index]); - instance.loadOptionalChunk(chunkPos[0], chunkPos[1], chunk -> { + final long chunkIndex = updatedVisibleChunks[index]; + final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex); + final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex); + + instance.loadOptionalChunk(chunkX, chunkZ, chunk -> { if (chunk == null) { // Cannot load chunk (auto load is not enabled) return; diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index e62e49f61..6f015b24a 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -832,7 +832,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta } private Set getEntitiesInChunk(long index) { - final Set entities = chunkEntities.getOrDefault(index, new CopyOnWriteArraySet<>()); + final Set entities = chunkEntities.computeIfAbsent(index, i -> new CopyOnWriteArraySet<>()); return entities; } diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index c36208308..e346f2952 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -1,5 +1,7 @@ package net.minestom.server.instance; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import lombok.Setter; import net.minestom.server.MinecraftServer; import net.minestom.server.data.Data; @@ -30,7 +32,6 @@ import net.minestom.server.world.DimensionType; import net.minestom.server.world.biomes.Biome; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.locks.Lock; @@ -52,7 +53,8 @@ public class InstanceContainer extends Instance { private List sharedInstances = new CopyOnWriteArrayList<>(); private ChunkGenerator chunkGenerator; - private Map chunks = new ConcurrentHashMap<>(); + // WARNING: need to be synchronized properly + private Long2ObjectMap chunks = new Long2ObjectOpenHashMap(); private Set scheduledChunksToRemove = new HashSet<>(); private ReadWriteLock changingBlockLock = new ReentrantReadWriteLock(); @@ -333,7 +335,8 @@ public class InstanceContainer extends Instance { @Override public Chunk getChunk(int chunkX, int chunkZ) { - final Chunk chunk = chunks.get(ChunkUtils.getChunkIndex(chunkX, chunkZ)); + final long index = ChunkUtils.getChunkIndex(chunkX, chunkZ); + final Chunk chunk = chunks.get(index); return ChunkUtils.isLoaded(chunk) ? chunk : null; } @@ -390,11 +393,13 @@ public class InstanceContainer extends Instance { e.printStackTrace(); } } else { - final Iterator chunks = getChunks().iterator(); - while (chunks.hasNext()) { - final Chunk chunk = chunks.next(); - final boolean isLast = !chunks.hasNext(); - saveChunkToStorageFolder(chunk, isLast ? callback : null); + synchronized (chunks) { + final Iterator chunkIterator = chunks.values().iterator(); + while (chunkIterator.hasNext()) { + final Chunk chunk = chunkIterator.next(); + final boolean isLast = !chunkIterator.hasNext(); + saveChunkToStorageFolder(chunk, isLast ? callback : null); + } } } } @@ -503,6 +508,11 @@ public class InstanceContainer extends Instance { this.chunkGenerator = chunkGenerator; } + /** + * Get all the instance chunks + * + * @return the chunks of this instance + */ public Collection getChunks() { return Collections.unmodifiableCollection(chunks.values()); } diff --git a/src/main/java/net/minestom/server/thread/ThreadProvider.java b/src/main/java/net/minestom/server/thread/ThreadProvider.java index f1bbd4e98..657773e8e 100644 --- a/src/main/java/net/minestom/server/thread/ThreadProvider.java +++ b/src/main/java/net/minestom/server/thread/ThreadProvider.java @@ -94,8 +94,10 @@ public abstract class ThreadProvider { * @param time the time of the update in milliseconds */ protected void processChunkTick(Instance instance, long chunkIndex, long time) { - final int[] chunkCoordinates = ChunkUtils.getChunkCoord(chunkIndex); - final Chunk chunk = instance.getChunk(chunkCoordinates[0], chunkCoordinates[1]); + final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex); + final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex); + + final Chunk chunk = instance.getChunk(chunkX, chunkZ); if (!ChunkUtils.isLoaded(chunk)) return; diff --git a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java index a7612f6ee..a66e49e84 100644 --- a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java +++ b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java @@ -10,199 +10,211 @@ import net.minestom.server.utils.Position; public final class ChunkUtils { - private ChunkUtils() { + private ChunkUtils() { - } + } - /** - * Get if a chunk is loaded - * - * @param chunk the chunk to check - * @return true if the chunk is loaded, false otherwise - */ - public static boolean isLoaded(Chunk chunk) { - return chunk != null && chunk.isLoaded(); - } + /** + * Get if a chunk is loaded + * + * @param chunk the chunk to check + * @return true if the chunk is loaded, false otherwise + */ + public static boolean isLoaded(Chunk chunk) { + return chunk != null && chunk.isLoaded(); + } - /** - * 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 loaded, false otherwise - */ - public static boolean isLoaded(Instance instance, float x, float z) { - final int chunkX = getChunkCoordinate((int) x); - final int chunkZ = getChunkCoordinate((int) z); + /** + * 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 loaded, false otherwise + */ + 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 isLoaded(chunk); - } + final Chunk chunk = instance.getChunk(chunkX, chunkZ); + return isLoaded(chunk); + } - /** - * @param xz the instance coordinate to convert - * @return the chunk X or Z based on the argument - */ - public static int getChunkCoordinate(int xz) { - // Assume Chunk.CHUNK_SIZE_X == Chunk.CHUNK_SIZE_Z - return Math.floorDiv(xz, Chunk.CHUNK_SIZE_X); - } + /** + * @param xz the instance coordinate to convert + * @return the chunk X or Z based on the argument + */ + public static int getChunkCoordinate(int xz) { + // Assume Chunk.CHUNK_SIZE_X == Chunk.CHUNK_SIZE_Z + return Math.floorDiv(xz, Chunk.CHUNK_SIZE_X); + } - /** - * 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 - */ - public static long getChunkIndex(int chunkX, int chunkZ) { - return (((long) chunkX) << 32) | (chunkZ & 0xffffffffL); - } + /** + * 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 + */ + public static long getChunkIndex(int chunkX, int chunkZ) { + return (((long) chunkX) << 32) | (chunkZ & 0xffffffffL); + } - public static long getChunkIndexWithSection(int chunkX, int chunkZ, int section) { - long l = 0L; - l |= ((long) chunkX & 4194303L) << 42; - l |= ((long) section & 1048575L); - l |= ((long) chunkZ & 4194303L) << 20; - return l; - } + public static long getChunkIndexWithSection(int chunkX, int chunkZ, int section) { + long l = 0L; + l |= ((long) chunkX & 4194303L) << 42; + l |= ((long) section & 1048575L); + l |= ((long) chunkZ & 4194303L) << 20; + return l; + } - /** - * @param index the chunk index computed by {@link #getChunkIndex(int, int)} - * @return an array containing both the chunk X and Z (index 0 = X; index 1 = Z) - */ - public static int[] getChunkCoord(long index) { - final int chunkX = (int) (index >> 32); - final int chunkZ = (int) index; - return new int[]{chunkX, chunkZ}; - } + /** + * Convert a chunk index to its chunk X position + * + * @param index the chunk index computed by {@link #getChunkIndex(int, int)} + * @return the chunk X based on the index + */ + public static int getChunkCoordX(long index) { + return (int) (index >> 32); + } - public static short getLocalBlockPosAsShort(int x, int y, int z) { - x = x % 16; - z = z % 16; - return (short) (x << 8 | y << 4 | z); - } + /** + * Convert a chunk index to its chunk Z position + * + * @param index the chunk index computed by {@link #getChunkIndex(int, int)} + * @return the chunk Z based on the index + */ + public static int getChunkCoordZ(long index) { + final int chunkX = (int) (index >> 32); + final int chunkZ = (int) index; + return (int) index; + } - public static int getSectionAt(int y) { - return y / Chunk.CHUNK_SECTION_SIZE; - } + public static short getLocalBlockPosAsShort(int x, int y, int z) { + x = x % 16; + z = z % 16; + return (short) (x << 8 | y << 4 | z); + } - /** - * 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)} - */ - public static long[] getChunksInRange(final Position position, int range) { - range = range * 2; - long[] visibleChunks = new long[MathUtils.square(range + 1)]; - final int startLoop = -(range / 2); - final int endLoop = range / 2 + 1; - int counter = 0; - for (int x = startLoop; x < endLoop; x++) { - for (int z = startLoop; z < endLoop; z++) { - final int chunkX = getChunkCoordinate((int) (position.getX() + Chunk.CHUNK_SIZE_X * x)); - final int chunkZ = getChunkCoordinate((int) (position.getZ() + Chunk.CHUNK_SIZE_Z * z)); - visibleChunks[counter] = getChunkIndex(chunkX, chunkZ); - counter++; - } - } - return visibleChunks; - } + public static int getSectionAt(int y) { + return y / Chunk.CHUNK_SECTION_SIZE; + } - /** - * 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}; + /** + * 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 + */ + public static long[] getChunksInRange(final Position position, int range) { + range = range * 2; + long[] visibleChunks = new long[MathUtils.square(range + 1)]; + final int startLoop = -(range / 2); + final int endLoop = range / 2 + 1; + int counter = 0; + for (int x = startLoop; x < endLoop; x++) { + for (int z = startLoop; z < endLoop; z++) { + final int chunkX = getChunkCoordinate((int) (position.getX() + Chunk.CHUNK_SIZE_X * x)); + final int chunkZ = getChunkCoordinate((int) (position.getZ() + Chunk.CHUNK_SIZE_Z * z)); + visibleChunks[counter] = getChunkIndex(chunkX, chunkZ); + counter++; + } + } + return visibleChunks; + } - for (int x : posX) { - for (int z : posZ) { + /** + * 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}; - // No diagonal check - if ((Math.abs(x) + Math.abs(z)) == 2) - continue; + for (int x : posX) { + for (int z : posZ) { - 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); - } + // No diagonal check + if ((Math.abs(x) + Math.abs(z)) == 2) + continue; - } - } - return chunks.toArray(new long[0]); - } + 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); + } - /** - * Get the block index of a position - * - * @param x the block X - * @param y the block Y - * @param z the block Z - * @return an index which can be used to store and retrieve later data linked to a block position - */ - public static int getBlockIndex(int x, int y, int z) { - x = x % Chunk.CHUNK_SIZE_X; - z = z % Chunk.CHUNK_SIZE_Z; + } + } + return chunks.toArray(new long[0]); + } - short index = (short) (x & 0x000F); - index |= (y << 4) & 0x0FF0; - index |= (z << 12) & 0xF000; - return index & 0xffff; - } + /** + * Get the block index of a position + * + * @param x the block X + * @param y the block Y + * @param z the block Z + * @return an index which can be used to store and retrieve later data linked to a block position + */ + public static int getBlockIndex(int x, int y, int z) { + x = x % Chunk.CHUNK_SIZE_X; + z = z % Chunk.CHUNK_SIZE_Z; - /** - * @param index an index computed from {@link #getBlockIndex(int, int, int)} - * @param chunkX the chunk X - * @param chunkZ the chunk Z - * @return the instance position of the block located in {@code index} - */ - public static BlockPosition getBlockPosition(int index, int chunkX, int chunkZ) { - final int[] pos = indexToPosition(index, chunkX, chunkZ); - return new BlockPosition(pos[0], pos[1], pos[2]); - } + short index = (short) (x & 0x000F); + index |= (y << 4) & 0x0FF0; + index |= (z << 12) & 0xF000; + return index & 0xffff; + } - /** - * @param index an index computed from {@link #getBlockIndex(int, int, int)} - * @param chunkX the chunk X - * @param chunkZ the chunk Z - * @return the world position of the specified index with its chunks being {@code chunkX} and {@code chunk Z} - * positions in the array are in the order X/Y/Z - */ - public static int[] indexToPosition(int index, int chunkX, int chunkZ) { - int z = (byte) (index >> 12 & 0xF); - final int y = (index >>> 4 & 0xFF); - // index >> 0 = index - int x = (byte) (index & 0xF); + /** + * @param index an index computed from {@link #getBlockIndex(int, int, int)} + * @param chunkX the chunk X + * @param chunkZ the chunk Z + * @return the instance position of the block located in {@code index} + */ + public static BlockPosition getBlockPosition(int index, int chunkX, int chunkZ) { + final int[] pos = indexToPosition(index, chunkX, chunkZ); + return new BlockPosition(pos[0], pos[1], pos[2]); + } - x += 16 * chunkX; - z += 16 * chunkZ; + /** + * @param index an index computed from {@link #getBlockIndex(int, int, int)} + * @param chunkX the chunk X + * @param chunkZ the chunk Z + * @return the world position of the specified index with its chunks being {@code chunkX} and {@code chunk Z} + * positions in the array are in the order X/Y/Z + */ + public static int[] indexToPosition(int index, int chunkX, int chunkZ) { + int z = (byte) (index >> 12 & 0xF); + final int y = (index >>> 4 & 0xFF); + // index >> 0 = index + int x = (byte) (index & 0xF); - return new int[]{x, y, z}; - } + x += 16 * chunkX; + z += 16 * chunkZ; - /** - * @param index an index computed from {@link #getBlockIndex(int, int, int)} - * @return the chunk position (O-15) of the specified index, - * positions in the array are in the order X/Y/Z - */ - public static int[] indexToChunkPosition(int index) { - return indexToPosition(index, 0, 0); - } + return new int[]{x, y, z}; + } + + /** + * @param index an index computed from {@link #getBlockIndex(int, int, int)} + * @return the chunk position (O-15) of the specified index, + * positions in the array are in the order X/Y/Z + */ + public static int[] indexToChunkPosition(int index) { + return indexToPosition(index, 0, 0); + } } diff --git a/src/main/java/net/minestom/server/utils/entity/EntityUtils.java b/src/main/java/net/minestom/server/utils/entity/EntityUtils.java index 4681115c0..5beb19001 100644 --- a/src/main/java/net/minestom/server/utils/entity/EntityUtils.java +++ b/src/main/java/net/minestom/server/utils/entity/EntityUtils.java @@ -25,9 +25,9 @@ public final class EntityUtils { long[] visibleChunksEntity = ChunkUtils.getChunksInRange(ent2.getPosition(), MinecraftServer.ENTITY_VIEW_DISTANCE); for (long visibleChunk : visibleChunksEntity) { - final int[] chunkPos = ChunkUtils.getChunkCoord(visibleChunk); - final int chunkX = chunkPos[0]; - final int chunkZ = chunkPos[1]; + final int chunkX = ChunkUtils.getChunkCoordX(visibleChunk); + final int chunkZ = ChunkUtils.getChunkCoordZ(visibleChunk); + if (chunk.getChunkX() == chunkX && chunk.getChunkZ() == chunkZ) return true; }