From a38a143bbaa706c4996e957aaad323e0b5d9c94c Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Thu, 6 Aug 2020 16:28:04 +0200 Subject: [PATCH] Rework of the thread provider + ConnectionManager#getPlayer for UUID --- src/main/java/fr/themode/demo/PlayerInit.java | 7 +- .../themode/demo/entity/ChickenCreature.java | 6 +- .../net/minestom/server/MinecraftServer.java | 4 + .../net/minestom/server/UpdateManager.java | 46 +++++++---- .../minestom/server/instance/Instance.java | 2 + .../server/instance/InstanceContainer.java | 4 + .../server/network/ConnectionManager.java | 16 ++++ .../server/thread/PerGroupChunkProvider.java | 54 ++++++------ .../thread/PerInstanceThreadProvider.java | 82 +++++-------------- .../server/thread/SingleThreadProvider.java | 41 ---------- .../server/thread/ThreadProvider.java | 35 +++++--- 11 files changed, 135 insertions(+), 162 deletions(-) delete mode 100644 src/main/java/net/minestom/server/thread/SingleThreadProvider.java diff --git a/src/main/java/fr/themode/demo/PlayerInit.java b/src/main/java/fr/themode/demo/PlayerInit.java index 1abd3ef7a..c8ac9a466 100644 --- a/src/main/java/fr/themode/demo/PlayerInit.java +++ b/src/main/java/fr/themode/demo/PlayerInit.java @@ -149,9 +149,10 @@ public class PlayerInit { p.teleport(player.getPosition()); }*/ - ChickenCreature chickenCreature = new ChickenCreature(player.getPosition()); - chickenCreature.setInstance(player.getInstance()); - chickenCreature.setTarget(player); + for (int i = 0; i < 100; i++) { + ChickenCreature chickenCreature = new ChickenCreature(player.getPosition()); + chickenCreature.setInstance(player.getInstance()); + } /*EntityZombie zombie = new EntityZombie(player.getPosition()); zombie.setAttribute(Attribute.MOVEMENT_SPEED, 0.25f); diff --git a/src/main/java/fr/themode/demo/entity/ChickenCreature.java b/src/main/java/fr/themode/demo/entity/ChickenCreature.java index 6b56874cd..69da988cb 100644 --- a/src/main/java/fr/themode/demo/entity/ChickenCreature.java +++ b/src/main/java/fr/themode/demo/entity/ChickenCreature.java @@ -1,7 +1,7 @@ package fr.themode.demo.entity; import net.minestom.server.attribute.Attribute; -import net.minestom.server.entity.ai.goal.FollowTargetGoal; +import net.minestom.server.entity.ai.goal.RandomStrollGoal; import net.minestom.server.entity.ai.target.PlayerTarget; import net.minestom.server.entity.type.EntityChicken; import net.minestom.server.utils.Position; @@ -12,8 +12,8 @@ public class ChickenCreature extends EntityChicken { super(defaultPosition); //goalSelectors.add(new DoNothingGoal(this, 500, 0.1f)); - //goalSelectors.add(new RandomStrollGoal(this, 2)); - goalSelectors.add(new FollowTargetGoal(this)); + goalSelectors.add(new RandomStrollGoal(this, 2)); + //goalSelectors.add(new FollowTargetGoal(this)); targetSelectors.add(new PlayerTarget(this, 15)); diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 0ba49ee98..94e4b1423 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -288,6 +288,10 @@ public class MinecraftServer { return tagManager; } + public static UpdateManager getUpdateManager() { + return updateManager; + } + public void start(String address, int port, ResponseDataConsumer responseDataConsumer) { LOGGER.info("Starting Minestom server."); MinecraftServer.responseDataConsumer = responseDataConsumer; diff --git a/src/main/java/net/minestom/server/UpdateManager.java b/src/main/java/net/minestom/server/UpdateManager.java index f927e6061..a3b124bf7 100644 --- a/src/main/java/net/minestom/server/UpdateManager.java +++ b/src/main/java/net/minestom/server/UpdateManager.java @@ -4,12 +4,11 @@ import net.minestom.server.chat.ChatColor; import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.EntityManager; import net.minestom.server.entity.Player; -import net.minestom.server.instance.Chunk; import net.minestom.server.instance.Instance; import net.minestom.server.instance.InstanceManager; import net.minestom.server.network.ConnectionManager; import net.minestom.server.network.packet.server.play.KeepAlivePacket; -import net.minestom.server.thread.SingleThreadProvider; +import net.minestom.server.thread.PerInstanceThreadProvider; import net.minestom.server.thread.ThreadProvider; import net.minestom.server.utils.thread.MinestomThread; import net.minestom.server.utils.validate.Check; @@ -27,8 +26,7 @@ public final class UpdateManager { private ThreadProvider threadProvider; { - threadProvider = new SingleThreadProvider(); - //threadProvider = new PerInstanceThreadProvider(); + threadProvider = new PerInstanceThreadProvider(); //threadProvider = new PerGroupChunkProvider(); } @@ -49,24 +47,15 @@ public final class UpdateManager { long currentTime; while (!stopRequested) { currentTime = System.nanoTime(); + final long time = System.currentTimeMillis(); // Server tick - //long testTime = System.nanoTime(); - threadProvider.start(); - for (Instance instance : instanceManager.getInstances()) { - for (Chunk chunk : instance.getChunks()) { - threadProvider.linkThread(instance, chunk); - } - } - threadProvider.end(); - threadProvider.update(); - //System.out.println("time: " + (System.nanoTime() - testTime)); + threadProvider.update(time); // Waiting players update entityManager.updateWaitingPlayers(); // Keep Alive Handling - final long time = System.currentTimeMillis(); final KeepAlivePacket keepAlivePacket = new KeepAlivePacket(time); for (Player player : connectionManager.getOnlinePlayers()) { final long lastKeepAlive = time - player.getLastKeepAlive(); @@ -112,6 +101,33 @@ public final class UpdateManager { this.threadProvider = threadProvider; } + /** + * Signal the thread provider that a chunk has been loaded + * + * @param instance the instance of the chunk + * @param chunkX the chunk X + * @param chunkZ the chunk Z + */ + public void signalChunkLoad(Instance instance, int chunkX, int chunkZ) { + if (this.threadProvider == null) + return; + this.threadProvider.onChunkLoad(instance, chunkX, chunkZ); + } + + /** + * Signal the thread provider that a chunk has been unloaded + * + * @param instance the instance of the chunk + * @param chunkX the chunk X + * @param chunkZ the chunk Z + */ + public void signalChunkUnload(Instance instance, int chunkX, int chunkZ) { + if (this.threadProvider == null) + return; + this.threadProvider.onChunkUnload(instance, chunkX, chunkZ); + } + + public void stop() { stopRequested = true; } diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index f22d00603..e05ba9a7a 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -4,6 +4,7 @@ import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minestom.server.MinecraftServer; +import net.minestom.server.UpdateManager; import net.minestom.server.data.Data; import net.minestom.server.data.DataContainer; import net.minestom.server.entity.*; @@ -48,6 +49,7 @@ import java.util.function.Consumer; public abstract class Instance implements BlockModifier, EventHandler, DataContainer { protected static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager(); + protected static final UpdateManager UPDATE_MANAGER = MinecraftServer.getUpdateManager(); private DimensionType dimensionType; diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index da9bc4fa8..54698dc2f 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -342,6 +342,8 @@ public class InstanceContainer extends Instance { this.chunkEntities.remove(index); chunk.unload(); + + UPDATE_MANAGER.signalChunkUnload(this, chunkX, chunkZ); } @Override @@ -428,6 +430,7 @@ public class InstanceContainer extends Instance { final boolean loaded = chunkLoader.loadChunk(this, chunkX, chunkZ, chunk -> { cacheChunk(chunk); callChunkLoadEvent(chunkX, chunkZ); + UPDATE_MANAGER.signalChunkLoad(this, chunkX, chunkZ); if (callback != null) callback.accept(chunk); }); @@ -459,6 +462,7 @@ public class InstanceContainer extends Instance { callback.accept(chunk); } + UPDATE_MANAGER.signalChunkLoad(this, chunkX, chunkZ); callChunkLoadEvent(chunkX, chunkZ); } diff --git a/src/main/java/net/minestom/server/network/ConnectionManager.java b/src/main/java/net/minestom/server/network/ConnectionManager.java index 3b5d46e7b..8e6ab1846 100644 --- a/src/main/java/net/minestom/server/network/ConnectionManager.java +++ b/src/main/java/net/minestom/server/network/ConnectionManager.java @@ -60,6 +60,22 @@ public final class ConnectionManager { return null; } + /** + * Get the first player which validate {@link UUID#equals(Object)} + *

