1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-12-19 14:59:27 +00:00

Refactor a bunch of things

This commit is contained in:
RednedEpic
2025-11-09 18:03:58 +00:00
parent 86b1ac979f
commit 4542019a58
38 changed files with 1175 additions and 320 deletions

View File

@@ -29,13 +29,18 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
import org.geysermc.geyser.api.network.NetworkChannel;
import org.geysermc.geyser.api.network.message.Message;
import org.geysermc.geyser.api.network.message.MessageBuffer;
import org.geysermc.geyser.api.network.message.MessageCodec;
import org.geysermc.geyser.api.network.message.MessageFactory;
import org.geysermc.geyser.api.network.message.MessageHandler;
import org.geysermc.geyser.api.network.message.MessagePriority;
import java.util.function.Consumer;
/**
* Called whenever Geyser is registering network channels.
* @since 2.8.2
* @since 2.9.1
*/
public abstract class SessionDefineNetworkChannelsEvent extends ConnectionEvent {
@@ -44,19 +49,159 @@ public abstract class SessionDefineNetworkChannelsEvent extends ConnectionEvent
}
/**
* Registers a new network channel with a message factory.
* Defines the registration of a new network channel with a message factory.
*
* @param channel the channel to register
* @param messageFactory the factory to create messages from the buffer
* @param <M> the message type created by the factory
* @return a registration builder to configure handlers
*/
public abstract void register(@NonNull NetworkChannel channel, @NonNull MessageFactory<MessageBuffer> messageFactory);
public abstract <M extends Message<MessageBuffer>> Builder.@NonNull Initial<M> define(@NonNull NetworkChannel channel, @NonNull MessageFactory<MessageBuffer, M> messageFactory);
/**
* Registers a new network channel with a message factory.
* Defines the registration of a new network channel with a codec and message factory.
*
* @param channel the channel to register
* @param codec the codec to use to encode/decode the buffer
* @param messageFactory the factory to create messages from the buffer
* @param <T> the buffer type
* @param <M> the message type created by the factory
* @return a registration builder to configure handlers
*/
public abstract <T extends MessageBuffer> void register(@NonNull NetworkChannel channel, @NonNull MessageCodec<T> codec, @NonNull MessageFactory<T> messageFactory);
public abstract <T extends MessageBuffer, M extends Message<T>> Builder.@NonNull Initial<M> define(@NonNull NetworkChannel channel, @NonNull MessageCodec<T> codec, @NonNull MessageFactory<T, M> messageFactory);
/**
* Registration builder for attaching handlers to a channel.
*
* @param <M> the message type
*/
public interface Builder<M extends Message<? extends MessageBuffer>> {
/**
* Configures the pipeline for this handler.
*
* @param pipeline the pipeline consumer
* @return the builder instance
*/
@NonNull
Builder<M> pipeline(@NonNull Consumer<Pipeline> pipeline);
/**
* Finalizes the registration.
*
* @return the completed registration
*/
@NonNull
Registration<M> register();
interface Initial<M extends Message<? extends MessageBuffer>> extends Sided<M>, Bidirectional<M> {
/**
* {@inheritDoc}
*/
@Override
@NonNull
Initial<M> pipeline(@NonNull Consumer<Pipeline> pipeline);
}
interface Sided<M extends Message<? extends MessageBuffer>> extends Builder<M> {
/**
* Register a clientbound handler.
*/
@NonNull
Sided<M> clientbound(MessageHandler.@NonNull Sided<M> handler);
/**
* Register a clientbound handler with a priority.
*/
@NonNull
Sided<M> clientbound(@NonNull MessagePriority priority, MessageHandler.@NonNull Sided<M> handler);
/**
* Register a serverbound handler.
*/
@NonNull
Sided<M> serverbound(MessageHandler.@NonNull Sided<M> handler);
/**
* Register a serverbound handler with a priority.
*/
@NonNull
Sided<M> serverbound(@NonNull MessagePriority priority, MessageHandler.@NonNull Sided<M> handler);
/**
* {@inheritDoc}
*/
@Override
@NonNull
Sided<M> pipeline(@NonNull Consumer<Pipeline> pipeline);
}
interface Bidirectional<M extends Message<? extends MessageBuffer>> extends Builder<M> {
/**
* Register a bidirectional handler receiving the message and direction.
*/
@NonNull
Bidirectional<M> bidirectional(@NonNull MessageHandler<M> handler);
/**
* Register a bidirectional handler with a priority.
*/
@NonNull
Bidirectional<M> bidirectional(@NonNull MessagePriority priority, @NonNull MessageHandler<M> handler);
/**
* {@inheritDoc}
*/
@Override
@NonNull
Bidirectional<M> pipeline(@NonNull Consumer<Pipeline> pipeline);
}
/**
* Pipeline configuration for ordering handlers.
*/
interface Pipeline {
/**
* Tags this handler in the pipeline.
*
* @param tag the tag to apply
* @return the pipeline instance
*/
@NonNull
Pipeline tag(@NonNull String tag);
/**
* Places this handler before the handler with the given tag.
* <p>
* The tag used here must be previously defined in a separate handler using {@link #tag(String)}.
* However, it should be noted that if the specified tag does not exist at the time of registration,
* the handler will be added to the end of the pipeline without throwing an error.
*
* @param tag the tag to place before
* @return the pipeline instance
*/
@NonNull
Pipeline before(@NonNull String tag);
/**
* Places this handler after the handler with the given tag.
* <p>
* The tag used here must be previously defined in a separate handler using {@link #tag(String)}.
* However, it should be noted that if the specified tag does not exist at the time of registration,
* the handler will be added to the end of the pipeline without throwing an error.
*
* @param tag the tag to place after
* @return the pipeline instance
*/
@NonNull
Pipeline after(@NonNull String tag);
}
}
public interface Registration<M extends Message<? extends MessageBuffer>> {
}
}

View File

@@ -1,101 +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.api.event.java;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.Cancellable;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
import org.geysermc.geyser.api.network.MessageDirection;
import org.geysermc.geyser.api.network.NetworkChannel;
import org.geysermc.geyser.api.network.message.Message;
/**
* Called when Geyser receives a network message from the server.
* @since 2.8.2
*/
public final class ServerReceiveNetworkMessageEvent extends ConnectionEvent implements Cancellable {
private final NetworkChannel channel;
private final Message<?> message;
private final MessageDirection direction;
private boolean cancelled = false;
public ServerReceiveNetworkMessageEvent(@NonNull GeyserConnection connection, @NonNull NetworkChannel channel, @NonNull Message<?> message, @NonNull MessageDirection direction) {
super(connection);
this.channel = channel;
this.message = message;
this.direction = direction;
}
/**
* Gets the channel that received the message.
* <p>
* See {@link NetworkChannel} for more information.
*
* @return the channel that received the message
*/
@NonNull
public NetworkChannel channel() {
return this.channel;
}
/**
* Gets the message that was received.
*
* @return the received message
*/
@NonNull
public Message<?> message() {
return this.message;
}
/**
* Gets the direction of the message.
*
* @return the direction of the message
*/
@NonNull
public MessageDirection direction() {
return this.direction;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isCancelled() {
return this.cancelled;
}
/**
* {@inheritDoc}
*/
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}

View File

@@ -27,7 +27,7 @@ package org.geysermc.geyser.api.network;
/**
* Represents the direction of a message.
* @since 2.8.2
* @since 2.9.1
*/
public enum MessageDirection {
/**

View File

@@ -27,6 +27,7 @@ package org.geysermc.geyser.api.network;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.Identifier;
@@ -67,11 +68,11 @@ import org.geysermc.geyser.api.util.Identifier;
*
* <p>
* Packet channels can also be registered against packet objects from
* exterbak protocol libraries, such as the ones provided in Geyser. For
* external protocol libraries, such as the ones provided in Geyser. For
* an example on how to do this, please see the
* <a href="https://geysermc.org/wiki/geyser/networking-api">Networking API documentation</a>.
*
* @since 2.8.2
* @since 2.9.1
*/
public interface NetworkChannel {
@@ -91,58 +92,61 @@ public interface NetworkChannel {
boolean isPacket();
/**
* Creates a new {@link NetworkChannel} instance.
* Creates a new external {@link NetworkChannel} instance.
* <p>
* Extensions should use this method to register
* their own channels for more robust identification.
*
* @param extension the extension that registered this channel
* @param channel the name of the channel
* @return a new {@link NetworkChannel} instance
* @param messageType the type of the message sent over this channel
* @return a new external {@link NetworkChannel} instance
*/
@NonNull
static NetworkChannel of(@NonNull Extension extension, @NonNull String channel) {
return new ExtensionNetworkChannel(extension, channel);
static NetworkChannel of(@NonNull Extension extension, @NonNull String channel, @NonNull Class<?> messageType) {
return GeyserApi.api().provider(NetworkChannel.class, extension, channel, messageType);
}
/**
* Creates a new {@link NetworkChannel} instance.
* Creates a new external {@link NetworkChannel} instance.
* <p>
* This method is used for external channels provided
* by third parties, such as plugins or mods.
*
* @param id the channel id
* @param channel the name of the channel
* @return a new {@link NetworkChannel} instance
* @param messageType the type of the message sent over this channel
* @return a new external {@link NetworkChannel} instance
*/
@NonNull
static NetworkChannel of(@NonNull String id, @NonNull String channel) {
return of(Identifier.of(id, channel));
static NetworkChannel of(@NonNull String id, @NonNull String channel, @NonNull Class<?> messageType) {
return of(Identifier.of(id, channel), messageType);
}
/**
* Creates a new {@link NetworkChannel} instance.
* Creates a new external {@link NetworkChannel} instance.
* <p>
* This method is used for external channels provided
* by third parties, such as plugins or mods.
*
* @param identifier the {@link Identifier} of the channel
* @return a new {@link NetworkChannel} instance
* @param messageType the type of the message sent over this channel
* @return a new external {@link NetworkChannel} instance
*/
@NonNull
static NetworkChannel of(@NonNull Identifier identifier) {
return new ExternalNetworkChannel(identifier);
static NetworkChannel of(@NonNull Identifier identifier, @NonNull Class<?> messageType) {
return GeyserApi.api().provider(NetworkChannel.class, identifier, messageType);
}
/**
* Creates a new {@link PacketChannel} instance for a packet channel.
* Creates a new packet {@link NetworkChannel} instance for a packet channel.
*
* @param key the packet key
* @param packetId the packet ID
* @param packetType the type of the packet
* @return a new {@link PacketChannel} instance for a packet channel
* @return a new packet {@link NetworkChannel} instance for a packet channel
*/
static NetworkChannel packet(@NonNull String key, @NonNegative int packetId, @NonNull Class<?> packetType) {
return new PacketChannel(key, packetId, packetType);
return GeyserApi.api().provider(NetworkChannel.class, key, packetId, packetType);
}
}

View File

@@ -36,7 +36,7 @@ import java.util.Set;
* Represents the network manager responsible for handling network operations
* for a {@link GeyserConnection}.
*
* @since 2.8.2
* @since 2.9.1
*/
public interface NetworkManager {

View File

@@ -35,7 +35,7 @@ import java.util.Optional;
* Represents a data type that can be sent or received over the network.
*
* @param <T> the type
* @since 2.8.2
* @since 2.9.1
*/
public final class DataType<T> {
/**

View File

@@ -33,7 +33,7 @@ import java.util.function.Supplier;
/**
* Represents a message that can be sent over the network.
* @since 2.8.2
* @since 2.9.1
*/
public interface Message<T extends MessageBuffer> {
@@ -69,8 +69,10 @@ public interface Message<T extends MessageBuffer> {
* @param packet the packet object to create the message from
* @return a new packet message
*/
static <T extends MessageBuffer> PacketWrapped<T> of(@NonNull Object packet) {
return GeyserApi.api().provider(PacketWrapped.class, packet);
@SuppressWarnings("unchecked")
@NonNull
static <T extends MessageBuffer, P> PacketWrapped<T, P> of(@NonNull Object packet) {
return (PacketWrapped<T, P>) GeyserApi.api().provider(PacketWrapped.class, packet);
}
/**
@@ -80,7 +82,7 @@ public interface Message<T extends MessageBuffer> {
* @return a new packet message
*/
@NonNull
static <T extends MessageBuffer> MessageFactory<T> of(@NonNull Supplier<Object> packetSupplier) {
static <T extends MessageBuffer, P> MessageFactory<T, PacketWrapped<T, P>> of(@NonNull Supplier<P> packetSupplier) {
return buffer -> of(packetSupplier.get());
}
@@ -92,7 +94,7 @@ public interface Message<T extends MessageBuffer> {
* @return a new packet message factory
*/
@NonNull
static <T extends MessageBuffer, V> MessageFactory<T> of(@NonNull Function<T, V> substitutor, @NonNull Function<V, Object> packetSupplier) {
static <T extends MessageBuffer, V, P> MessageFactory<T, PacketWrapped<T, P>> of(@NonNull Function<T, V> substitutor, @NonNull Function<V, P> packetSupplier) {
return buffer -> of(packetSupplier.apply(substitutor.apply(buffer)));
}
}
@@ -102,7 +104,7 @@ public interface Message<T extends MessageBuffer> {
*
* @param <T> the type of message buffer
*/
interface PacketWrapped<T extends MessageBuffer> extends PacketBase<T> {
interface PacketWrapped<T extends MessageBuffer, P> extends PacketBase<T> {
/**
* Gets the packet associated with this message.
@@ -110,6 +112,6 @@ public interface Message<T extends MessageBuffer> {
* @return the packet
*/
@NonNull
Object packet();
P packet();
}
}

View File

@@ -29,7 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
/**
* A buffer for messages that can be sent over the network.
* @since 2.8.2
* @since 2.9.1
*/
public interface MessageBuffer {

View File

@@ -31,7 +31,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
* A codec for encoding and decoding messages.
*
* @param <T> the type of {@link MessageBuffer} used for encoding and decoding
* @since 2.8.2
* @since 2.9.1
*/
public interface MessageCodec<T extends MessageBuffer> {

View File

@@ -31,10 +31,11 @@ import org.checkerframework.checker.nullness.qual.NonNull;
* A factory interface for creating messages from a given message buffer.
*
* @param <T> the type of the message buffer
* @since 2.8.2
* @param <M> the type of the message
* @since 2.9.1
*/
@FunctionalInterface
public interface MessageFactory<T extends MessageBuffer> {
public interface MessageFactory<T extends MessageBuffer, M extends Message<T>> {
/**
* Creates a new message from the provided buffer.
@@ -43,5 +44,5 @@ public interface MessageFactory<T extends MessageBuffer> {
* @return a new message created from the buffer
*/
@NonNull
Message<T> create(@NonNull T buffer);
M create(@NonNull T buffer);
}

View File

@@ -0,0 +1,84 @@
/*
* 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.api.network.message;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.network.MessageDirection;
/**
* Represents a handler for processing messages.
*
* @param <T> the type of message to handle
*/
@FunctionalInterface
public interface MessageHandler<T extends Message<? extends MessageBuffer>> {
/**
* Handles the given message in the specified direction.
*
* @param message the message to handle
* @param direction the direction of the message
* @return the state after handling the message
*/
@NonNull
State handle(@NonNull T message, @NonNull MessageDirection direction);
/**
* A message handler that belongs to a specific side (clientbound or serverbound).
*
* @param <T> the type of message to handle
*/
interface Sided<T extends Message<? extends MessageBuffer>> {
/**
* Handles the given message in the specified direction.
*
* @param message the message to handle
* @return the state after handling the message
*/
@NonNull
State handle(@NonNull T message);
}
/**
* Represents the state after handling a message.
*/
enum State {
/**
* The message was handled and should not be processed further.
*/
HANDLED,
/**
* The message was not handled and should be passed through for further processing.
*/
UNHANDLED,
/**
* Indicates that the message has been modified but should still be
* passed through to the next handler or processing step.
*/
MODIFIED
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.api.network.message;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Represents the priority of a message when being processed.
* @since 2.9.1
*/
public enum MessagePriority {
FIRST(100),
EARLY(50),
NORMAL(0),
LATE(-50),
LAST(-100);
private final int value;
MessagePriority(int value) {
this.value = value;
}
/**
* Gets the numeric value associated with this priority. Higher means earlier.
*
* @return the priority value
*/
public int value() {
return value;
}
/**
* Creates a custom priority in the range [-100, 100].
*
* @param value the priority value
* @return the priority
* @throws IllegalArgumentException if outside allowed range
*/
@NonNull
public static MessagePriority of(int value) {
if (value >= 75) return LAST;
if (value >= 25) return LATE;
if (value <= -75) return FIRST;
if (value <= -25) return EARLY;
return NORMAL;
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.network.NetworkChannel;
public abstract class BaseNetworkChannel implements NetworkChannel {
private final Class<?> messageType;
public BaseNetworkChannel(Class<?> messageType) {
this.messageType = messageType;
}
/**
* Gets the type of message this channel handles.
*
* @return the message type
*/
@NonNull
public Class<?> messageType() {
return messageType;
}
}

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.network;
package org.geysermc.geyser.network;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.extension.Extension;
@@ -33,13 +33,14 @@ import java.util.Objects;
/**
* Represents a network channel associated with an extension.
* @since 2.8.2
*/
public class ExtensionNetworkChannel implements NetworkChannel {
public class ExtensionNetworkChannel extends BaseNetworkChannel {
private final Extension extension;
private final String channel;
protected ExtensionNetworkChannel(@NonNull Extension extension, @NonNull String channel) {
public ExtensionNetworkChannel(@NonNull Extension extension, @NonNull String channel, @NonNull Class<?> messageType) {
super(messageType);
this.extension = extension;
this.channel = channel;
}
@@ -63,14 +64,14 @@ public class ExtensionNetworkChannel implements NetworkChannel {
@Override
public boolean equals(Object o) {
if (o == null || !NetworkChannel.class.isAssignableFrom(o.getClass())) return false;
NetworkChannel that = (NetworkChannel) o;
return Objects.equals(this.identifier(), that.identifier());
if (o == null || getClass() != o.getClass()) return false;
ExtensionNetworkChannel that = (ExtensionNetworkChannel) o;
return Objects.equals(this.extension, that.extension) && Objects.equals(this.channel, that.channel) && Objects.equals(this.messageType(), that.messageType());
}
@Override
public int hashCode() {
return Objects.hash(this.identifier());
return Objects.hash(this.identifier(), this.messageType());
}
@Override
@@ -78,6 +79,7 @@ public class ExtensionNetworkChannel implements NetworkChannel {
return "ExtensionNetworkChannel{" +
"extension=" + this.extension.description().id() +
", channel='" + this.channel + '\'' +
", messageType=" + this.messageType() +
'}';
}
}

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.network;
package org.geysermc.geyser.network;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.util.Identifier;
@@ -34,12 +34,13 @@ import java.util.Objects;
* Represents a network channel not associated with any specific extension.
* <p>
* This can be used for external communication channels, like mods or plugins.
* @since 2.8.2
*/
public class ExternalNetworkChannel implements NetworkChannel {
public class ExternalNetworkChannel extends BaseNetworkChannel {
private final Identifier identifier;
protected ExternalNetworkChannel(@NonNull Identifier identifier) {
public ExternalNetworkChannel(@NonNull Identifier identifier, @NonNull Class<?> messageType) {
super(messageType);
this.identifier = identifier;
}
@@ -62,20 +63,21 @@ public class ExternalNetworkChannel implements NetworkChannel {
@Override
public boolean equals(Object o) {
if (o == null || !NetworkChannel.class.isAssignableFrom(o.getClass())) return false;
NetworkChannel that = (NetworkChannel) o;
return Objects.equals(this.identifier(), that.identifier());
if (o == null || getClass() != o.getClass()) return false;
ExternalNetworkChannel that = (ExternalNetworkChannel) o;
return Objects.equals(this.identifier, that.identifier) && Objects.equals(this.messageType(), that.messageType());
}
@Override
public int hashCode() {
return Objects.hash(this.identifier());
return Objects.hash(this.identifier(), this.messageType());
}
@Override
public String toString() {
return "ExternalNetworkChannel{" +
"identifier='" + this.identifier + '\'' +
", messageType=" + this.messageType() +
'}';
}
}

View File

@@ -31,30 +31,33 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.kyori.adventure.key.Key;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodecHelper;
import org.cloudburstmc.protocol.bedrock.codec.BedrockPacketDefinition;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.event.bedrock.SessionDefineNetworkChannelsEvent;
import org.geysermc.geyser.api.event.java.ServerReceiveNetworkMessageEvent;
import org.geysermc.geyser.api.network.MessageDirection;
import org.geysermc.geyser.api.network.NetworkChannel;
import org.geysermc.geyser.api.network.NetworkManager;
import org.geysermc.geyser.api.network.PacketChannel;
import org.geysermc.geyser.api.network.message.Message;
import org.geysermc.geyser.api.network.message.MessageBuffer;
import org.geysermc.geyser.api.network.message.MessageCodec;
import org.geysermc.geyser.api.network.message.MessageFactory;
import org.geysermc.geyser.api.network.message.MessageHandler;
import org.geysermc.geyser.network.message.BedrockPacketMessage;
import org.geysermc.geyser.network.message.ByteBufCodec;
import org.geysermc.geyser.network.message.ByteBufMessageBuffer;
import org.geysermc.geyser.network.message.JavaPacketMessage;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundCustomPayloadPacket;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.VisibleForTesting;
import java.util.HashMap;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
@@ -62,7 +65,7 @@ import java.util.function.Function;
public class GeyserNetworkManager implements NetworkManager {
private final GeyserSession session;
private final Map<NetworkChannel, MessageDefinition<?>> definitions = new HashMap<>();
private final Map<NetworkChannel, List<MessageDefinition<?, ?>>> definitions = new LinkedHashMap<>();
private final Int2ObjectMap<PacketChannel> packetChannels = new Int2ObjectOpenHashMap<>();
public GeyserNetworkManager(GeyserSession session) {
@@ -71,31 +74,77 @@ public class GeyserNetworkManager implements NetworkManager {
SessionDefineNetworkChannelsEvent event = new SessionDefineNetworkChannelsEvent(session) {
@Override
public void register(@NonNull NetworkChannel channel, @NonNull MessageFactory<MessageBuffer> messageFactory) {
GeyserNetworkManager.this.registerMessage(channel, new MessageDefinition<>(ByteBufCodec.INSTANCE, messageFactory));
public <M extends Message<MessageBuffer>> Builder.@NonNull Initial<M> define(@NonNull NetworkChannel channel, @NonNull MessageFactory<MessageBuffer, M> messageFactory) {
return new NetworkDefinitionBuilder<>(registration -> onRegister(channel, ByteBufCodec.INSTANCE, messageFactory, registration));
}
@Override
public <T extends MessageBuffer> void register(@NonNull NetworkChannel channel, @NonNull MessageCodec<T> codec, @NonNull MessageFactory<T> messageFactory) {
GeyserNetworkManager.this.registerMessage(channel, new MessageDefinition<>(codec, messageFactory));
public <T extends MessageBuffer, M extends Message<T>> Builder.@NonNull Initial<M> define(@NonNull NetworkChannel channel, @NonNull MessageCodec<T> codec, @NonNull MessageFactory<T, M> messageFactory) {
return new NetworkDefinitionBuilder<>(registration -> onRegister(channel, codec, messageFactory, registration));
}
};
GeyserApi.api().eventBus().fire(event);
GeyserImpl.getInstance().getEventBus().fire(event);
}
@Override
public @NonNull Set<NetworkChannel> registeredChannels() {
return Set.copyOf(this.definitions.keySet());
@VisibleForTesting
<M extends Message<MessageBuffer>> void onRegister(@NonNull NetworkChannel channel, @NonNull MessageFactory<MessageBuffer, M> messageFactory, NetworkDefinitionBuilder.@NonNull RegistrationImpl<M> registration) {
this.onRegister(channel, ByteBufCodec.INSTANCE, messageFactory, registration);
}
@SuppressWarnings("unchecked")
private <T extends MessageBuffer, M extends Message<T>> void onRegister(@NonNull NetworkChannel channel, @NonNull MessageCodec<? extends T> codec,
@NonNull MessageFactory<T, M> messageFactory, NetworkDefinitionBuilder.@NonNull RegistrationImpl<M> registration) {
MessageHandler<M> handler;
int priority;
NetworkDefinitionBuilder.HandlerEntry<M> bidirectional = registration.handler();
NetworkDefinitionBuilder.SidedHandlerEntry<M> clientbound = registration.clientbound();
NetworkDefinitionBuilder.SidedHandlerEntry<M> serverbound = registration.serverbound();
if (bidirectional != null) {
handler = bidirectional.handler();
priority = bidirectional.priority() != null ? bidirectional.priority().value() : 0;
} else {
handler = null;
int cbPriority = clientbound != null && clientbound.priority() != null ? clientbound.priority().value() : Integer.MIN_VALUE;
int sbPriority = serverbound != null && serverbound.priority() != null ? serverbound.priority().value() : Integer.MIN_VALUE;
priority = Math.max(cbPriority, sbPriority);
if (priority == Integer.MIN_VALUE) {
priority = 0;
}
}
MessageDefinition<T, M> definition = new MessageDefinition<>((MessageCodec<T>) codec,
messageFactory,
handler,
clientbound != null ? clientbound.handler() : null,
serverbound != null ? serverbound.handler() : null,
priority,
clientbound != null && clientbound.priority() != null ? clientbound.priority().value() : null,
serverbound != null && serverbound.priority() != null ? serverbound.priority().value() : null,
registration.tag(),
registration.beforeTag(),
registration.afterTag()
);
this.registerMessage(channel, definition);
}
@Override
@Nonnull
public Set<NetworkChannel> registeredChannels() {
return Set.copyOf(this.definitions.keySet());
}
@Override
public <T extends MessageBuffer> void send(@NonNull NetworkChannel channel, @NonNull Message<T> message, @NonNull MessageDirection direction) {
if (channel.isPacket() && message instanceof Message.PacketBase<T> packetBase) {
if (packetBase instanceof BedrockPacketMessage packetMessage) {
if (packetBase instanceof BedrockPacketMessage<?> packetMessage) {
this.session.sendUpstreamPacket(packetMessage.packet());
} else if (packetBase instanceof JavaPacketMessage packetMessage) {
} else if (packetBase instanceof JavaPacketMessage<?> packetMessage) {
this.session.sendDownstreamPacket(packetMessage.packet());
} else if (packetBase instanceof Message.Packet packet) {
PacketChannel packetChannel = (PacketChannel) channel;
@@ -123,10 +172,7 @@ public class GeyserNetworkManager implements NetworkManager {
return;
}
MessageDefinition<T> definition = (MessageDefinition<T>) this.definitions.get(channel);
if (definition == null) {
throw new IllegalArgumentException("No message definition registered for channel: " + channel);
}
MessageDefinition<T, Message<T>> definition = this.findMessageDefinition(channel, message);
T buffer = definition.codec.createBuffer();
message.encode(buffer);
@@ -139,29 +185,40 @@ public class GeyserNetworkManager implements NetworkManager {
this.session.sendDownstreamPacket(packet);
}
public <T extends MessageBuffer> Message<T> createMessage(@NonNull NetworkChannel channel, byte @NotNull[] data) {
return this.createMessage0(channel, definition -> definition.createBuffer(data));
@NonNull
public <T extends MessageBuffer> List<Message<T>> createMessages(@NonNull NetworkChannel channel, byte @NonNull[] data) {
return this.createMessages0(channel, definition -> definition.createBuffer(data));
}
public <T extends MessageBuffer> Message<T> createMessage(@NonNull NetworkChannel channel, @NonNull T buffer) {
return this.createMessage0(channel, def -> buffer);
@NonNull
public <T extends MessageBuffer> List<Message<T>> createMessages(@NonNull NetworkChannel channel, @NonNull T buffer) {
return this.createMessages0(channel, def -> buffer);
}
@SuppressWarnings("unchecked")
private <T extends MessageBuffer> Message<T> createMessage0(@NonNull NetworkChannel channel, @NonNull Function<MessageDefinition<T>, T> creator) {
MessageDefinition<T> definition = (MessageDefinition<T>) this.definitions.get(channel);
if (definition == null) {
@NonNull
private <T extends MessageBuffer, M extends Message<T>> List<M> createMessages0(@NonNull NetworkChannel channel, @NonNull Function<MessageDefinition<T, M>, T> creator) {
List<MessageDefinition<?, ?>> definitions = this.definitions.get(channel);
if (definitions == null || definitions.isEmpty()) {
throw new IllegalArgumentException("No message definition registered for channel: " + channel);
}
T buffer = creator.apply(definition);
Message<T> message = definition.createMessage(buffer);
if (message instanceof BedrockPacketMessage packetMessage) {
packetMessage.postProcess(this.session, (ByteBufMessageBuffer) buffer);
List<M> messages = new ArrayList<>();
for (MessageDefinition<?, ?> def : definitions) {
MessageDefinition<T, M> definition = (MessageDefinition<T, M>) def;
T buffer = creator.apply(definition);
M message = definition.createMessage(buffer);
if (message instanceof BedrockPacketMessage<?> packetMessage) {
packetMessage.postProcess(this.session, (ByteBufMessageBuffer) buffer);
}
messages.add(message);
}
return message;
return messages;
}
@Nullable
public PacketChannel getPacketChannel(int packetId) {
return this.packetChannels.get(packetId);
}
@@ -179,42 +236,184 @@ public class GeyserNetworkManager implements NetworkManager {
return true;
}
Message<?> message;
if (channel.packetType().isInstance(packet)) {
message = new BedrockPacketMessage(packet);
List<Message<ByteBufMessageBuffer>> messages;
if (channel.messageType().isInstance(packet)) {
messages = List.of(new BedrockPacketMessage<>(packet));
} else {
ByteBuf buffer = Unpooled.buffer();
definition.getSerializer().serialize(buffer, this.session.getUpstream().getCodecHelper(), packet);
message = this.createMessage(channel, new ByteBufMessageBuffer(ByteBufCodec.INSTANCE_LE, buffer));
messages = this.createMessages(channel, new ByteBufMessageBuffer(ByteBufCodec.INSTANCE_LE, buffer));
}
ServerReceiveNetworkMessageEvent event = new ServerReceiveNetworkMessageEvent(this.session, channel, message, direction);
this.session.getGeyser().eventBus().fire(event);
// If the event is canceled, we do not want to process the packet further
return !event.isCancelled();
return this.handleMessages(channel, messages, direction);
}
private <T extends MessageBuffer> void registerMessage(@NonNull NetworkChannel channel, @NonNull MessageDefinition<T> codec) {
if (this.definitions.containsKey(channel)) {
throw new IllegalArgumentException("Channel is already registered: " + channel);
@SuppressWarnings("unchecked")
public <T extends MessageBuffer> boolean handleMessages(@NonNull NetworkChannel channel, @NonNull List<Message<T>> messages, @NonNull MessageDirection direction) {
List<MessageDefinition<?, ?>> rawList = this.definitions.get(channel);
if (rawList == null || rawList.isEmpty()) {
return true;
}
this.definitions.put(channel, codec);
// Build a direction-aware ordered list while preserving pipeline tag anchors
List<MessageDefinition<?, ?>> ordered = new ArrayList<>();
List<MessageDefinition<?, ?>> unpinnedBlock = new ArrayList<>();
for (MessageDefinition<?, ?> def : rawList) {
boolean pinned = def.tag() != null || def.beforeTag() != null || def.afterTag() != null;
if (pinned) {
// flush any accumulated unpinned block sorted by effective priority for this direction
if (!unpinnedBlock.isEmpty()) {
unpinnedBlock.sort((a, b) -> Integer.compare(
b.priority(direction),
a.priority(direction)
));
ordered.addAll(unpinnedBlock);
unpinnedBlock.clear();
}
ordered.add(def);
} else {
unpinnedBlock.add(def);
}
}
if (!unpinnedBlock.isEmpty()) {
unpinnedBlock.sort((a, b) -> Integer.compare(
b.priority(direction),
a.priority(direction)
));
ordered.addAll(unpinnedBlock);
unpinnedBlock.clear();
}
for (Message<T> message : messages) {
for (MessageDefinition<?, ?> def : ordered) {
if (!(channel instanceof BaseNetworkChannel base) || !base.messageType().isInstance(message)) {
continue;
}
MessageDefinition<T, Message<T>> definition = (MessageDefinition<T, Message<T>>) def;
MessageHandler.State state;
if (definition.handler != null) {
state = definition.handler.handle(message, direction);
} else if (direction == MessageDirection.CLIENTBOUND && definition.clientboundHandler != null) {
state = definition.clientboundHandler.handle(message);
} else if (direction == MessageDirection.SERVERBOUND && definition.serverboundHandler != null) {
state = definition.serverboundHandler.handle(message);
} else {
continue; // no suitable handler; try next definition
}
if (state == MessageHandler.State.HANDLED) {
return false;
}
}
}
return true;
}
@SuppressWarnings("unchecked")
@NonNull
private <T extends MessageBuffer, M extends Message<T>> MessageDefinition<T, M> findMessageDefinition(@NonNull NetworkChannel channel, @NonNull Message<T> message) {
List<MessageDefinition<?, ?>> definitions = this.definitions.get(channel);
if (definitions == null || definitions.isEmpty()) {
throw new IllegalArgumentException("No message definition registered for channel: " + channel);
}
MessageDefinition<T, Message<T>> definition = null;
for (MessageDefinition<?, ?> def : definitions) {
if (channel instanceof BaseNetworkChannel baseChannel) {
if (baseChannel.messageType().isInstance(message)) {
definition = (MessageDefinition<T, Message<T>>) def;
break;
}
}
}
if (definition == null) {
throw new IllegalArgumentException("No suitable message definition found for channel: " + channel + " and message type: " + message.getClass());
}
return (MessageDefinition<T, M>) definition;
}
private <T extends MessageBuffer, M extends Message<T>> void registerMessage(@NonNull NetworkChannel channel, @NonNull MessageDefinition<T, M> definition) {
List<MessageDefinition<?, ?>> list = this.definitions.computeIfAbsent(channel, key -> new ArrayList<>());
// Determine the insert position based on pipeline tags or priority
int insertIndex = -1;
if (definition.beforeTag() != null) {
for (int i = 0; i < list.size(); i++) {
MessageDefinition<?, ?> existing = list.get(i);
if (definition.beforeTag().equals(existing.tag())) {
insertIndex = i;
break;
}
}
} else if (definition.afterTag() != null) {
for (int i = 0; i < list.size(); i++) {
MessageDefinition<?, ?> existing = list.get(i);
if (definition.afterTag().equals(existing.tag())) {
insertIndex = i + 1;
break;
}
}
}
if (insertIndex == -1) {
// Fallback: insert by descending priority
insertIndex = list.size();
for (int i = 0; i < list.size(); i++) {
MessageDefinition<?, ?> existing = list.get(i);
if (definition.priority() > existing.priority()) {
insertIndex = i;
break;
}
}
}
list.add(insertIndex, definition);
if (channel.isPacket() && channel instanceof PacketChannel packetChannel) {
int packetId = packetChannel.packetId();
this.packetChannels.put(packetId, packetChannel);
}
}
public record MessageDefinition<T extends MessageBuffer>(MessageCodec<? extends T> codec, MessageFactory<T> messageFactory) {
public record MessageDefinition<T extends MessageBuffer, M extends Message<T>>(
@NonNull MessageCodec<T> codec,
@NonNull MessageFactory<T, M> messageFactory,
@Nullable MessageHandler<M> handler,
MessageHandler.Sided<M> clientboundHandler,
MessageHandler.Sided<M> serverboundHandler,
int priority,
@Nullable Integer clientboundPriority,
@Nullable Integer serverboundPriority,
@Nullable String tag,
@Nullable String beforeTag,
@Nullable String afterTag
) {
public T createBuffer(byte @NotNull[] data) {
@NonNull
public T createBuffer(byte @NonNull[] data) {
return this.codec.createBuffer(data);
}
public Message<T> createMessage(@NonNull T buffer) {
@NonNull
public M createMessage(@NonNull T buffer) {
return this.messageFactory.create(buffer);
}
public int priority(@NonNull MessageDirection direction) {
if (this.handler != null) {
return this.priority;
}
if (direction == MessageDirection.CLIENTBOUND) {
return this.clientboundPriority != null ? this.clientboundPriority : 0;
}
return this.serverboundPriority != null ? this.serverboundPriority : 0;
}
}
}

View File

@@ -0,0 +1,167 @@
/*
* 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;
import com.google.common.base.Preconditions;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.event.bedrock.SessionDefineNetworkChannelsEvent;
import org.geysermc.geyser.api.network.message.Message;
import org.geysermc.geyser.api.network.message.MessageBuffer;
import org.geysermc.geyser.api.network.message.MessageHandler;
import org.geysermc.geyser.api.network.message.MessagePriority;
import java.util.Objects;
import java.util.function.Consumer;
public class NetworkDefinitionBuilder<M extends Message<? extends MessageBuffer>> implements SessionDefineNetworkChannelsEvent.Builder.Initial<M> {
private HandlerEntry<M> handler;
private SidedHandlerEntry<M> clientbound;
private SidedHandlerEntry<M> serverbound;
private String tag;
private String beforeTag;
private String afterTag;
private final Consumer<RegistrationImpl<M>> registrationCallback;
private boolean registered;
public NetworkDefinitionBuilder(@NonNull Consumer<RegistrationImpl<M>> registrationCallback) {
this.registrationCallback = registrationCallback;
}
public SessionDefineNetworkChannelsEvent.Builder.@NonNull Initial<M> pipeline(@NonNull Consumer<Pipeline> pipeline) {
Objects.requireNonNull(pipeline, "pipeline");
PipelineImpl impl = new PipelineImpl();
pipeline.accept(impl);
this.tag = impl.tag;
this.beforeTag = impl.beforeTag;
this.afterTag = impl.afterTag;
return this;
}
@Override
public SessionDefineNetworkChannelsEvent.Builder.@NonNull Sided<M> clientbound(MessageHandler.@NonNull Sided<M> handler) {
return this.clientbound(MessagePriority.NORMAL, handler);
}
@Override
public SessionDefineNetworkChannelsEvent.Builder.@NonNull Sided<M> clientbound(@NonNull MessagePriority priority, MessageHandler.@NonNull Sided<M> handler) {
Objects.requireNonNull(priority, "priority");
Objects.requireNonNull(handler, "handler");
this.clientbound = new SidedHandlerEntry<>(priority, handler);
return this;
}
@Override
public SessionDefineNetworkChannelsEvent.Builder.@NonNull Sided<M> serverbound(MessageHandler.@NonNull Sided<M> handler) {
return this.serverbound(MessagePriority.NORMAL, handler);
}
@Override
public SessionDefineNetworkChannelsEvent.Builder.@NonNull Sided<M> serverbound(@NonNull MessagePriority priority, MessageHandler.@NonNull Sided<M> handler) {
Objects.requireNonNull(priority, "priority");
Objects.requireNonNull(handler, "handler");
this.serverbound = new SidedHandlerEntry<>(priority, handler);
return this;
}
@Override
public SessionDefineNetworkChannelsEvent.Builder.@NonNull Bidirectional<M> bidirectional(@NonNull MessageHandler<M> handler) {
return this.bidirectional(MessagePriority.NORMAL, handler);
}
@Override
public SessionDefineNetworkChannelsEvent.Builder.@NonNull Bidirectional<M> bidirectional(@NonNull MessagePriority priority, @NonNull MessageHandler<M> handler) {
Objects.requireNonNull(priority, "priority");
Objects.requireNonNull(handler, "handler");
this.handler = new HandlerEntry<>(priority, handler);
return this;
}
@Override
public SessionDefineNetworkChannelsEvent.@NonNull Registration<M> register() {
Preconditions.checkState(!this.registered, "This message has already been registered");
Preconditions.checkState(this.handler == null || (this.clientbound == null && this.serverbound == null), "Cannot register both bidirectional and sided handlers for the same message");
RegistrationImpl<M> registration = new RegistrationImpl<>(
this.handler,
this.clientbound,
this.serverbound,
this.tag,
this.beforeTag,
this.afterTag
);
this.registered = true;
this.registrationCallback.accept(registration);
return registration;
}
private static final class PipelineImpl implements SessionDefineNetworkChannelsEvent.Builder.Pipeline {
private String tag;
private String beforeTag;
private String afterTag;
@Override
public SessionDefineNetworkChannelsEvent.Builder.@NonNull Pipeline tag(@NonNull String tag) {
this.tag = Objects.requireNonNull(tag, "tag");
return this;
}
@Override
public SessionDefineNetworkChannelsEvent.Builder.@NonNull Pipeline before(@NonNull String tag) {
this.beforeTag = Objects.requireNonNull(tag, "tag");
return this;
}
@Override
public SessionDefineNetworkChannelsEvent.Builder.@NonNull Pipeline after(@NonNull String tag) {
this.afterTag = Objects.requireNonNull(tag, "tag");
return this;
}
}
public record RegistrationImpl<M extends Message<? extends MessageBuffer>>(
HandlerEntry<M> handler,
SidedHandlerEntry<M> clientbound,
SidedHandlerEntry<M> serverbound,
String tag,
String beforeTag,
String afterTag
) implements SessionDefineNetworkChannelsEvent.Registration<M> {
}
public record SidedHandlerEntry<T extends Message<? extends MessageBuffer>>(
MessagePriority priority,
MessageHandler.Sided<T> handler
) {
}
public record HandlerEntry<T extends Message<? extends MessageBuffer>>(
MessagePriority priority,
MessageHandler<T> handler
) {
}
}

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.network;
package org.geysermc.geyser.network;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -37,19 +37,16 @@ import java.util.Objects;
* This channel is used for listening to communication over
* packets between the server and client and can be used to
* send or receive packets.
* @since 2.8.2
*/
public class PacketChannel extends ExternalNetworkChannel {
private static final String PACKET_CHANNEL_KEY = "packet";
private final int packetId;
private final Class<?> packetType;
protected PacketChannel(@NonNull String key, @NonNegative int packetId, @NonNull Class<?> packetType) {
super(Identifier.of(PACKET_CHANNEL_KEY, key));
public PacketChannel(@NonNull String key, @NonNegative int packetId, @NonNull Class<?> packetType) {
super(Identifier.of(PACKET_CHANNEL_KEY, key), packetType);
this.packetId = packetId;
this.packetType = packetType;
}
/**
@@ -62,16 +59,6 @@ public class PacketChannel extends ExternalNetworkChannel {
return this.packetId;
}
/**
* Gets the type of the packet associated with this channel.
*
* @return the class of the packet type
*/
@NonNull
public Class<?> packetType() {
return this.packetType;
}
/**
* {@inheritDoc}
*/

View File

@@ -33,7 +33,7 @@ import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.geysermc.geyser.api.network.message.Message;
import org.geysermc.geyser.session.GeyserSession;
public record BedrockPacketMessage(@NonNull BedrockPacket packet) implements Message.PacketWrapped<ByteBufMessageBuffer> {
public record BedrockPacketMessage<T extends BedrockPacket>(@NonNull T packet) implements Message.PacketWrapped<ByteBufMessageBuffer, T> {
@SuppressWarnings("unchecked")
public void postProcess(@NonNull GeyserSession session, @NonNull ByteBufMessageBuffer buffer) {

View File

@@ -29,7 +29,6 @@ import io.netty.buffer.Unpooled;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.protocol.common.util.VarInts;
import org.geysermc.geyser.api.network.message.MessageCodec;
import org.jetbrains.annotations.NotNull;
import java.nio.charset.StandardCharsets;
@@ -41,62 +40,62 @@ public class ByteBufCodec implements MessageCodec<ByteBufMessageBuffer> {
}
@Override
public boolean readBoolean(@NotNull ByteBufMessageBuffer buffer) {
public boolean readBoolean(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readBoolean();
}
@Override
public byte readByte(@NotNull ByteBufMessageBuffer buffer) {
public byte readByte(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readByte();
}
@Override
public short readShort(@NotNull ByteBufMessageBuffer buffer) {
public short readShort(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readShort();
}
@Override
public int readInt(@NotNull ByteBufMessageBuffer buffer) {
public int readInt(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readInt();
}
@Override
public float readFloat(@NotNull ByteBufMessageBuffer buffer) {
public float readFloat(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readFloat();
}
@Override
public double readDouble(@NotNull ByteBufMessageBuffer buffer) {
public double readDouble(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readDouble();
}
@Override
public long readLong(@NotNull ByteBufMessageBuffer buffer) {
public long readLong(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readLong();
}
@Override
public int readVarInt(@NotNull ByteBufMessageBuffer buffer) {
public int readVarInt(@NonNull ByteBufMessageBuffer buffer) {
return VarInts.readInt(buffer.buffer());
}
@Override
public int readUnsignedVarInt(@NotNull ByteBufMessageBuffer buffer) {
public int readUnsignedVarInt(@NonNull ByteBufMessageBuffer buffer) {
return VarInts.readUnsignedInt(buffer.buffer());
}
@Override
public long readVarLong(@NotNull ByteBufMessageBuffer buffer) {
public long readVarLong(@NonNull ByteBufMessageBuffer buffer) {
return VarInts.readLong(buffer.buffer());
}
@Override
public long readUnsignedVarLong(@NotNull ByteBufMessageBuffer buffer) {
public long readUnsignedVarLong(@NonNull ByteBufMessageBuffer buffer) {
return VarInts.readUnsignedLong(buffer.buffer());
}
@Override
public @NotNull String readString(@NotNull ByteBufMessageBuffer buffer) {
public @NonNull String readString(@NonNull ByteBufMessageBuffer buffer) {
int size = VarInts.readUnsignedInt(buffer.buffer());
byte[] bytes = new byte[size];
buffer.buffer().readBytes(bytes);
@@ -105,74 +104,74 @@ public class ByteBufCodec implements MessageCodec<ByteBufMessageBuffer> {
}
@Override
public void writeBoolean(@NotNull ByteBufMessageBuffer buffer, boolean value) {
public void writeBoolean(@NonNull ByteBufMessageBuffer buffer, boolean value) {
buffer.buffer().writeBoolean(value);
}
@Override
public void writeByte(@NotNull ByteBufMessageBuffer buffer, byte value) {
public void writeByte(@NonNull ByteBufMessageBuffer buffer, byte value) {
buffer.buffer().writeByte(value);
}
@Override
public void writeShort(@NotNull ByteBufMessageBuffer buffer, short value) {
public void writeShort(@NonNull ByteBufMessageBuffer buffer, short value) {
buffer.buffer().writeShort(value);
}
@Override
public void writeInt(@NotNull ByteBufMessageBuffer buffer, int value) {
public void writeInt(@NonNull ByteBufMessageBuffer buffer, int value) {
buffer.buffer().writeInt(value);
}
@Override
public void writeFloat(@NotNull ByteBufMessageBuffer buffer, float value) {
public void writeFloat(@NonNull ByteBufMessageBuffer buffer, float value) {
buffer.buffer().writeFloat(value);
}
@Override
public void writeDouble(@NotNull ByteBufMessageBuffer buffer, double value) {
public void writeDouble(@NonNull ByteBufMessageBuffer buffer, double value) {
buffer.buffer().writeDouble(value);
}
@Override
public void writeLong(@NotNull ByteBufMessageBuffer buffer, long value) {
public void writeLong(@NonNull ByteBufMessageBuffer buffer, long value) {
buffer.buffer().writeLong(value);
}
@Override
public void writeVarInt(@NotNull ByteBufMessageBuffer buffer, int value) {
public void writeVarInt(@NonNull ByteBufMessageBuffer buffer, int value) {
VarInts.writeInt(buffer.buffer(), value);
}
@Override
public void writeUnsignedVarInt(@NotNull ByteBufMessageBuffer buffer, int value) {
public void writeUnsignedVarInt(@NonNull ByteBufMessageBuffer buffer, int value) {
VarInts.writeUnsignedInt(buffer.buffer(), value);
}
@Override
public void writeVarLong(@NotNull ByteBufMessageBuffer buffer, long value) {
public void writeVarLong(@NonNull ByteBufMessageBuffer buffer, long value) {
VarInts.writeLong(buffer.buffer(), value);
}
@Override
public void writeUnsignedVarLong(@NotNull ByteBufMessageBuffer buffer, long value) {
public void writeUnsignedVarLong(@NonNull ByteBufMessageBuffer buffer, long value) {
VarInts.writeUnsignedLong(buffer.buffer(), value);
}
@Override
public void writeString(@NotNull ByteBufMessageBuffer buffer, @NonNull String value) {
public void writeString(@NonNull ByteBufMessageBuffer buffer, @NonNull String value) {
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
VarInts.writeUnsignedInt(buffer.buffer(), bytes.length);
buffer.buffer().writeBytes(bytes);
}
@Override
public @NotNull ByteBufMessageBuffer createBuffer() {
public @NonNull ByteBufMessageBuffer createBuffer() {
return new ByteBufMessageBuffer(this);
}
@Override
public @NotNull ByteBufMessageBuffer createBuffer(byte @NotNull [] data) {
public @NonNull ByteBufMessageBuffer createBuffer(byte @NonNull [] data) {
return new ByteBufMessageBuffer(this, Unpooled.wrappedBuffer(data));
}
}

View File

@@ -29,7 +29,6 @@ import io.netty.buffer.Unpooled;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.protocol.common.util.VarInts;
import org.geysermc.geyser.api.network.message.MessageCodec;
import org.jetbrains.annotations.NotNull;
import java.nio.charset.StandardCharsets;
@@ -39,62 +38,62 @@ public class ByteBufCodecLE implements MessageCodec<ByteBufMessageBuffer> {
}
@Override
public boolean readBoolean(@NotNull ByteBufMessageBuffer buffer) {
public boolean readBoolean(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readBoolean();
}
@Override
public byte readByte(@NotNull ByteBufMessageBuffer buffer) {
public byte readByte(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readByte();
}
@Override
public short readShort(@NotNull ByteBufMessageBuffer buffer) {
public short readShort(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readShortLE();
}
@Override
public int readInt(@NotNull ByteBufMessageBuffer buffer) {
public int readInt(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readIntLE();
}
@Override
public float readFloat(@NotNull ByteBufMessageBuffer buffer) {
public float readFloat(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readFloatLE();
}
@Override
public double readDouble(@NotNull ByteBufMessageBuffer buffer) {
public double readDouble(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readDoubleLE();
}
@Override
public long readLong(@NotNull ByteBufMessageBuffer buffer) {
public long readLong(@NonNull ByteBufMessageBuffer buffer) {
return buffer.buffer().readLongLE();
}
@Override
public int readVarInt(@NotNull ByteBufMessageBuffer buffer) {
public int readVarInt(@NonNull ByteBufMessageBuffer buffer) {
return VarInts.readInt(buffer.buffer());
}
@Override
public int readUnsignedVarInt(@NotNull ByteBufMessageBuffer buffer) {
public int readUnsignedVarInt(@NonNull ByteBufMessageBuffer buffer) {
return VarInts.readUnsignedInt(buffer.buffer());
}
@Override
public long readVarLong(@NotNull ByteBufMessageBuffer buffer) {
public long readVarLong(@NonNull ByteBufMessageBuffer buffer) {
return VarInts.readLong(buffer.buffer());
}
@Override
public long readUnsignedVarLong(@NotNull ByteBufMessageBuffer buffer) {
public long readUnsignedVarLong(@NonNull ByteBufMessageBuffer buffer) {
return VarInts.readUnsignedLong(buffer.buffer());
}
@Override
public @NotNull String readString(@NotNull ByteBufMessageBuffer buffer) {
public @NonNull String readString(@NonNull ByteBufMessageBuffer buffer) {
int size = VarInts.readUnsignedInt(buffer.buffer());
byte[] bytes = new byte[size];
buffer.buffer().readBytes(bytes);
@@ -103,74 +102,74 @@ public class ByteBufCodecLE implements MessageCodec<ByteBufMessageBuffer> {
}
@Override
public void writeBoolean(@NotNull ByteBufMessageBuffer buffer, boolean value) {
public void writeBoolean(@NonNull ByteBufMessageBuffer buffer, boolean value) {
buffer.buffer().writeBoolean(value);
}
@Override
public void writeByte(@NotNull ByteBufMessageBuffer buffer, byte value) {
public void writeByte(@NonNull ByteBufMessageBuffer buffer, byte value) {
buffer.buffer().writeByte(value);
}
@Override
public void writeShort(@NotNull ByteBufMessageBuffer buffer, short value) {
public void writeShort(@NonNull ByteBufMessageBuffer buffer, short value) {
buffer.buffer().writeShortLE(value);
}
@Override
public void writeInt(@NotNull ByteBufMessageBuffer buffer, int value) {
public void writeInt(@NonNull ByteBufMessageBuffer buffer, int value) {
buffer.buffer().writeIntLE(value);
}
@Override
public void writeFloat(@NotNull ByteBufMessageBuffer buffer, float value) {
public void writeFloat(@NonNull ByteBufMessageBuffer buffer, float value) {
buffer.buffer().writeFloatLE(value);
}
@Override
public void writeDouble(@NotNull ByteBufMessageBuffer buffer, double value) {
public void writeDouble(@NonNull ByteBufMessageBuffer buffer, double value) {
buffer.buffer().writeDoubleLE(value);
}
@Override
public void writeLong(@NotNull ByteBufMessageBuffer buffer, long value) {
public void writeLong(@NonNull ByteBufMessageBuffer buffer, long value) {
buffer.buffer().writeLongLE(value);
}
@Override
public void writeVarInt(@NotNull ByteBufMessageBuffer buffer, int value) {
public void writeVarInt(@NonNull ByteBufMessageBuffer buffer, int value) {
VarInts.writeInt(buffer.buffer(), value);
}
@Override
public void writeUnsignedVarInt(@NotNull ByteBufMessageBuffer buffer, int value) {
public void writeUnsignedVarInt(@NonNull ByteBufMessageBuffer buffer, int value) {
VarInts.writeUnsignedInt(buffer.buffer(), value);
}
@Override
public void writeVarLong(@NotNull ByteBufMessageBuffer buffer, long value) {
public void writeVarLong(@NonNull ByteBufMessageBuffer buffer, long value) {
VarInts.writeLong(buffer.buffer(), value);
}
@Override
public void writeUnsignedVarLong(@NotNull ByteBufMessageBuffer buffer, long value) {
public void writeUnsignedVarLong(@NonNull ByteBufMessageBuffer buffer, long value) {
VarInts.writeUnsignedLong(buffer.buffer(), value);
}
@Override
public void writeString(@NotNull ByteBufMessageBuffer buffer, @NonNull String value) {
public void writeString(@NonNull ByteBufMessageBuffer buffer, @NonNull String value) {
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
VarInts.writeUnsignedInt(buffer.buffer(), bytes.length);
buffer.buffer().writeBytes(bytes);
}
@Override
public @NotNull ByteBufMessageBuffer createBuffer() {
public @NonNull ByteBufMessageBuffer createBuffer() {
return new ByteBufMessageBuffer(this);
}
@Override
public @NotNull ByteBufMessageBuffer createBuffer(byte @NotNull [] data) {
public @NonNull ByteBufMessageBuffer createBuffer(byte @NonNull [] data) {
return new ByteBufMessageBuffer(this, Unpooled.wrappedBuffer(data));
}
}

View File

@@ -29,7 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.network.message.Message;
import org.geysermc.mcprotocollib.protocol.codec.MinecraftPacket;
public record JavaPacketMessage(MinecraftPacket packet) implements Message.PacketWrapped<ByteBufMessageBuffer> {
public record JavaPacketMessage<T extends MinecraftPacket>(T packet) implements Message.PacketWrapped<ByteBufMessageBuffer, T> {
@Override
public void encode(@NonNull ByteBufMessageBuffer buffer) {

View File

@@ -40,6 +40,7 @@ import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.item.custom.CustomItemData;
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
import org.geysermc.geyser.api.network.NetworkChannel;
import org.geysermc.geyser.api.network.message.Message;
import org.geysermc.geyser.api.pack.PathPackCodec;
import org.geysermc.geyser.api.pack.UrlPackCodec;
@@ -61,6 +62,9 @@ import org.geysermc.geyser.level.block.GeyserGeometryComponent;
import org.geysermc.geyser.level.block.GeyserJavaBlockState;
import org.geysermc.geyser.level.block.GeyserMaterialInstance;
import org.geysermc.geyser.level.block.GeyserNonVanillaCustomBlockData;
import org.geysermc.geyser.network.ExtensionNetworkChannel;
import org.geysermc.geyser.network.ExternalNetworkChannel;
import org.geysermc.geyser.network.PacketChannel;
import org.geysermc.geyser.network.message.BedrockPacketMessage;
import org.geysermc.geyser.network.message.JavaPacketMessage;
import org.geysermc.geyser.pack.option.GeyserPriorityOption;
@@ -72,6 +76,7 @@ import org.geysermc.geyser.registry.provider.ProviderSupplier;
import org.geysermc.mcprotocollib.protocol.codec.MinecraftPacket;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Map;
/**
@@ -121,14 +126,29 @@ public class ProviderRegistryLoader implements RegistryLoader<Map<Class<?>, Prov
}
if (args[0] instanceof BedrockPacket bedrockPacket) {
return new BedrockPacketMessage(bedrockPacket);
return new BedrockPacketMessage<>(bedrockPacket);
} else if (args[0] instanceof MinecraftPacket javaPacket) {
return new JavaPacketMessage(javaPacket);
return new JavaPacketMessage<>(javaPacket);
} else {
throw new IllegalArgumentException("Unsupported packet type: " + args[0].getClass().getName());
}
});
providers.put(NetworkChannel.class, args -> {
// Extension network channel
if (args.length == 3 && args[0] instanceof Extension extension && args[1] instanceof String channel && args[2] instanceof Class<?> messageType) {
return new ExtensionNetworkChannel(extension, channel, messageType);
} else if (args.length == 3 && args[0] instanceof String key && args[1] instanceof Integer packetId && args[2] instanceof Class<?> packetType) {
// Packet channel
return new PacketChannel(key, packetId, packetType);
} else if (args.length == 2 && args[0] instanceof Identifier identifier && args[1] instanceof Class<?> messageType) {
// External network channel
return new ExternalNetworkChannel(identifier, messageType);
} else {
throw new IllegalArgumentException("Unknown arguments provided for NetworkChannel provider. Could not create a channel given the arguments: " + Arrays.toString(args));
}
});
return providers;
}
}

View File

@@ -40,8 +40,6 @@ import org.geysermc.erosion.packet.geyserbound.GeyserboundPacket;
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.event.EventBus;
import org.geysermc.geyser.api.event.java.ServerReceiveNetworkMessageEvent;
import org.geysermc.geyser.api.network.MessageDirection;
import org.geysermc.geyser.api.network.NetworkChannel;
import org.geysermc.geyser.api.network.message.Message;
@@ -54,6 +52,9 @@ import org.geysermc.mcprotocollib.protocol.packet.common.clientbound.Clientbound
import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundCustomPayloadPacket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Translator(packet = ClientboundCustomPayloadPacket.class)
public class JavaCustomPayloadTranslator extends PacketTranslator<ClientboundCustomPayloadPacket> {
@@ -153,16 +154,28 @@ public class JavaCustomPayloadTranslator extends PacketTranslator<ClientboundCus
} else {
session.ensureInEventLoop(() -> {
GeyserNetworkManager networkManager = session.getNetworkManager();
NetworkChannel networkChannel = NetworkChannel.of(packet.getChannel().namespace(), packet.getChannel().value());
if (!networkManager.registeredChannels().contains(networkChannel)) {
logger.debug("Received a custom payload for an unregistered channel: " + networkChannel.channel());
Set<NetworkChannel> channels = networkManager.registeredChannels();
if (channels.isEmpty()) {
this.logger.debug("Received a custom payload for an unregistered channel: " + channel);
return;
}
Message<MessageBuffer> message = networkManager.createMessage(networkChannel, packet.getData());
List<NetworkChannel> identifiedChannels = new ArrayList<>();
for (NetworkChannel registeredChannel : channels) {
if (!registeredChannel.isPacket() && registeredChannel.identifier().toString().equals(channel)) {
identifiedChannels.add(registeredChannel);
}
}
EventBus<?> eventBus = session.getGeyser().getEventBus();
eventBus.fire(new ServerReceiveNetworkMessageEvent(session, networkChannel, message, MessageDirection.CLIENTBOUND));
if (identifiedChannels.isEmpty()) {
this.logger.debug("Received a custom payload for an unregistered channel: " + channel);
return;
}
for (NetworkChannel networkChannel : identifiedChannels) {
List<Message<MessageBuffer>> message = networkManager.createMessages(networkChannel, packet.getData());
networkManager.handleMessages(networkChannel, message, MessageDirection.CLIENTBOUND);
}
});
}
}

View File

@@ -137,7 +137,8 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
if (!registeredChannels.isEmpty()) {
String channels = registeredChannels
.stream()
.map(channel -> channel.key() + ":" + channel.channel())
.filter(channel -> !channel.isPacket())
.map(channel -> channel.identifier().namespace() + ":" + channel.identifier().path())
.collect(Collectors.joining("\0"));
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(register, channels.getBytes(StandardCharsets.UTF_8)));

View File

@@ -0,0 +1,208 @@
/*
* 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;
import net.kyori.adventure.key.Key;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.network.MessageDirection;
import org.geysermc.geyser.api.network.NetworkChannel;
import org.geysermc.geyser.api.network.message.DataType;
import org.geysermc.geyser.api.network.message.Message;
import org.geysermc.geyser.api.network.message.MessageBuffer;
import org.geysermc.geyser.api.network.message.MessageHandler;
import org.geysermc.geyser.api.network.message.MessagePriority;
import org.geysermc.geyser.impl.IdentifierImpl;
import org.geysermc.geyser.session.GeyserSession;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.geysermc.geyser.util.GeyserMockContext.mockContext;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class MessageRegistrationOrderTest {
private final NetworkChannel xuidChannel = new ExternalNetworkChannel(new IdentifierImpl(Key.key("geyser_test", "xuid")), XuidMessage.class);
@Test
void testRegistrationOrder() {
mockContext(context -> {
GeyserSession session = context.mock(GeyserSession.class);
GeyserNetworkManager manager = new GeyserNetworkManager(session);
AtomicInteger state = new AtomicInteger(0);
new NetworkDefinitionBuilder<XuidMessage>(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration))
.clientbound(MessagePriority.EARLY, message -> {
assertEquals(0, state.getAndIncrement());
return MessageHandler.State.UNHANDLED;
})
.register();
new NetworkDefinitionBuilder<XuidMessage>(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration))
.clientbound(MessagePriority.LATE, message -> {
assertEquals(2, state.getAndIncrement());
return MessageHandler.State.UNHANDLED;
})
.register();
new NetworkDefinitionBuilder<XuidMessage>(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration))
.clientbound(MessagePriority.NORMAL, message -> {
assertEquals(1, state.getAndIncrement());
return MessageHandler.State.UNHANDLED;
})
.register();
manager.handleMessages(this.xuidChannel, List.of(new XuidMessage("test-xuid")), MessageDirection.CLIENTBOUND);
});
}
@Test
void testPipelineTags() {
mockContext(context -> {
GeyserSession session = context.mock(GeyserSession.class);
GeyserNetworkManager manager = new GeyserNetworkManager(session);
AtomicInteger state = new AtomicInteger(0);
new NetworkDefinitionBuilder<XuidMessage>(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration))
.clientbound(message -> {
assertEquals(2, state.getAndIncrement());
return MessageHandler.State.UNHANDLED;
})
.pipeline(pipeline -> {
pipeline.tag("monitor");
})
.register();
new NetworkDefinitionBuilder<XuidMessage>(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration))
.clientbound(message -> {
assertEquals(1, state.getAndIncrement());
return MessageHandler.State.UNHANDLED;
})
.pipeline(pipeline -> {
pipeline.tag("initial-handler");
pipeline.before("monitor");
})
.register();
new NetworkDefinitionBuilder<XuidMessage>(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration))
.clientbound(message -> {
assertEquals(3, state.getAndIncrement());
return MessageHandler.State.UNHANDLED;
})
.pipeline(pipeline -> {
pipeline.tag("tail");
pipeline.after("monitor");
})
.register();
// No pipeline, so should automatically be added to the tail
new NetworkDefinitionBuilder<XuidMessage>(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration))
.clientbound(message -> {
assertEquals(4, state.getAndIncrement());
return MessageHandler.State.UNHANDLED;
})
.register();
// Early priority - should come first regardless of tail structure
new NetworkDefinitionBuilder<XuidMessage>(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration))
.clientbound(MessagePriority.EARLY, message -> {
assertEquals(0, state.getAndIncrement());
return MessageHandler.State.UNHANDLED;
})
.register();
manager.handleMessages(this.xuidChannel, List.of(new XuidMessage("test-xuid")), MessageDirection.CLIENTBOUND);
});
}
@Test
void testMixedHandlers() {
mockContext(context -> {
GeyserSession session = context.mock(GeyserSession.class);
GeyserNetworkManager manager = new GeyserNetworkManager(session);
AtomicInteger state = new AtomicInteger(0);
// Simple early clientbound
new NetworkDefinitionBuilder<XuidMessage>(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration))
.clientbound(MessagePriority.EARLY, message -> {
assertEquals(0, state.getAndIncrement());
return MessageHandler.State.UNHANDLED;
})
.register();
// Late clientbound but first serverbound
new NetworkDefinitionBuilder<XuidMessage>(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration))
.clientbound(MessagePriority.LATE, message -> {
assertEquals(2, state.getAndIncrement());
return MessageHandler.State.UNHANDLED;
})
.serverbound(MessagePriority.FIRST, message -> {
fail("Serverbound handler should not be called in this test");
return MessageHandler.State.UNHANDLED;
})
.register();
// Normal (default) bidirectional
new NetworkDefinitionBuilder<XuidMessage>(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration))
.bidirectional((message, direction) -> {
if (direction == MessageDirection.SERVERBOUND) {
fail("Serverbound handler should not be called in this test");
return MessageHandler.State.UNHANDLED;
}
assertEquals(1, state.getAndIncrement());
return MessageHandler.State.UNHANDLED;
})
.register();
// Serverbound only - should never be called
new NetworkDefinitionBuilder<XuidMessage>(registration -> manager.onRegister(this.xuidChannel, XuidMessage::new, registration))
.serverbound(MessagePriority.NORMAL, message -> {
fail("Serverbound handler should not be called in this test");
return MessageHandler.State.UNHANDLED;
})
.register();
manager.handleMessages(this.xuidChannel, List.of(new XuidMessage("test-xuid")), MessageDirection.CLIENTBOUND);
});
}
public record XuidMessage(String xuid) implements Message.Simple {
public XuidMessage(MessageBuffer buffer) {
this(buffer.read(DataType.STRING));
}
@Override
public void encode(@NonNull MessageBuffer buffer) {
buffer.write(DataType.STRING, this.xuid);
}
}
}

View File

@@ -25,8 +25,8 @@
package org.geysermc.geyser.scoreboard.network;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.spawnPlayerSilently;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;

View File

@@ -61,7 +61,7 @@ import java.util.EnumSet;
import java.util.Optional;
import java.util.UUID;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.*;
import static org.geysermc.geyser.util.AssertUtils.*;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;

View File

@@ -25,8 +25,8 @@
package org.geysermc.geyser.scoreboard.network.belowname;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.spawnPlayerSilently;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;

View File

@@ -25,8 +25,8 @@
package org.geysermc.geyser.scoreboard.network.playerlist;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
import java.util.List;

View File

@@ -25,9 +25,9 @@
package org.geysermc.geyser.scoreboard.network.server;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacketMatch;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNextPacketMatch;
import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.spawnPlayer;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
import static org.junit.jupiter.api.Assertions.assertEquals;

View File

@@ -25,8 +25,8 @@
package org.geysermc.geyser.scoreboard.network.sidebar;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
import java.util.List;

View File

@@ -25,8 +25,8 @@
package org.geysermc.geyser.scoreboard.network.sidebar;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
import java.util.List;

View File

@@ -25,8 +25,8 @@
package org.geysermc.geyser.scoreboard.network.sidebar;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNextPacket;
import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
import java.util.List;

View File

@@ -25,9 +25,9 @@
package org.geysermc.geyser.scoreboard.network.util;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacketType;
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContext.mockContext;
import static org.geysermc.geyser.util.AssertUtils.assertNextPacketType;
import static org.geysermc.geyser.util.AssertUtils.assertNoNextPacket;
import static org.geysermc.geyser.util.GeyserMockContext.mockContext;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
@@ -45,6 +45,7 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.EntityCache;
import org.geysermc.geyser.session.cache.WorldCache;
import org.geysermc.geyser.session.cache.waypoint.WaypointCache;
import org.geysermc.geyser.util.GeyserMockContext;
import org.mockito.stubbing.Answer;
public class GeyserMockContextScoreboard {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
* Copyright (c) 2024-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
@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network.util;
package org.geysermc.geyser.util;
import java.util.Collections;
import java.util.function.Consumer;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
* Copyright (c) 2024-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
@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network.util;
package org.geysermc.geyser.util;
import org.geysermc.geyser.GeyserLogger;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
* Copyright (c) 2024-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
@@ -23,22 +23,23 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.scoreboard.network.util;
package org.geysermc.geyser.util;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.event.GeyserEventBus;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.mockito.Mockito;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public class GeyserMockContext {
private final List<Object> mocksAndSpies = new ArrayList<>();
@@ -58,6 +59,9 @@ public class GeyserMockContext {
var logger = context.storeObject(new EmptyGeyserLogger());
when(geyserImpl.getLogger()).thenReturn(logger);
var eventBus = context.mock(GeyserEventBus.class);
when(geyserImpl.getEventBus()).thenReturn(eventBus);
try (var geyserImplMock = mockStatic(GeyserImpl.class)) {
geyserImplMock.when(GeyserImpl::getInstance).thenReturn(geyserImpl);
@@ -113,7 +117,7 @@ public class GeyserMockContext {
return mockOrSpy(GeyserSession.class);
}
void addPacket(BedrockPacket packet) {
public void addPacket(BedrockPacket packet) {
packets.add(packet);
}