diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts index 89396d62d..70bffe5bc 100644 --- a/bootstrap/velocity/build.gradle.kts +++ b/bootstrap/velocity/build.gradle.kts @@ -8,6 +8,10 @@ dependencies { annotationProcessor(libs.velocity.api) api(projects.core) + compileOnly(libs.velocity.proxy) + compileOnly(libs.netty.transport.native.io.uring) + compileOnly(libs.netty.transport.native.kqueue) + compileOnlyApi(libs.velocity.api) api(libs.cloud.velocity) } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java index c8a850131..a33a89f4b 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.platform.velocity; import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.proxy.network.TransportType; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -33,18 +34,25 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.IoEventLoop; +import io.netty.channel.IoEventLoopGroup; +import io.netty.channel.IoHandler; import io.netty.channel.IoHandlerFactory; import io.netty.channel.MultiThreadIoEventLoopGroup; +import io.netty.channel.SingleThreadIoEventLoop; import io.netty.channel.WriteBufferWaterMark; +import io.netty.channel.epoll.EpollIoHandler; +import io.netty.channel.kqueue.KQueueIoHandler; import io.netty.channel.local.LocalAddress; import io.netty.channel.local.LocalIoHandler; +import io.netty.channel.nio.NioIoHandler; +import io.netty.channel.uring.IoUringIoHandler; import io.netty.util.concurrent.DefaultThreadFactory; +import io.netty.util.concurrent.ThreadAwareExecutor; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserBootstrap; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.network.netty.GeyserInjector; +import org.geysermc.geyser.network.netty.IoHandlerWrapper; import org.geysermc.geyser.network.netty.LocalServerChannelWrapper; -import org.geysermc.geyser.network.netty.WatchedSingleThreadIoEventLoop; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -86,9 +94,18 @@ public class GeyserVelocityInjector extends GeyserInjector { Field workerGroupField = connectionManagerClass.getDeclaredField("workerGroup"); workerGroupField.setAccessible(true); - EventLoopGroup workerGroup = (EventLoopGroup) workerGroupField.get(connectionManager); + IoEventLoopGroup workerGroup = (IoEventLoopGroup) workerGroupField.get(connectionManager); - EventLoopGroup wrapperGroup = new MultiThreadIoEventLoopGroup(LocalIoHandler.newFactory()) { + var factory = LocalIoHandler.newFactory(); + var nativeFactory = getNativeHandlerFactory(); + var wrapperFactory = new IoHandlerFactory() { + @Override + public IoHandler newHandler(ThreadAwareExecutor ioExecutor) { + return new IoHandlerWrapper(factory.newHandler(ioExecutor), nativeFactory.newHandler(ioExecutor)); + } + }; + + EventLoopGroup wrapperGroup = new MultiThreadIoEventLoopGroup(factory) { @Override protected ThreadFactory newDefaultThreadFactory() { return new DefaultThreadFactory("Geyser Backend Worker Group", Thread.MAX_PRIORITY); @@ -96,7 +113,7 @@ public class GeyserVelocityInjector extends GeyserInjector { @Override protected IoEventLoop newChild(Executor executor, IoHandlerFactory ioHandlerFactory, Object... args) { - return new WatchedSingleThreadIoEventLoop(workerGroup, this, executor, ioHandlerFactory); + return new SingleThreadIoEventLoop(workerGroup, executor, wrapperFactory); } }; @@ -126,4 +143,13 @@ public class GeyserVelocityInjector extends GeyserInjector { this.localChannel = channelFuture; this.serverSocketAddress = channelFuture.channel().localAddress(); } + + private static IoHandlerFactory getNativeHandlerFactory() { + return switch (TransportType.bestType()) { + case NIO -> NioIoHandler.newFactory(); + case EPOLL -> EpollIoHandler.newFactory(); + case KQUEUE -> KQueueIoHandler.newFactory(); + case IO_URING -> IoUringIoHandler.newFactory(); + }; + } } diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/WatchedSingleThreadIoEventLoop.java b/core/src/main/java/org/geysermc/geyser/network/netty/WatchedSingleThreadIoEventLoop.java deleted file mode 100644 index 7de8cd18c..000000000 --- a/core/src/main/java/org/geysermc/geyser/network/netty/WatchedSingleThreadIoEventLoop.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2025 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.network.netty; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.IoEventLoopGroup; -import io.netty.channel.IoHandlerFactory; -import io.netty.channel.SingleThreadIoEventLoop; - -import java.util.concurrent.Executor; - -public class WatchedSingleThreadIoEventLoop extends SingleThreadIoEventLoop { - private final EventLoopGroup trueWorkerGroup; - - public WatchedSingleThreadIoEventLoop(EventLoopGroup trueWorkerGroup, IoEventLoopGroup parent, - Executor executor, IoHandlerFactory ioHandlerFactory) { - super(parent, executor, ioHandlerFactory); - this.trueWorkerGroup = trueWorkerGroup; - } - - @Override - @SuppressWarnings("deprecation") - public ChannelFuture register(Channel channel) { - if (channel.getClass().getName().startsWith("org.geysermc.geyser")) { - return super.register(channel); - } - // Starting with Netty 4.2, channels/event loops are very picky with what can be accepted for each. - // For example, IoUringIoHandler (on a Linux machine, what Velocity's worker group will be) - // will not accept LocalChannels on bootstrap creation in GeyserVelocityInjector. - // And using a MultiThreadEventLoopGroup with LocalIoHandler will throw an error when trying to - // connect to the backend server. - // Inserting ourselves here allows our local channels to use the event loop made for LocalChannels, - // while re-using the settings and style of Velocity. - return this.trueWorkerGroup.register(channel); - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ada156a73..6cdd054b1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -130,6 +130,7 @@ minecraftauth = { group = "net.raphimc", name = "MinecraftAuth", version.ref = " mcprotocollib = { group = "org.geysermc.mcprotocollib", name = "protocol", version.ref = "mcprotocollib" } raknet = { group = "org.cloudburstmc.netty", name = "netty-transport-raknet", version.ref = "raknet" } terminalconsoleappender = { group = "net.minecrell", name = "terminalconsoleappender", version.ref = "terminalconsoleappender" } +velocity-proxy = { group = "com.velocitypowered", name = "velocity-proxy", version.ref = "velocity" } velocity-api = { group = "com.velocitypowered", name = "velocity-api", version.ref = "velocity" } viaproxy = { group = "net.raphimc", name = "ViaProxy", version.ref = "viaproxy" } viaversion = { group = "com.viaversion", name = "viaversion", version.ref = "viaversion" }