+ * This can cause issue if two or more players have the same UUID + * + * @param uuid the player UUID + * @return the first player who validate the UUID condition + */ + public Player getPlayer(UUID uuid) { + for (Player player : getOnlinePlayers()) { + if (player.getUuid().equals(uuid)) + return player; + } + return null; + } + /** * Send a rich message to all online players who validate the condition {@code condition} * diff --git a/src/main/java/net/minestom/server/thread/PerGroupChunkProvider.java b/src/main/java/net/minestom/server/thread/PerGroupChunkProvider.java index 688d24d5e..ff7dcf888 100644 --- a/src/main/java/net/minestom/server/thread/PerGroupChunkProvider.java +++ b/src/main/java/net/minestom/server/thread/PerGroupChunkProvider.java @@ -2,6 +2,7 @@ package net.minestom.server.thread; 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; @@ -13,66 +14,66 @@ import java.util.Set; *

* (1 chunks group = 1 thread execution) */ -// FIXME: unusable at the moment, too much overhead because groups need to be created every tick -// Should have a callback for when a chunk is loaded and unloaded, so groups are updated only once +// TODO public class PerGroupChunkProvider extends ThreadProvider { /** * Here are stored all cached chunks waiting for a ChunkGroup */ - private Map> cachedChunks = new HashMap<>(); + private Map> cachedChunks = new HashMap<>(); /** * Used to know to which instance is linked a Set of chunks */ - private Map, Instance> instanceMap = new HashMap<>(); + private Map, Instance> instanceMap = new HashMap<>(); @Override - public void start() { - this.cachedChunks.clear(); - this.instanceMap.clear(); - } - - @Override - public void linkThread(Instance instance, Chunk chunk) { - startChunkQuery(instance, chunk.getChunkX(), chunk.getChunkZ()); - } - - @Override - public void end() { + public void onChunkLoad(Instance instance, int chunkX, int chunkZ) { } @Override - public void update() { - // The time of the tick - final long time = System.currentTimeMillis(); + public void onChunkUnload(Instance instance, int chunkX, int chunkZ) { + + } + + @Override + public void update(long time) { // Set of already-updated instances final Set updatedInstance = new HashSet<>(); // Update all the chunks - for (Map.Entry, Instance> entry : instanceMap.entrySet()) { - Set chunks = entry.getKey(); + for (Map.Entry, Instance> entry : instanceMap.entrySet()) { + Set chunks = entry.getKey(); Instance instance = entry.getValue(); final boolean updateInstance = updatedInstance.add(instance); pool.execute(() -> { - /*if (updateInstance) { + if (updateInstance) { updateInstance(instance, time); } - for (Chunk chunk : chunks) { + for (ChunkCoordinate chunkCoordinate : chunks) { + final Chunk chunk = instance.getChunk(chunkCoordinate.chunkX, chunkCoordinate.chunkZ); + if (ChunkUtils.isChunkUnloaded(chunk)) { + continue; + } updateChunk(instance, chunk, time); updateEntities(instance, chunk, time); - }*/ + } }); } } + /*@Override + public void linkThread(Instance instance, Chunk chunk) { + startChunkQuery(instance, chunk.getChunkX(), chunk.getChunkZ()); + }*/ + /** * Check the four chunk neighbors (up/down/left/right) * and add them to the cache list @@ -81,7 +82,7 @@ public class PerGroupChunkProvider extends ThreadProvider { * @param chunkX the chunk X * @param chunkZ the chunk Z */ - private void startChunkQuery(Instance instance, int chunkX, int chunkZ) { + /*private void startChunkQuery(Instance instance, int chunkX, int chunkZ) { // Constants used to loop through the neighbors final int[] posX = {1, 0, -1}; final int[] posZ = {1, 0, -1}; @@ -124,6 +125,5 @@ public class PerGroupChunkProvider extends ThreadProvider { this.cachedChunks.put(cachedChunk, cache); } this.instanceMap.put(cache, instance); - } - + }*/ } diff --git a/src/main/java/net/minestom/server/thread/PerInstanceThreadProvider.java b/src/main/java/net/minestom/server/thread/PerInstanceThreadProvider.java index baed22b9b..7e96f5bca 100644 --- a/src/main/java/net/minestom/server/thread/PerInstanceThreadProvider.java +++ b/src/main/java/net/minestom/server/thread/PerInstanceThreadProvider.java @@ -2,96 +2,56 @@ package net.minestom.server.thread; 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.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 groupMap = new HashMap<>(); + private Map> instanceChunkMap = new HashMap<>(); @Override - public void start() { - this.groupMap.clear(); + public void onChunkLoad(Instance instance, int chunkX, int chunkZ) { + Set chunkCoordinates = instanceChunkMap.computeIfAbsent(instance, inst -> new HashSet<>()); + chunkCoordinates.add(new ChunkCoordinate(chunkX, chunkZ)); } @Override - public void linkThread(Instance instance, Chunk chunk) { - InstanceChunk instanceChunk = new InstanceChunk(instance, chunk); + public void onChunkUnload(Instance instance, int chunkX, int chunkZ) { + Set chunkCoordinates = instanceChunkMap.computeIfAbsent(instance, inst -> new HashSet<>()); - GroupedInstanceChunk groupedInstanceChunk = groupMap.computeIfAbsent(instance, inst -> new GroupedInstanceChunk()); - groupedInstanceChunk.instanceChunks.add(instanceChunk); - } - - @Override - public void end() { + chunkCoordinates.removeIf(chunkCoordinate -> chunkCoordinate.chunkX == chunkX && + chunkCoordinate.chunkZ == chunkZ); } @Override - public void update() { - final long time = System.currentTimeMillis(); - - for (Map.Entry entry : groupMap.entrySet()) { + public void update(long time) { + for (Map.Entry> entry : instanceChunkMap.entrySet()) { final Instance instance = entry.getKey(); - final GroupedInstanceChunk groupedInstanceChunk = entry.getValue(); + final Set chunkCoordinates = entry.getValue(); pool.execute(() -> { updateInstance(instance, time); - for (InstanceChunk instanceChunk : groupedInstanceChunk.instanceChunks) { - Chunk chunk = instanceChunk.getChunk(); + for (ChunkCoordinate chunkCoordinate : chunkCoordinates) { + final Chunk chunk = instance.getChunk(chunkCoordinate.chunkX, chunkCoordinate.chunkZ); + if (ChunkUtils.isChunkUnloaded(chunk)) + continue; updateChunk(instance, chunk, time); updateEntities(instance, chunk, time); + } }); - } - } - /** - * Contains a list of {@link InstanceChunk} - */ - private static class GroupedInstanceChunk { - private List instanceChunks = new ArrayList<>(); - } - - /** - * Contains both a chunk and its instance - */ - private static class InstanceChunk { - - private Instance instance; - private Chunk chunk; - - protected InstanceChunk(Instance instance, Chunk chunk) { - this.instance = instance; - this.chunk = chunk; - } - - public Instance getInstance() { - return instance; - } - - public Chunk getChunk() { - return chunk; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - InstanceChunk that = (InstanceChunk) o; - return Objects.equals(instance, that.instance) && - Objects.equals(chunk, that.chunk); - } - - @Override - public int hashCode() { - return Objects.hash(instance, chunk); } } diff --git a/src/main/java/net/minestom/server/thread/SingleThreadProvider.java b/src/main/java/net/minestom/server/thread/SingleThreadProvider.java deleted file mode 100644 index 4468a7d45..000000000 --- a/src/main/java/net/minestom/server/thread/SingleThreadProvider.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.minestom.server.thread; - -import net.minestom.server.instance.Chunk; -import net.minestom.server.instance.Instance; - -import java.util.HashSet; -import java.util.Set; - -public class SingleThreadProvider extends ThreadProvider { - - private Set instances = new HashSet<>(); - private long time; - - @Override - public void start() { - this.instances.clear(); - this.time = System.currentTimeMillis(); - } - - @Override - public void linkThread(Instance instance, Chunk chunk) { - - if (instances.add(instance)) { - updateInstance(instance, time); - } - - updateChunk(instance, chunk, time); - - updateEntities(instance, chunk, time); - } - - @Override - public void end() { - - } - - @Override - public void update() { - - } -} diff --git a/src/main/java/net/minestom/server/thread/ThreadProvider.java b/src/main/java/net/minestom/server/thread/ThreadProvider.java index 326a32a23..6756e178d 100644 --- a/src/main/java/net/minestom/server/thread/ThreadProvider.java +++ b/src/main/java/net/minestom/server/thread/ThreadProvider.java @@ -31,27 +31,29 @@ public abstract class ThreadProvider { } /** - * Called to prepare the thread provider, to provide threads for the next server tick - */ - public abstract void start(); - - /** - * Assign a thread to a chunk, create one if none is defined + * Called when a chunk is loaded * - * @param instance the instance where the chunk is - * @param chunk the chunk which should get an assigned thread + * @param instance the instance of the chunk + * @param chunkX the chunk X + * @param chunkZ the chunk Z */ - public abstract void linkThread(Instance instance, Chunk chunk); + public abstract void onChunkLoad(Instance instance, int chunkX, int chunkZ); /** - * Inform the server that all chunks have been assigned to a thread + * Called when a chunk is unloaded + * + * @param instance the instance of the chunk + * @param chunkX the chunk X + * @param chunkZ the chunk Z */ - public abstract void end(); + public abstract void onChunkUnload(Instance instance, int chunkX, int chunkZ); /** * Perform a server tick for all chunks based on their linked thread + * + * @param time the update time in milliseconds */ - public abstract void update(); + public abstract void update(long time); /** * Get the current size of the thread pool @@ -180,4 +182,13 @@ public abstract class ThreadProvider { } } + protected static class ChunkCoordinate { + public int chunkX, chunkZ; + + public ChunkCoordinate(int chunkX, int chunkZ) { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + } + } + }