-
-
Couldn't load subscription status.
- Fork 773
Geyser Networking API #5685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Geyser Networking API #5685
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks very promising! Left some notes. A few more things that don't fit anywhere:
- it might be worth to separate listening / receiving packet listeners? This could be used to avoid extra processing for packets that are sent both ways (where one is only interested in one direction)
- Currently; some bedrock packet (de)serializers are modified by Geyser. This would result in incorrect values for some packets... do we want to note that / have a mechanism to disable that?
api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDefineNetworkChannelsEvent.java
Show resolved
Hide resolved
api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java
Outdated
Show resolved
Hide resolved
api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java
Outdated
Show resolved
Hide resolved
api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java
Show resolved
Hide resolved
| * Represents a network channel associated with an extension. | ||
| * @since 2.8.2 | ||
| */ | ||
| public class ExtensionNetworkChannel implements NetworkChannel { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's make this an interface + geyser impl?
| * @param <T> the type of the message buffer | ||
| * @since 2.8.2 | ||
| */ | ||
| public interface MessageFactory<T extends MessageBuffer> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe worth marking this as a functional interface?
| /** | ||
| * Represents a network channel associated with a packet. | ||
| * <p> | ||
| * This channel is used for communication of packets between the server and client. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * This channel is used for communication of packets between the server and client. | |
| * This channel is used for listening to communication over packets between the server and client, and can be used to send or receive packets. |
| } | ||
| } | ||
|
|
||
| interface PacketWrapped<T extends MessageBuffer> extends PacketBase<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing javadocs
| } else if (packetBase instanceof Message.Packet packet) { | ||
| PacketChannel packetChannel = (PacketChannel) channel; | ||
| int packetId = packetChannel.packetId(); | ||
|
|
||
| ByteBufMessageBuffer buffer = ByteBufCodec.INSTANCE_LE.createBuffer(); | ||
| packet.encode(buffer); | ||
|
|
||
| BedrockCodec codec = this.session.getUpstream().getSession().getCodec(); | ||
| BedrockCodecHelper helper = this.session.getUpstream().getCodecHelper(); | ||
|
|
||
| BedrockPacket bedrockPacket = codec.tryDecode(helper, buffer.buffer(), packetId); | ||
| if (bedrockPacket == null) { | ||
| throw new IllegalArgumentException("No Bedrock packet definition found for packet ID: " + packetId); | ||
| } | ||
|
|
||
| // Clientbound packets are sent upstream, serverbound packets are sent downstream | ||
| if (direction == MessageDirection.CLIENTBOUND) { | ||
| this.session.sendUpstreamPacket(bedrockPacket); | ||
| } else { | ||
| this.session.getUpstream().getSession().getPacketHandler().handlePacket(bedrockPacket); | ||
| } | ||
| } | ||
|
|
||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assumes that all sent packets are strictly Bedrock edition packets; which could pose an issue if we at some point want to allow the same for Java edition packets :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like supporting Java packets here would end up being relatively niche. Since we are only dealing with Bedrock users, it makes the most sense that only Bedrock packets would work here when being sent to the client.
That being said, if we wanted to support one or the other, it would be rather challenging to distinguish them unless we want to make this declared in the network channel. Currently, unless you are using MCPL or Cloudburst and wrapping the packet, only custom Bedrock packet messages are supported. Having both may cause some confusion, but I am open to ideas.
| } | ||
|
|
||
| this.definitions.put(channel, codec); | ||
| if (channel.isPacket()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could also be solved using an instanceof check - is there a reason for explicitly forcing declaring whether a channel is a packet channel opposed to instanceof checking?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small optimization - checking a boolean is cheaper than an instanceof, especially with hot code where I could see people calling this method.
|
Another thing that came up recently is whether we'd allow forwarding packets to a backend server - even without an extension present. This could be combined with this PR, assuming we'd want to do that.. thoughts? |
|
😢😢😢😢 |
Introduces a networking API to Geyser which can be used in extensions. This supports both sending and listening for plugin messages, and allows Geyser to intercept these, as well as intercepting and sending packets.
Plugin Messages
In order to start sending and listening for plugin messages, you need to tell Geyser which channels to listen for. This can be done by listening on the
SessionDefineNetworkChannelsEventand registering the channel for the connection.Registering the channel
Example:
In the
registermethod, you also need to define the creator of the message that will be sent. This effectively turns the content from aMessageBufferinto your type.As a recommended principle, this should be a
recordwith two constructors: one creating the object just using its values (an all-args constructor), then one for theMessageBuffer. It should also extendMessage.Simple.Example:
A
MessageBuffersupports both reading and writing, with built-inDataTypes for common values (int, string, long, varint, etc.). CustomDataTypes can easily be added withDataType#of.Sending the message
Now that your channel and its corresponding message creator is registered, it can now be either listened for, or sent out. This can easily be sent out by fetching the
NetworkManagerfrom aGeyserConnection, and running the#sendmethod.Example:
Note: When sending a message to the server, ensure the direction is
SERVERBOUNDas that will specify that the server should receive the message. If you were to useCLIENTBOUNDfor example, that would send it to the client.Now on your server, you can listen for this message! Here is an example using Bukkit:
Listening for messages
In some cases, it may be more desirable to do the opposite of what was shown above - sending information to your Geyser extension. This too is supported! In that case, you just need to listen for the
ServerReceiveNetworkMessageEventand ensure that the value coming in is the same message. As an example:And for plugin messages, that is about it!
Packets
This API also supports listening for packets. This works alongside the plugin messaging component to it. There are two methods: defining the packet structure using API, or using the Cloudburst API. Both are explained in more detail below.
Packet Structure Using API
When constructing your
NetworkChannel, a special method needs to be used:NetworkChannel#packet. This creates aNetworkChannelthat is capable of listening for packets.Example:
The first parameter is the name of the packet - this is not particularly important and at this point in time can be anything. The following two are very important though: the packet ID and the actual message. These should correspond to real packets in Minecraft: Bedrock Edition. Like above, this should be registered in the
SessionDefineNetworkChannelsEvent.The
AnimateMessageon the other hand from the example, is the actual implementation of the animate packet in Bedrock. Here is how that looks:Now that the
AnimateMessagehas been created, we can now send it:Or if you wanted to listen for other players swinging their arms:
It is also worth noting that the
ServerReceiveNetworkMessageEventisCancellable, meaning if you wanted to intercept a packet and cancel it outright, simply runServerReceiveNetworkMessageEvent#setCancelled(true)and the packet will be cancelled.Packet Structure Using Cloudburst Protocol Library.
While the first example required a bit more manual work, in some cases that may be more desired for fine-turning the entire process. However, it is also possible to simply just use the raw packet objects themselves as provided by the Cloudburst Protocol Library.
In addition to depending on the Geyser API, depending on Cloudburst Protocol too is all that is required here - no need to rely on any Geyser internals!
Creating the channel is nearly identical as above, except rather than a custom
AnimateMessage, just use the packet directly like so:When registering the channel though, it's a tad different. This can be done like so:
And sending can be done like so:
However, if you want to listen for one of these, it can be a tad more complicated. But, it can be done like so:
Note that this is only an initial draft and subject to change! Testing and feedback are more than welcome.
A gist of what was covered above with slightly more can be found here: https://gist.github.com/Redned235/3cf05b62290fa9eec70d8b4f3fa22f67