From 5bbf4642e8eed241d8b6ca999437cb4f907dab97 Mon Sep 17 00:00:00 2001 From: TheMode Date: Wed, 5 Jan 2022 09:01:21 +0100 Subject: [PATCH] Server process (#550) --- .../java/net/minestom/demo/PlayerInit.java | 3 +- .../net/minestom/server/MinecraftServer.java | 357 +++--------------- .../net/minestom/server/ServerProcess.java | 158 ++++++++ .../minestom/server/ServerProcessImpl.java | 324 ++++++++++++++++ .../net/minestom/server/UpdateManager.java | 249 ------------ .../net/minestom/server/entity/Entity.java | 4 +- .../minestom/server/event/GlobalHandles.java | 3 + .../event/server/ServerTickMonitorEvent.java | 17 + .../server/extensions/ExtensionManager.java | 25 +- .../minestom/server/extras/MojangAuth.java | 2 +- .../minestom/server/instance/Instance.java | 6 +- .../server/instance/InstanceContainer.java | 7 +- .../server/instance/InstanceManager.java | 6 +- .../manager/PacketListenerManager.java | 11 +- .../server/scoreboard/TeamManager.java | 4 - .../server/terminal/MinestomTerminal.java | 5 +- .../server/thread/TickSchedulerThread.java | 31 ++ .../minestom/server/utils/PacketUtils.java | 4 +- .../minestom/server/ServerProcessTest.java | 30 ++ 19 files changed, 652 insertions(+), 594 deletions(-) create mode 100644 src/main/java/net/minestom/server/ServerProcess.java create mode 100644 src/main/java/net/minestom/server/ServerProcessImpl.java delete mode 100644 src/main/java/net/minestom/server/UpdateManager.java create mode 100644 src/main/java/net/minestom/server/event/server/ServerTickMonitorEvent.java create mode 100644 src/main/java/net/minestom/server/thread/TickSchedulerThread.java create mode 100644 src/test/java/net/minestom/server/ServerProcessTest.java diff --git a/demo/src/main/java/net/minestom/demo/PlayerInit.java b/demo/src/main/java/net/minestom/demo/PlayerInit.java index d370184d7..850fbfe79 100644 --- a/demo/src/main/java/net/minestom/demo/PlayerInit.java +++ b/demo/src/main/java/net/minestom/demo/PlayerInit.java @@ -21,6 +21,7 @@ import net.minestom.server.event.player.PlayerDeathEvent; import net.minestom.server.event.player.PlayerDisconnectEvent; import net.minestom.server.event.player.PlayerLoginEvent; import net.minestom.server.event.player.PlayerSpawnEvent; +import net.minestom.server.event.server.ServerTickMonitorEvent; import net.minestom.server.instance.Instance; import net.minestom.server.instance.InstanceContainer; import net.minestom.server.instance.InstanceManager; @@ -133,7 +134,7 @@ public class PlayerInit { var eventHandler = MinecraftServer.getGlobalEventHandler(); eventHandler.addChild(DEMO_NODE); - MinecraftServer.getUpdateManager().addTickMonitor(LAST_TICK::set); + eventHandler.addListener(ServerTickMonitorEvent.class, event -> LAST_TICK.set(event.getTickMonitor())); BenchmarkManager benchmarkManager = MinecraftServer.getBenchmarkManager(); MinecraftServer.getSchedulerManager().buildTask(() -> { diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 88fd7e333..0cfae7292 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -4,17 +4,12 @@ import net.minestom.server.advancements.AdvancementManager; import net.minestom.server.adventure.bossbar.BossBarManager; import net.minestom.server.command.CommandManager; import net.minestom.server.data.DataManager; -import net.minestom.server.data.DataType; -import net.minestom.server.data.SerializableData; import net.minestom.server.event.GlobalEventHandler; import net.minestom.server.exception.ExceptionManager; -import net.minestom.server.extensions.Extension; import net.minestom.server.extensions.ExtensionManager; -import net.minestom.server.fluid.Fluid; import net.minestom.server.gamedata.tags.TagManager; import net.minestom.server.instance.InstanceManager; import net.minestom.server.instance.block.BlockManager; -import net.minestom.server.instance.block.rule.BlockPlacementRule; import net.minestom.server.listener.manager.PacketListenerManager; import net.minestom.server.monitoring.BenchmarkManager; import net.minestom.server.network.ConnectionManager; @@ -25,10 +20,8 @@ import net.minestom.server.network.socket.Server; import net.minestom.server.ping.ResponseDataConsumer; import net.minestom.server.recipe.RecipeManager; import net.minestom.server.scoreboard.TeamManager; -import net.minestom.server.storage.StorageLocation; import net.minestom.server.storage.StorageManager; -import net.minestom.server.terminal.MinestomTerminal; -import net.minestom.server.thread.MinestomThreadPool; +import net.minestom.server.thread.TickSchedulerThread; import net.minestom.server.timer.SchedulerManager; import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.PacketUtils; @@ -36,8 +29,10 @@ import net.minestom.server.utils.validate.Check; import net.minestom.server.world.Difficulty; import net.minestom.server.world.DimensionTypeManager; import net.minestom.server.world.biomes.BiomeManager; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -75,40 +70,9 @@ public final class MinecraftServer { // Network monitoring private static int rateLimit = 300; private static int maxPacketSize = 30_000; - // Network - private static PacketListenerManager packetListenerManager; - private static PacketProcessor packetProcessor; - private static Server server; - - private static ExceptionManager exceptionManager; // In-Game Manager - private static ConnectionManager connectionManager; - private static InstanceManager instanceManager; - private static BlockManager blockManager; - private static CommandManager commandManager; - private static RecipeManager recipeManager; - private static StorageManager storageManager; - private static DataManager dataManager; - private static TeamManager teamManager; - private static SchedulerManager schedulerManager; - private static BenchmarkManager benchmarkManager; - private static DimensionTypeManager dimensionTypeManager; - private static BiomeManager biomeManager; - private static AdvancementManager advancementManager; - private static BossBarManager bossBarManager; - - private static ExtensionManager extensionManager; - - private static final GlobalEventHandler GLOBAL_EVENT_HANDLER = new GlobalEventHandler(); - - private static UpdateManager updateManager; - private static MinecraftServer minecraftServer; - - // Data - private static boolean initialized; - private static boolean started; - private static volatile boolean stopping; + private static volatile ServerProcess serverProcess; private static int chunkViewDistance = Integer.getInteger("minestom.chunk-view-distance", 8); private static int entityViewDistance = Integer.getInteger("minestom.entity-view-distance", 5); @@ -117,57 +81,22 @@ public final class MinecraftServer { private static ResponseDataConsumer responseDataConsumer; private static String brandName = "Minestom"; private static Difficulty difficulty = Difficulty.NORMAL; - private static TagManager tagManager; public static MinecraftServer init() { - if (minecraftServer != null) // don't init twice - return minecraftServer; - - // Initialize the ExceptionManager at first - exceptionManager = new ExceptionManager(); - - extensionManager = new ExtensionManager(); - - // warmup/force-init registries - // without this line, registry types that are not loaded explicitly will have an internal empty registry in Registries - // That can happen with PotionType for instance, if no code tries to access a PotionType field - // TODO: automate (probably with code generation) - Fluid.values(); - - connectionManager = new ConnectionManager(); - // Networking - packetProcessor = new PacketProcessor(); - packetListenerManager = new PacketListenerManager(); - - instanceManager = new InstanceManager(); - blockManager = new BlockManager(); - commandManager = new CommandManager(); - recipeManager = new RecipeManager(); - storageManager = new StorageManager(); - dataManager = new DataManager(); - teamManager = new TeamManager(); - schedulerManager = new SchedulerManager(); - benchmarkManager = new BenchmarkManager(); - dimensionTypeManager = new DimensionTypeManager(); - biomeManager = new BiomeManager(); - advancementManager = new AdvancementManager(); - bossBarManager = new BossBarManager(); - - updateManager = new UpdateManager(); - - tagManager = new TagManager(); + updateProcess(); + return new MinecraftServer(); + } + @ApiStatus.Internal + public static ServerProcess updateProcess() { + ServerProcess process; try { - server = new Server(packetProcessor); + process = new ServerProcessImpl(); + serverProcess = process; } catch (IOException e) { - e.printStackTrace(); + throw new RuntimeException(e); } - - initialized = true; - - minecraftServer = new MinecraftServer(); - - return minecraftServer; + return process; } /** @@ -247,108 +176,51 @@ public final class MinecraftServer { PacketUtils.broadcastPacket(new ServerDifficultyPacket(difficulty, true)); } - /** - * Gets the global event handler. - *

- * Used to register event callback at a global scale. - * - * @return the global event handler - */ + @ApiStatus.Experimental + public static @UnknownNullability ServerProcess process() { + return serverProcess; + } + public static @NotNull GlobalEventHandler getGlobalEventHandler() { - return GLOBAL_EVENT_HANDLER; + return serverProcess.eventHandler(); } - /** - * Gets the manager handling all incoming packets - * - * @return the packet listener manager - */ public static PacketListenerManager getPacketListenerManager() { - checkInitStatus(packetListenerManager); - return packetListenerManager; + return serverProcess.packetListener(); } - /** - * Gets the manager handling all registered instances. - * - * @return the instance manager - */ public static InstanceManager getInstanceManager() { - checkInitStatus(instanceManager); - return instanceManager; + return serverProcess.instance(); } - /** - * Gets the manager handling {@link net.minestom.server.instance.block.BlockHandler block handlers} - * and {@link BlockPlacementRule placement rules}. - * - * @return the block manager - */ public static BlockManager getBlockManager() { - checkInitStatus(blockManager); - return blockManager; + return serverProcess.block(); } - /** - * Gets the manager handling commands. - * - * @return the command manager - */ public static CommandManager getCommandManager() { - checkInitStatus(commandManager); - return commandManager; + return serverProcess.command(); } - /** - * Gets the manager handling recipes show to the clients. - * - * @return the recipe manager - */ public static RecipeManager getRecipeManager() { - checkInitStatus(recipeManager); - return recipeManager; + return serverProcess.recipe(); } - /** - * Gets the manager handling storage. - * - * @return the storage manager - */ @Deprecated public static StorageManager getStorageManager() { - checkInitStatus(storageManager); - return storageManager; + return serverProcess.storage(); } - /** - * Gets the manager handling {@link DataType} used by {@link SerializableData}. - * - * @return the data manager - */ @Deprecated public static DataManager getDataManager() { - checkInitStatus(dataManager); - return dataManager; + return serverProcess.data(); } - /** - * Gets the manager handling teams. - * - * @return the team manager - */ public static TeamManager getTeamManager() { - checkInitStatus(teamManager); - return teamManager; + return serverProcess.team(); } - /** - * Gets the manager handling scheduled tasks. - * - * @return the scheduler manager - */ public static SchedulerManager getSchedulerManager() { - checkInitStatus(schedulerManager); - return schedulerManager; + return serverProcess.scheduler(); } /** @@ -357,68 +229,31 @@ public final class MinecraftServer { * @return the benchmark manager */ public static BenchmarkManager getBenchmarkManager() { - checkInitStatus(benchmarkManager); - return benchmarkManager; + return serverProcess.benchmark(); } - /** - * Gets the exception manager for exception handling. - * - * @return the exception manager - */ public static ExceptionManager getExceptionManager() { - checkInitStatus(exceptionManager); - return exceptionManager; + return serverProcess.exception(); } - /** - * Gets the manager handling server connections. - * - * @return the connection manager - */ public static ConnectionManager getConnectionManager() { - checkInitStatus(connectionManager); - return connectionManager; + return serverProcess.connection(); } - /** - * Gets the boss bar manager. - * - * @return the boss bar manager - */ public static BossBarManager getBossBarManager() { - checkInitStatus(bossBarManager); - return bossBarManager; + return serverProcess.bossBar(); } - /** - * Gets the object handling the client packets processing. - *

- * Can be used if you want to convert a buffer to a client packet object. - * - * @return the packet processor - */ public static PacketProcessor getPacketProcessor() { - checkInitStatus(packetProcessor); - return packetProcessor; + return serverProcess.packetProcessor(); } - /** - * Gets if the server is up and running. - * - * @return true if the server is started - */ public static boolean isStarted() { - return started; + return serverProcess.isAlive(); } - /** - * Gets if the server is currently being shutdown using {@link #stopCleanly()}. - * - * @return true if the server is being stopped - */ public static boolean isStopping() { - return stopping; + return !isStarted(); } /** @@ -439,7 +274,7 @@ public final class MinecraftServer { */ @Deprecated public static void setChunkViewDistance(int chunkViewDistance) { - Check.stateCondition(started, "You cannot change the chunk view distance after the server has been started."); + Check.stateCondition(serverProcess.isAlive(), "You cannot change the chunk view distance after the server has been started."); Check.argCondition(!MathUtils.isBetween(chunkViewDistance, 2, 32), "The chunk view distance must be between 2 and 32"); MinecraftServer.chunkViewDistance = chunkViewDistance; @@ -463,7 +298,7 @@ public final class MinecraftServer { */ @Deprecated public static void setEntityViewDistance(int entityViewDistance) { - Check.stateCondition(started, "You cannot change the entity view distance after the server has been started."); + Check.stateCondition(serverProcess.isAlive(), "You cannot change the entity view distance after the server has been started."); Check.argCondition(!MathUtils.isBetween(entityViewDistance, 0, 32), "The entity view distance must be between 0 and 32"); MinecraftServer.entityViewDistance = entityViewDistance; @@ -487,7 +322,7 @@ public final class MinecraftServer { * @throws IllegalStateException if this is called after the server started */ public static void setCompressionThreshold(int compressionThreshold) { - Check.stateCondition(started, "The compression threshold cannot be changed after the server has been started."); + Check.stateCondition(serverProcess.isAlive(), "The compression threshold cannot be changed after the server has been started."); MinecraftServer.compressionThreshold = compressionThreshold; } @@ -506,7 +341,7 @@ public final class MinecraftServer { * @param enabled true to enable, false to disable */ public static void setTerminalEnabled(boolean enabled) { - Check.stateCondition(started, "Terminal settings may not be changed after starting the server."); + Check.stateCondition(serverProcess.isAlive(), "Terminal settings may not be changed after starting the server."); MinecraftServer.terminalEnabled = enabled; } @@ -518,73 +353,31 @@ public final class MinecraftServer { */ @Deprecated public static ResponseDataConsumer getResponseDataConsumer() { - checkInitStatus(responseDataConsumer); return responseDataConsumer; } - /** - * Gets the manager handling dimensions. - * - * @return the dimension manager - */ public static DimensionTypeManager getDimensionTypeManager() { - checkInitStatus(dimensionTypeManager); - return dimensionTypeManager; + return serverProcess.dimension(); } - /** - * Gets the manager handling biomes. - * - * @return the biome manager - */ public static BiomeManager getBiomeManager() { - checkInitStatus(biomeManager); - return biomeManager; + return serverProcess.biome(); } - /** - * Gets the manager handling advancements. - * - * @return the advancement manager - */ public static AdvancementManager getAdvancementManager() { - checkInitStatus(advancementManager); - return advancementManager; + return serverProcess.advancement(); } - /** - * Get the manager handling {@link Extension}. - * - * @return the extension manager - */ public static ExtensionManager getExtensionManager() { - checkInitStatus(extensionManager); - return extensionManager; + return serverProcess.extension(); } - /** - * Gets the manager handling tags. - * - * @return the tag manager - */ public static TagManager getTagManager() { - checkInitStatus(tagManager); - return tagManager; - } - - /** - * Gets the manager handling the server ticks. - * - * @return the update manager - */ - public static UpdateManager getUpdateManager() { - checkInitStatus(updateManager); - return updateManager; + return serverProcess.tag(); } public static Server getServer() { - checkInitStatus(server); - return server; + return serverProcess.server(); } /** @@ -614,67 +407,15 @@ public final class MinecraftServer { * @throws IllegalStateException if called before {@link #init()} or if the server is already running */ public void start(@NotNull String address, int port) { - Check.stateCondition(!initialized, "#start can only be called after #init"); - Check.stateCondition(started, "The server is already started"); - - extensionManager.start(); - extensionManager.gotoPreInit(); - - MinecraftServer.started = true; - - LOGGER.info("Starting Minestom server."); - - updateManager.start(); - - extensionManager.gotoInit(); - - // Init server - try { - server.init(new InetSocketAddress(address, port)); - } catch (IOException e) { - e.printStackTrace(); - } - - // Start server - server.start(); - - extensionManager.gotoPostInit(); - - LOGGER.info("Minestom server started successfully."); - - if (terminalEnabled) { - MinestomTerminal.start(); - } - - // Stop the server on SIGINT - Runtime.getRuntime().addShutdownHook(new Thread(MinecraftServer::stopCleanly)); + serverProcess.start(new InetSocketAddress(address, port)); + new TickSchedulerThread(serverProcess).start(); } /** * Stops this server properly (saves if needed, kicking players, etc.) */ public static void stopCleanly() { - if (stopping) return; - stopping = true; - LOGGER.info("Stopping Minestom server."); - LOGGER.info("Unloading all extensions."); - extensionManager.shutdown(); - updateManager.stop(); - schedulerManager.shutdown(); - connectionManager.shutdown(); - server.stop(); - storageManager.getLoadedLocations().forEach(StorageLocation::close); - LOGGER.info("Shutting down all thread pools."); - benchmarkManager.disable(); - MinestomTerminal.stop(); - MinestomThreadPool.shutdownAll(); - LOGGER.info("Minestom server stopped successfully."); - } - - private static void checkInitStatus(@Nullable Object object) { - /*Check.stateCondition(Objects.isNull(object), - "You cannot access the manager before MinecraftServer#init, " + - "if you are developing an extension be sure to retrieve them at least after Extension#preInitialize");*/ + serverProcess.stop(); } private static int getThreadCount(@NotNull String property, int count) { diff --git a/src/main/java/net/minestom/server/ServerProcess.java b/src/main/java/net/minestom/server/ServerProcess.java new file mode 100644 index 000000000..644607e27 --- /dev/null +++ b/src/main/java/net/minestom/server/ServerProcess.java @@ -0,0 +1,158 @@ +package net.minestom.server; + +import net.minestom.server.advancements.AdvancementManager; +import net.minestom.server.adventure.bossbar.BossBarManager; +import net.minestom.server.command.CommandManager; +import net.minestom.server.data.DataManager; +import net.minestom.server.event.GlobalEventHandler; +import net.minestom.server.exception.ExceptionManager; +import net.minestom.server.extensions.ExtensionManager; +import net.minestom.server.gamedata.tags.TagManager; +import net.minestom.server.instance.Chunk; +import net.minestom.server.instance.InstanceManager; +import net.minestom.server.instance.block.BlockManager; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.server.listener.manager.PacketListenerManager; +import net.minestom.server.monitoring.BenchmarkManager; +import net.minestom.server.network.ConnectionManager; +import net.minestom.server.network.PacketProcessor; +import net.minestom.server.network.socket.Server; +import net.minestom.server.recipe.RecipeManager; +import net.minestom.server.scoreboard.TeamManager; +import net.minestom.server.storage.StorageManager; +import net.minestom.server.thread.ThreadDispatcher; +import net.minestom.server.timer.SchedulerManager; +import net.minestom.server.world.DimensionTypeManager; +import net.minestom.server.world.biomes.BiomeManager; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.net.SocketAddress; + +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface ServerProcess { + /** + * Handles incoming connections/players. + */ + @NotNull ConnectionManager connection(); + + /** + * Handles registered instances. + */ + @NotNull InstanceManager instance(); + + /** + * Handles {@link net.minestom.server.instance.block.BlockHandler block handlers} + * and {@link BlockPlacementRule placement rules}. + */ + @NotNull BlockManager block(); + + /** + * Handles registered commands. + */ + @NotNull CommandManager command(); + + /** + * Handles registered recipes shown to clients. + */ + @NotNull RecipeManager recipe(); + + /** + * Handles registered teams. + */ + @NotNull TeamManager team(); + + /** + * Gets the global event handler. + *

+ * Used to register event callback at a global scale. + */ + @NotNull GlobalEventHandler eventHandler(); + + /** + * Main scheduler ticked at the server rate. + */ + @NotNull SchedulerManager scheduler(); + + @NotNull BenchmarkManager benchmark(); + + /** + * Handles registered dimensions. + */ + @NotNull DimensionTypeManager dimension(); + + /** + * Handles registered biomes. + */ + @NotNull BiomeManager biome(); + + /** + * Handles registered advancements. + */ + @NotNull AdvancementManager advancement(); + + /** + * Handles registered boss bars. + */ + @NotNull BossBarManager bossBar(); + + /** + * Loads and handle extensions. + */ + @NotNull ExtensionManager extension(); + + /** + * Handles registry tags. + */ + @NotNull TagManager tag(); + + /** + * Handles all thrown exceptions from the server. + */ + @NotNull ExceptionManager exception(); + + /** + * Handles incoming packets. + */ + @NotNull PacketListenerManager packetListener(); + + /** + * Gets the object handling the client packets processing. + *

+ * Can be used if you want to convert a buffer to a client packet object. + */ + @NotNull PacketProcessor packetProcessor(); + + /** + * Exposed socket server. + */ + @NotNull Server server(); + + /** + * Dispatcher for tickable game objects. + */ + @NotNull ThreadDispatcher dispatcher(); + + /** + * Handles the server ticks. + */ + @NotNull Ticker ticker(); + + void start(@NotNull SocketAddress socketAddress); + + void stop(); + + boolean isAlive(); + + @ApiStatus.NonExtendable + interface Ticker { + void tick(long nanoTime); + } + + @Deprecated + @NotNull StorageManager storage(); + + @Deprecated + @NotNull DataManager data(); +} diff --git a/src/main/java/net/minestom/server/ServerProcessImpl.java b/src/main/java/net/minestom/server/ServerProcessImpl.java new file mode 100644 index 000000000..75e679b69 --- /dev/null +++ b/src/main/java/net/minestom/server/ServerProcessImpl.java @@ -0,0 +1,324 @@ +package net.minestom.server; + +import net.minestom.server.acquirable.Acquirable; +import net.minestom.server.advancements.AdvancementManager; +import net.minestom.server.adventure.bossbar.BossBarManager; +import net.minestom.server.command.CommandManager; +import net.minestom.server.data.DataManager; +import net.minestom.server.event.GlobalEventHandler; +import net.minestom.server.event.GlobalHandles; +import net.minestom.server.event.server.ServerTickMonitorEvent; +import net.minestom.server.exception.ExceptionManager; +import net.minestom.server.extensions.ExtensionManager; +import net.minestom.server.gamedata.tags.TagManager; +import net.minestom.server.instance.Chunk; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.InstanceManager; +import net.minestom.server.instance.block.BlockManager; +import net.minestom.server.listener.manager.PacketListenerManager; +import net.minestom.server.monitoring.BenchmarkManager; +import net.minestom.server.monitoring.TickMonitor; +import net.minestom.server.network.ConnectionManager; +import net.minestom.server.network.PacketProcessor; +import net.minestom.server.network.socket.Server; +import net.minestom.server.network.socket.Worker; +import net.minestom.server.recipe.RecipeManager; +import net.minestom.server.scoreboard.TeamManager; +import net.minestom.server.storage.StorageLocation; +import net.minestom.server.storage.StorageManager; +import net.minestom.server.terminal.MinestomTerminal; +import net.minestom.server.thread.MinestomThreadPool; +import net.minestom.server.thread.ThreadDispatcher; +import net.minestom.server.timer.SchedulerManager; +import net.minestom.server.utils.PacketUtils; +import net.minestom.server.world.DimensionTypeManager; +import net.minestom.server.world.biomes.BiomeManager; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.SocketAddress; +import java.util.concurrent.atomic.AtomicBoolean; + +final class ServerProcessImpl implements ServerProcess { + private final static Logger LOGGER = LoggerFactory.getLogger(ServerProcessImpl.class); + + private final ExceptionManager exception; + private final ExtensionManager extension; + private final ConnectionManager connection; + private final PacketProcessor packetProcessor; + private final PacketListenerManager packetListener; + private final InstanceManager instance; + private final BlockManager block; + private final CommandManager command; + private final RecipeManager recipe; + private final StorageManager storage; + private final DataManager data; + private final TeamManager team; + private final GlobalEventHandler eventHandler; + private final SchedulerManager scheduler; + private final BenchmarkManager benchmark; + private final DimensionTypeManager dimension; + private final BiomeManager biome; + private final AdvancementManager advancement; + private final BossBarManager bossBar; + private final TagManager tag; + private final Server server; + + private final ThreadDispatcher dispatcher; + private final Ticker ticker; + + private boolean terminalEnabled = System.getProperty("minestom.terminal.disabled") == null; + private final AtomicBoolean started = new AtomicBoolean(); + private final AtomicBoolean stopped = new AtomicBoolean(); + + public ServerProcessImpl() throws IOException { + this.exception = new ExceptionManager(); + this.extension = new ExtensionManager(this); + this.connection = new ConnectionManager(); + this.packetProcessor = new PacketProcessor(); + this.packetListener = new PacketListenerManager(this); + this.instance = new InstanceManager(); + this.block = new BlockManager(); + this.command = new CommandManager(); + this.recipe = new RecipeManager(); + this.storage = new StorageManager(); + this.data = new DataManager(); + this.team = new TeamManager(); + this.eventHandler = new GlobalEventHandler(); + this.scheduler = new SchedulerManager(); + this.benchmark = new BenchmarkManager(); + this.dimension = new DimensionTypeManager(); + this.biome = new BiomeManager(); + this.advancement = new AdvancementManager(); + this.bossBar = new BossBarManager(); + this.tag = new TagManager(); + this.server = new Server(packetProcessor); + + this.dispatcher = ThreadDispatcher.singleThread(); + this.ticker = new TickerImpl(); + } + + @Override + public @NotNull ConnectionManager connection() { + return connection; + } + + @Override + public @NotNull InstanceManager instance() { + return instance; + } + + @Override + public @NotNull BlockManager block() { + return block; + } + + @Override + public @NotNull CommandManager command() { + return command; + } + + @Override + public @NotNull RecipeManager recipe() { + return recipe; + } + + @Override + public @NotNull StorageManager storage() { + return storage; + } + + @Override + public @NotNull DataManager data() { + return data; + } + + @Override + public @NotNull TeamManager team() { + return team; + } + + @Override + public @NotNull GlobalEventHandler eventHandler() { + return eventHandler; + } + + @Override + public @NotNull SchedulerManager scheduler() { + return scheduler; + } + + @Override + public @NotNull BenchmarkManager benchmark() { + return benchmark; + } + + @Override + public @NotNull DimensionTypeManager dimension() { + return dimension; + } + + @Override + public @NotNull BiomeManager biome() { + return biome; + } + + @Override + public @NotNull AdvancementManager advancement() { + return advancement; + } + + @Override + public @NotNull BossBarManager bossBar() { + return bossBar; + } + + @Override + public @NotNull ExtensionManager extension() { + return extension; + } + + @Override + public @NotNull TagManager tag() { + return tag; + } + + @Override + public @NotNull ExceptionManager exception() { + return exception; + } + + @Override + public @NotNull PacketListenerManager packetListener() { + return packetListener; + } + + @Override + public @NotNull PacketProcessor packetProcessor() { + return packetProcessor; + } + + @Override + public @NotNull Server server() { + return server; + } + + @Override + public @NotNull ThreadDispatcher dispatcher() { + return dispatcher; + } + + @Override + public @NotNull Ticker ticker() { + return ticker; + } + + @Override + public void start(@NotNull SocketAddress socketAddress) { + if (!started.compareAndSet(false, true)) { + throw new IllegalStateException("Server already started"); + } + + extension.start(); + extension.gotoPreInit(); + + LOGGER.info("Starting Minestom server."); + + extension.gotoInit(); + + // Init server + try { + server.init(socketAddress); + } catch (IOException e) { + exception.handleException(e); + throw new RuntimeException(e); + } + + // Start server + server.start(); + + extension.gotoPostInit(); + + LOGGER.info("Minestom server started successfully."); + + if (terminalEnabled) { + MinestomTerminal.start(); + } + // Stop the server on SIGINT + Runtime.getRuntime().addShutdownHook(new Thread(this::stop)); + } + + @Override + public void stop() { + if (!stopped.compareAndSet(false, true)) + return; + LOGGER.info("Stopping Minestom server."); + LOGGER.info("Unloading all extensions."); + extension.shutdown(); + scheduler.shutdown(); + connection.shutdown(); + server.stop(); + storage.getLoadedLocations().forEach(StorageLocation::close); + LOGGER.info("Shutting down all thread pools."); + benchmark.disable(); + MinestomTerminal.stop(); + MinestomThreadPool.shutdownAll(); + dispatcher.shutdown(); + LOGGER.info("Minestom server stopped successfully."); + } + + @Override + public boolean isAlive() { + return started.get() && !stopped.get(); + } + + private final class TickerImpl implements Ticker { + @Override + public void tick(long nanoTime) { + final long msTime = System.currentTimeMillis(); + + scheduler().processTick(); + + // Waiting players update (newly connected clients waiting to get into the server) + connection().updateWaitingPlayers(); + + // Keep Alive Handling + connection().handleKeepAlive(msTime); + + // Server tick (chunks/entities) + serverTick(msTime); + + // Flush all waiting packets + PacketUtils.flush(); + server().workers().forEach(Worker::flush); + + // Monitoring + { + final double acquisitionTimeMs = Acquirable.getAcquiringTime() / 1e6D; + final double tickTimeMs = (System.nanoTime() - nanoTime) / 1e6D; + final TickMonitor tickMonitor = new TickMonitor(tickTimeMs, acquisitionTimeMs); + GlobalHandles.SERVER_TICK_MONITOR_HANDLE.call(new ServerTickMonitorEvent(tickMonitor)); + Acquirable.resetAcquiringTime(); + } + } + + private void serverTick(long tickStart) { + // Tick all instances + for (Instance instance : instance().getInstances()) { + try { + instance.tick(tickStart); + } catch (Exception e) { + exception().handleException(e); + } + } + // Tick all chunks (and entities inside) + dispatcher().updateAndAwait(tickStart); + + // Clear removed entities & update threads + final long tickTime = System.currentTimeMillis() - tickStart; + dispatcher().refreshThreads(tickTime); + } + } +} diff --git a/src/main/java/net/minestom/server/UpdateManager.java b/src/main/java/net/minestom/server/UpdateManager.java deleted file mode 100644 index 0e0189577..000000000 --- a/src/main/java/net/minestom/server/UpdateManager.java +++ /dev/null @@ -1,249 +0,0 @@ -package net.minestom.server; - -import net.minestom.server.acquirable.Acquirable; -import net.minestom.server.instance.Chunk; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.InstanceManager; -import net.minestom.server.monitoring.TickMonitor; -import net.minestom.server.network.ConnectionManager; -import net.minestom.server.network.socket.Worker; -import net.minestom.server.thread.MinestomThread; -import net.minestom.server.thread.ThreadDispatcher; -import net.minestom.server.timer.SchedulerManager; -import net.minestom.server.utils.PacketUtils; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.locks.LockSupport; -import java.util.function.Consumer; -import java.util.function.LongConsumer; - -/** - * Manager responsible for the server ticks. - *

- * The {@link ThreadDispatcher} manages the multi-thread aspect of chunk ticks. - */ -public final class UpdateManager { - private volatile boolean stopRequested; - - // TODO make configurable - private final ThreadDispatcher threadDispatcher = ThreadDispatcher.singleThread(); - - private final Queue tickStartCallbacks = new ConcurrentLinkedQueue<>(); - private final Queue tickEndCallbacks = new ConcurrentLinkedQueue<>(); - private final List> tickMonitors = new CopyOnWriteArrayList<>(); - - /** - * Should only be created in MinecraftServer. - */ - UpdateManager() { - } - - /** - * Starts the server loop in the update thread. - */ - void start() { - new TickSchedulerThread().start(); - } - - /** - * Gets the current {@link ThreadDispatcher}. - * - * @return the current thread provider - */ - public @NotNull ThreadDispatcher getThreadProvider() { - return threadDispatcher; - } - - /** - * Signals the {@link ThreadDispatcher} that an instance has been created. - *

- * WARNING: should be automatically done by the {@link InstanceManager}. - * - * @param instance the instance - */ - public void signalInstanceCreate(Instance instance) { - instance.getChunks().forEach(this::signalChunkLoad); - } - - /** - * Signals the {@link ThreadDispatcher} that an instance has been deleted. - *

- * WARNING: should be automatically done by the {@link InstanceManager}. - * - * @param instance the instance - */ - public void signalInstanceDelete(Instance instance) { - instance.getChunks().forEach(this::signalChunkUnload); - } - - /** - * Signals the {@link ThreadDispatcher} that a chunk has been loaded. - *

- * WARNING: should be automatically done by the {@link Instance} implementation. - * - * @param chunk the loaded chunk - */ - public void signalChunkLoad(@NotNull Chunk chunk) { - this.threadDispatcher.createPartition(chunk); - } - - /** - * Signals the {@link ThreadDispatcher} that a chunk has been unloaded. - *

- * WARNING: should be automatically done by the {@link Instance} implementation. - * - * @param chunk the unloaded chunk - */ - public void signalChunkUnload(@NotNull Chunk chunk) { - this.threadDispatcher.deletePartition(chunk); - } - - /** - * Adds a callback executed at the start of the next server tick. - *

- * The long in the consumer represents the starting time (in ms) of the tick. - * - * @param callback the tick start callback - */ - public void addTickStartCallback(@NotNull LongConsumer callback) { - this.tickStartCallbacks.add(callback); - } - - /** - * Removes a tick start callback. - * - * @param callback the callback to remove - */ - public void removeTickStartCallback(@NotNull LongConsumer callback) { - this.tickStartCallbacks.remove(callback); - } - - /** - * Adds a callback executed at the end of the next server tick. - *

- * The long in the consumer represents the duration (in ms) of the tick. - * - * @param callback the tick end callback - */ - public void addTickEndCallback(@NotNull LongConsumer callback) { - this.tickEndCallbacks.add(callback); - } - - /** - * Removes a tick end callback. - * - * @param callback the callback to remove - */ - public void removeTickEndCallback(@NotNull LongConsumer callback) { - this.tickEndCallbacks.remove(callback); - } - - public void addTickMonitor(@NotNull Consumer consumer) { - this.tickMonitors.add(consumer); - } - - public void removeTickMonitor(@NotNull Consumer consumer) { - this.tickMonitors.remove(consumer); - } - - /** - * Stops the server loop. - */ - public void stop() { - this.stopRequested = true; - } - - private final class TickSchedulerThread extends MinestomThread { - private final ThreadDispatcher threadDispatcher = UpdateManager.this.threadDispatcher; - - TickSchedulerThread() { - super(MinecraftServer.THREAD_NAME_TICK_SCHEDULER); - } - - @Override - public void run() { - final ConnectionManager connectionManager = MinecraftServer.getConnectionManager(); - final SchedulerManager schedulerManager = MinecraftServer.getSchedulerManager(); - final List workers = MinecraftServer.getServer().workers(); - while (!stopRequested) { - try { - long currentTime = System.nanoTime(); - final long tickStart = System.currentTimeMillis(); - - // Tick start callbacks - doTickCallback(tickStartCallbacks, tickStart); - - schedulerManager.processTick(); - - // Waiting players update (newly connected clients waiting to get into the server) - connectionManager.updateWaitingPlayers(); - - // Keep Alive Handling - connectionManager.handleKeepAlive(tickStart); - - // Server tick (chunks/entities) - serverTick(tickStart); - - // Flush all waiting packets - PacketUtils.flush(); - workers.forEach(Worker::flush); - - // the time that the tick took in nanoseconds - final long tickTime = System.nanoTime() - currentTime; - - // Tick end callbacks - doTickCallback(tickEndCallbacks, tickTime); - - // Monitoring - if (!tickMonitors.isEmpty()) { - final double acquisitionTimeMs = Acquirable.getAcquiringTime() / 1e6D; - final double tickTimeMs = tickTime / 1e6D; - final TickMonitor tickMonitor = new TickMonitor(tickTimeMs, acquisitionTimeMs); - for (Consumer consumer : tickMonitors) { - consumer.accept(tickMonitor); - } - Acquirable.resetAcquiringTime(); - } - - // Disable thread until next tick - LockSupport.parkNanos((long) ((MinecraftServer.TICK_MS * 1e6) - tickTime)); - } catch (Exception e) { - MinecraftServer.getExceptionManager().handleException(e); - } - } - this.threadDispatcher.shutdown(); - } - - /** - * Executes a server tick and returns only once all the futures are completed. - * - * @param tickStart the time of the tick in milliseconds - */ - private void serverTick(long tickStart) { - // Tick all instances - for (Instance instance : MinecraftServer.getInstanceManager().getInstances()) { - try { - instance.tick(tickStart); - } catch (Exception e) { - MinecraftServer.getExceptionManager().handleException(e); - } - } - // Tick all chunks (and entities inside) - this.threadDispatcher.updateAndAwait(tickStart); - - // Clear removed entities & update threads - this.threadDispatcher.refreshThreads(); - } - - private void doTickCallback(Queue callbacks, long value) { - LongConsumer callback; - while ((callback = callbacks.poll()) != null) { - callback.accept(value); - } - } - } -} diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 8ccbd70f3..d270681e1 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -784,7 +784,7 @@ public class Entity implements Viewable, Tickable, Schedulable, TagHandler, Perm @ApiStatus.Internal protected void refreshCurrentChunk(Chunk currentChunk) { this.currentChunk = currentChunk; - MinecraftServer.getUpdateManager().getThreadProvider().updateElement(this, currentChunk); + MinecraftServer.process().dispatcher().updateElement(this, currentChunk); } /** @@ -1423,7 +1423,7 @@ public class Entity implements Viewable, Tickable, Schedulable, TagHandler, Perm if (!passengers.isEmpty()) passengers.forEach(this::removePassenger); final Entity vehicle = this.vehicle; if (vehicle != null) vehicle.removePassenger(this); - MinecraftServer.getUpdateManager().getThreadProvider().removeElement(this); + MinecraftServer.process().dispatcher().removeElement(this); this.removed = true; Entity.ENTITY_BY_ID.remove(id); Entity.ENTITY_BY_UUID.remove(uuid); diff --git a/src/main/java/net/minestom/server/event/GlobalHandles.java b/src/main/java/net/minestom/server/event/GlobalHandles.java index 1e2fc95fa..30760833e 100644 --- a/src/main/java/net/minestom/server/event/GlobalHandles.java +++ b/src/main/java/net/minestom/server/event/GlobalHandles.java @@ -7,6 +7,7 @@ import net.minestom.server.event.instance.InstanceTickEvent; import net.minestom.server.event.inventory.InventoryItemChangeEvent; import net.minestom.server.event.inventory.PlayerInventoryItemChangeEvent; import net.minestom.server.event.player.*; +import net.minestom.server.event.server.ServerTickMonitorEvent; import org.jetbrains.annotations.ApiStatus; /** @@ -25,4 +26,6 @@ public final class GlobalHandles { public static final ListenerHandle INSTANCE_CHUNK_LOAD = EventDispatcher.getHandle(InstanceChunkLoadEvent.class); public static final ListenerHandle INVENTORY_ITEM_CHANGE_EVENT = EventDispatcher.getHandle(InventoryItemChangeEvent.class); public static final ListenerHandle PLAYER_INVENTORY_ITEM_CHANGE_EVENT = EventDispatcher.getHandle(PlayerInventoryItemChangeEvent.class); + + public static final ListenerHandle SERVER_TICK_MONITOR_HANDLE = EventDispatcher.getHandle(ServerTickMonitorEvent.class); } diff --git a/src/main/java/net/minestom/server/event/server/ServerTickMonitorEvent.java b/src/main/java/net/minestom/server/event/server/ServerTickMonitorEvent.java new file mode 100644 index 000000000..62e91d3ed --- /dev/null +++ b/src/main/java/net/minestom/server/event/server/ServerTickMonitorEvent.java @@ -0,0 +1,17 @@ +package net.minestom.server.event.server; + +import net.minestom.server.event.Event; +import net.minestom.server.monitoring.TickMonitor; +import org.jetbrains.annotations.NotNull; + +public final class ServerTickMonitorEvent implements Event { + private final TickMonitor tickMonitor; + + public ServerTickMonitorEvent(@NotNull TickMonitor tickMonitor) { + this.tickMonitor = tickMonitor; + } + + public @NotNull TickMonitor getTickMonitor() { + return tickMonitor; + } +} diff --git a/src/main/java/net/minestom/server/extensions/ExtensionManager.java b/src/main/java/net/minestom/server/extensions/ExtensionManager.java index c3ba92a9f..e2b430934 100644 --- a/src/main/java/net/minestom/server/extensions/ExtensionManager.java +++ b/src/main/java/net/minestom/server/extensions/ExtensionManager.java @@ -4,7 +4,7 @@ import com.google.gson.Gson; import net.minestom.dependencies.DependencyGetter; import net.minestom.dependencies.ResolvedDependency; import net.minestom.dependencies.maven.MavenRepository; -import net.minestom.server.MinecraftServer; +import net.minestom.server.ServerProcess; import net.minestom.server.event.Event; import net.minestom.server.event.EventNode; import net.minestom.server.ping.ResponseDataConsumer; @@ -34,6 +34,8 @@ public class ExtensionManager { public final static String INDEV_RESOURCES_FOLDER = "minestom.extension.indevfolder.resources"; private final static Gson GSON = new Gson(); + private final ServerProcess serverProcess; + // LinkedHashMaps are HashMaps that preserve order private final Map extensions = new LinkedHashMap<>(); private final Map immutableExtensions = Collections.unmodifiableMap(extensions); @@ -42,10 +44,11 @@ public class ExtensionManager { private final File dependenciesFolder = new File(extensionFolder, ".libs"); private Path extensionDataRoot = extensionFolder.toPath(); - private enum State { DO_NOT_START, NOT_STARTED, STARTED, PRE_INIT, INIT, POST_INIT } + private enum State {DO_NOT_START, NOT_STARTED, STARTED, PRE_INIT, INIT, POST_INIT} private State state = State.NOT_STARTED; - public ExtensionManager() { + public ExtensionManager(ServerProcess serverProcess) { + this.serverProcess = serverProcess; } /** @@ -219,7 +222,7 @@ public class ExtensionManager { discoveredExtension.createClassLoader(); } catch (Exception e) { discoveredExtension.loadStatus = DiscoveredExtension.LoadStatus.FAILED_TO_SETUP_CLASSLOADER; - MinecraftServer.getExceptionManager().handleException(e); + serverProcess.exception().handleException(e); LOGGER.error("Failed to load extension {}", discoveredExtension.getName()); LOGGER.error("Failed to load extension", e); extensionIterator.remove(); @@ -239,7 +242,7 @@ public class ExtensionManager { } catch (Exception e) { discoveredExtension.loadStatus = DiscoveredExtension.LoadStatus.LOAD_FAILED; LOGGER.error("Failed to load extension {}", discoveredExtension.getName()); - MinecraftServer.getExceptionManager().handleException(e); + serverProcess.exception().handleException(e); } } } @@ -338,7 +341,7 @@ public class ExtensionManager { loggerField.set(extension, LoggerFactory.getLogger(extensionClass)); } catch (IllegalAccessException e) { // We made it accessible, should not occur - MinecraftServer.getExceptionManager().handleException(e); + serverProcess.exception().handleException(e); } catch (NoSuchFieldException e) { // This should also not occur (unless someone changed the logger in Extension superclass). LOGGER.error("Main class '{}' in '{}' has no logger field.", mainClass, extensionName, e); @@ -351,10 +354,10 @@ public class ExtensionManager { loggerField.setAccessible(true); loggerField.set(extension, eventNode); - MinecraftServer.getGlobalEventHandler().addChild(eventNode); + serverProcess.eventHandler().addChild(eventNode); } catch (IllegalAccessException e) { // We made it accessible, should not occur - MinecraftServer.getExceptionManager().handleException(e); + serverProcess.exception().handleException(e); } catch (NoSuchFieldException e) { // This should also not occur LOGGER.error("Main class '{}' in '{}' has no event node field.", mainClass, extensionName, e); @@ -430,7 +433,7 @@ public class ExtensionManager { extensions.add(extension); } } catch (IOException e) { - MinecraftServer.getExceptionManager().handleException(e); + serverProcess.exception().handleException(e); } } return extensions; @@ -463,7 +466,7 @@ public class ExtensionManager { return extension; } catch (IOException e) { - MinecraftServer.getExceptionManager().handleException(e); + serverProcess.exception().handleException(e); return null; } } @@ -727,7 +730,7 @@ public class ExtensionManager { // Remove event node EventNode eventNode = ext.getEventNode(); - MinecraftServer.getGlobalEventHandler().removeChild(eventNode); + serverProcess.eventHandler().removeChild(eventNode); ext.postTerminate(); diff --git a/src/main/java/net/minestom/server/extras/MojangAuth.java b/src/main/java/net/minestom/server/extras/MojangAuth.java index f503ef5fc..9f78d9181 100644 --- a/src/main/java/net/minestom/server/extras/MojangAuth.java +++ b/src/main/java/net/minestom/server/extras/MojangAuth.java @@ -18,7 +18,7 @@ public final class MojangAuth { */ public static void init() { Check.stateCondition(enabled, "Mojang auth is already enabled!"); - Check.stateCondition(MinecraftServer.isStarted(), "The server has already been started!"); + Check.stateCondition(MinecraftServer.process().isAlive(), "The server has already been started!"); MojangAuth.enabled = true; // Generate necessary fields... MojangAuth.keyPair = MojangCrypt.generateKeyPair(); diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index c6d8ffd63..717b71f80 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -5,7 +5,6 @@ import net.kyori.adventure.identity.Identity; import net.kyori.adventure.pointer.Pointers; import net.minestom.server.MinecraftServer; import net.minestom.server.Tickable; -import net.minestom.server.UpdateManager; import net.minestom.server.adventure.audience.PacketGroupingAudience; import net.minestom.server.coordinate.Point; import net.minestom.server.data.Data; @@ -23,6 +22,7 @@ import net.minestom.server.network.packet.server.play.BlockActionPacket; import net.minestom.server.network.packet.server.play.TimeUpdatePacket; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagHandler; +import net.minestom.server.thread.ThreadDispatcher; import net.minestom.server.timer.Schedulable; import net.minestom.server.timer.Scheduler; import net.minestom.server.utils.PacketUtils; @@ -50,13 +50,11 @@ import java.util.stream.Collectors; *

* WARNING: when making your own implementation registering the instance manually is required * with {@link InstanceManager#registerInstance(Instance)}, and - * you need to be sure to signal the {@link UpdateManager} of the changes using - * {@link UpdateManager#signalChunkLoad(Chunk)} and {@link UpdateManager#signalChunkUnload(Chunk)}. + * you need to be sure to signal the {@link ThreadDispatcher} of every partition/element changes. */ public abstract class Instance implements Block.Getter, Block.Setter, Tickable, Schedulable, TagHandler, PacketGroupingAudience { protected static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager(); - protected static final UpdateManager UPDATE_MANAGER = MinecraftServer.getUpdateManager(); private boolean registered; diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index 39753798e..d3e7e3830 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -2,6 +2,7 @@ package net.minestom.server.instance; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minestom.server.MinecraftServer; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.Entity; @@ -223,7 +224,8 @@ public class InstanceContainer extends Instance { // Clear cache this.chunks.remove(index); chunk.unload(); - UPDATE_MANAGER.signalChunkUnload(chunk); + var dispatcher = MinecraftServer.process().dispatcher(); + dispatcher.deletePartition(chunk); } @Override @@ -525,6 +527,7 @@ public class InstanceContainer extends Instance { private void cacheChunk(@NotNull Chunk chunk) { final long index = ChunkUtils.getChunkIndex(chunk); this.chunks.put(index, chunk); - UPDATE_MANAGER.signalChunkLoad(chunk); + var dispatcher = MinecraftServer.process().dispatcher(); + dispatcher.createPartition(chunk); } } \ No newline at end of file diff --git a/src/main/java/net/minestom/server/instance/InstanceManager.java b/src/main/java/net/minestom/server/instance/InstanceManager.java index 4dbece1a2..37218be50 100644 --- a/src/main/java/net/minestom/server/instance/InstanceManager.java +++ b/src/main/java/net/minestom/server/instance/InstanceManager.java @@ -118,7 +118,8 @@ public final class InstanceManager { // Unregister instance.setRegistered(false); this.instances.remove(instance); - MinecraftServer.getUpdateManager().signalInstanceDelete(instance); + var dispatcher = MinecraftServer.process().dispatcher(); + instance.getChunks().forEach(dispatcher::deletePartition); } } @@ -155,6 +156,7 @@ public final class InstanceManager { private void UNSAFE_registerInstance(@NotNull Instance instance) { instance.setRegistered(true); this.instances.add(instance); - MinecraftServer.getUpdateManager().signalInstanceCreate(instance); + var dispatcher = MinecraftServer.process().dispatcher(); + instance.getChunks().forEach(dispatcher::createPartition); } } diff --git a/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java b/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java index 57df52318..790c09333 100644 --- a/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java +++ b/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java @@ -1,6 +1,7 @@ package net.minestom.server.listener.manager; import net.minestom.server.MinecraftServer; +import net.minestom.server.ServerProcess; import net.minestom.server.entity.Player; import net.minestom.server.event.GlobalHandles; import net.minestom.server.event.player.PlayerPacketEvent; @@ -21,11 +22,13 @@ import java.util.concurrent.ConcurrentHashMap; public final class PacketListenerManager { public final static Logger LOGGER = LoggerFactory.getLogger(PacketListenerManager.class); - private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager(); + private final ServerProcess serverProcess; private final Map, PacketListenerConsumer> listeners = new ConcurrentHashMap<>(); - public PacketListenerManager() { + public PacketListenerManager(ServerProcess serverProcess) { + this.serverProcess = serverProcess; + setListener(ClientKeepAlivePacket.class, KeepAliveListener::listener); setListener(ClientChatMessagePacket.class, ChatMessageListener::listener); setListener(ClientClickWindowPacket.class, WindowListener::clickWindowListener); @@ -79,7 +82,7 @@ public final class PacketListenerManager { // TODO remove legacy { final PacketController packetController = new PacketController(); - for (ClientPacketConsumer clientPacketConsumer : CONNECTION_MANAGER.getReceivePacketConsumers()) { + for (ClientPacketConsumer clientPacketConsumer : serverProcess.connection().getReceivePacketConsumers()) { clientPacketConsumer.accept(player, packetController, packet); } @@ -113,7 +116,7 @@ public final class PacketListenerManager { * @return true if the packet is not cancelled, false otherwise */ public boolean processServerPacket(@NotNull ServerPacket packet, @NotNull Collection players) { - final List consumers = CONNECTION_MANAGER.getSendPacketConsumers(); + final List consumers = serverProcess.connection().getSendPacketConsumers(); if (consumers.isEmpty()) { return true; } diff --git a/src/main/java/net/minestom/server/scoreboard/TeamManager.java b/src/main/java/net/minestom/server/scoreboard/TeamManager.java index 5e3ce614c..dd0b9d9ef 100644 --- a/src/main/java/net/minestom/server/scoreboard/TeamManager.java +++ b/src/main/java/net/minestom/server/scoreboard/TeamManager.java @@ -2,10 +2,8 @@ package net.minestom.server.scoreboard; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import net.minestom.server.MinecraftServer; import net.minestom.server.entity.LivingEntity; import net.minestom.server.entity.Player; -import net.minestom.server.network.ConnectionManager; import net.minestom.server.utils.PacketUtils; import net.minestom.server.utils.UniqueIdUtils; import org.jetbrains.annotations.NotNull; @@ -20,8 +18,6 @@ import java.util.concurrent.CopyOnWriteArraySet; */ public final class TeamManager { - private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager(); - /** * Represents all registered teams */ diff --git a/src/main/java/net/minestom/server/terminal/MinestomTerminal.java b/src/main/java/net/minestom/server/terminal/MinestomTerminal.java index c5ec53f68..e7563d480 100644 --- a/src/main/java/net/minestom/server/terminal/MinestomTerminal.java +++ b/src/main/java/net/minestom/server/terminal/MinestomTerminal.java @@ -13,8 +13,6 @@ import org.jline.terminal.TerminalBuilder; import java.io.IOException; public class MinestomTerminal { - - private static final CommandManager COMMAND_MANAGER = MinecraftServer.getCommandManager(); private static final String PROMPT = "> "; private static volatile Terminal terminal; @@ -37,7 +35,8 @@ public class MinestomTerminal { String command; try { command = reader.readLine(PROMPT); - COMMAND_MANAGER.execute(COMMAND_MANAGER.getConsoleSender(), command); + var commandManager = MinecraftServer.getCommandManager(); + commandManager.execute(commandManager.getConsoleSender(), command); } catch (UserInterruptException e) { // Handle Ctrl + C System.exit(0); diff --git a/src/main/java/net/minestom/server/thread/TickSchedulerThread.java b/src/main/java/net/minestom/server/thread/TickSchedulerThread.java new file mode 100644 index 000000000..2fe2c8f08 --- /dev/null +++ b/src/main/java/net/minestom/server/thread/TickSchedulerThread.java @@ -0,0 +1,31 @@ +package net.minestom.server.thread; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.ServerProcess; +import org.jetbrains.annotations.ApiStatus; + +import java.util.concurrent.locks.LockSupport; + +@ApiStatus.Internal +public final class TickSchedulerThread extends MinestomThread { + private final ServerProcess serverProcess; + + public TickSchedulerThread(ServerProcess serverProcess) { + super(MinecraftServer.THREAD_NAME_TICK_SCHEDULER); + this.serverProcess = serverProcess; + } + + @Override + public void run() { + while (serverProcess.isAlive()) { + final long tickStart = System.nanoTime(); + try { + serverProcess.ticker().tick(tickStart); + } catch (Exception e) { + serverProcess.exception().handleException(e); + } + final long tickTime = System.nanoTime() - tickStart; + LockSupport.parkNanos((long) ((MinecraftServer.TICK_MS * 1e6) - tickTime)); + } + } +} diff --git a/src/main/java/net/minestom/server/utils/PacketUtils.java b/src/main/java/net/minestom/server/utils/PacketUtils.java index c5ea7db5d..a70659de0 100644 --- a/src/main/java/net/minestom/server/utils/PacketUtils.java +++ b/src/main/java/net/minestom/server/utils/PacketUtils.java @@ -12,7 +12,6 @@ import net.minestom.server.Viewable; import net.minestom.server.adventure.audience.PacketGroupingAudience; import net.minestom.server.entity.Entity; import net.minestom.server.entity.Player; -import net.minestom.server.listener.manager.PacketListenerManager; import net.minestom.server.network.packet.server.CachedPacket; import net.minestom.server.network.packet.server.FramedPacket; import net.minestom.server.network.packet.server.SendablePacket; @@ -50,7 +49,6 @@ import java.util.zip.Inflater; * Be sure to check the implementation code. */ public final class PacketUtils { - private static final PacketListenerManager PACKET_LISTENER_MANAGER = MinecraftServer.getPacketListenerManager(); private static final LocalCache LOCAL_DEFLATER = LocalCache.of(Deflater::new); public static final boolean GROUPED_PACKET = getBoolean("minestom.grouped-packet", true); @@ -118,7 +116,7 @@ public final class PacketUtils { public static void sendGroupedPacket(@NotNull Collection players, @NotNull ServerPacket packet, @NotNull Predicate predicate) { if (players.isEmpty()) return; - if (!PACKET_LISTENER_MANAGER.processServerPacket(packet, players)) return; + if (!MinecraftServer.getPacketListenerManager().processServerPacket(packet, players)) return; // work out if the packet needs to be sent individually due to server-side translating final SendablePacket sendablePacket = GROUPED_PACKET ? new CachedPacket(packet) : packet; players.forEach(player -> { diff --git a/src/test/java/net/minestom/server/ServerProcessTest.java b/src/test/java/net/minestom/server/ServerProcessTest.java new file mode 100644 index 000000000..5b2daa9eb --- /dev/null +++ b/src/test/java/net/minestom/server/ServerProcessTest.java @@ -0,0 +1,30 @@ +package net.minestom.server; + +import org.junit.jupiter.api.Test; + +import java.net.InetSocketAddress; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ServerProcessTest { + + @Test + public void init() { + AtomicReference process = new AtomicReference<>(); + assertDoesNotThrow(() -> process.set(MinecraftServer.updateProcess())); + assertDoesNotThrow(() -> process.get().start(new InetSocketAddress("localhost", 25565))); + assertThrows(Exception.class, () -> process.get().start(new InetSocketAddress("localhost", 25566))); + assertDoesNotThrow(() -> process.get().stop()); + } + + @Test + public void tick() { + var process = MinecraftServer.updateProcess(); + process.start(new InetSocketAddress("localhost", 25567)); + var ticker = process.ticker(); + assertDoesNotThrow(() -> ticker.tick(System.currentTimeMillis())); + assertDoesNotThrow(process::stop); + } +}