-
-
Notifications
You must be signed in to change notification settings - Fork 179
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
Network refactoring #277
Merged
Merged
Network refactoring #277
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sciwhiz12
added
enhancement
New (or improvement to existing) feature or request
1.20
Targeted at Minecraft 1.20
breaking change
Breaks binary compatibility
labels
Nov 19, 2023
marchermans
force-pushed
the
feature/1.20.x/networking
branch
from
November 19, 2023 12:05
ea77500
to
87fbaa6
Compare
marchermans
force-pushed
the
feature/1.20.x/networking
branch
from
December 13, 2023 13:38
87fbaa6
to
e119f09
Compare
marchermans
force-pushed
the
feature/1.20.x/networking
branch
from
December 14, 2023 16:23
11b9e0b
to
75dba8c
Compare
Minecraftschurli
force-pushed
the
feature/1.20.x/networking
branch
from
December 16, 2023 19:40
872efe9
to
3a52359
Compare
Several features including status are still missing.
…o respect mod loading order.
…nfiguration phase is talking to a vanilla client.
…-modded connection scenario
marchermans
force-pushed
the
feature/1.20.x/networking
branch
from
December 18, 2023 17:34
3a480b4
to
d317efd
Compare
pupnewfster
requested changes
Dec 30, 2023
src/main/java/net/neoforged/neoforge/network/filters/GenericPacketSplitter.java
Outdated
Show resolved
Hide resolved
src/main/java/net/neoforged/neoforge/network/filters/GenericPacketSplitter.java
Outdated
Show resolved
Hide resolved
pupnewfster
previously approved these changes
Dec 31, 2023
How are these |
pupnewfster
requested changes
Dec 31, 2023
src/main/java/net/neoforged/neoforge/network/filters/GenericPacketSplitter.java
Outdated
Show resolved
Hide resolved
src/main/java/net/neoforged/neoforge/registries/RegistrySnapshot.java
Outdated
Show resolved
Hide resolved
src/main/java/net/neoforged/neoforge/registries/RegistrySnapshot.java
Outdated
Show resolved
Hide resolved
pupnewfster
reviewed
Dec 31, 2023
patches/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
Outdated
Show resolved
Hide resolved
pupnewfster
previously approved these changes
Dec 31, 2023
XFactHD
previously approved these changes
Dec 31, 2023
marchermans
dismissed stale reviews from XFactHD and pupnewfster
via
December 31, 2023 18:12
921045b
pupnewfster
approved these changes
Dec 31, 2023
7 tasks
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
1.20
Targeted at Minecraft 1.20
breaking change
Breaks binary compatibility
enhancement
New (or improvement to existing) feature or request
last call
Planned to be resolved by the end of the week, awaiting any last-minute comments
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
TLDR.
CustomPacketPayload
and native to MinecraftIntroduction
Reworked networking
Welcome to the reworked networking pull request for NeoForged.
The changes in this pull request implement the configuration network protocol designed by @modmuss50 with some minor modifications. I'm sharing more on those below.
Tip
The NeoForge @neoforged/maintainers did perform an analysis of the networking protocol. It is a bit rough, but the resulting packet flows, and analysis comments can be found here
Consequences of the split packet type introduced in 1.20.2
During the introduction of 1.20.2, Mojang created in practice two distinct packet systems, one set known as the login packets and one set known as the play packets. Within those play packets, a unique phase was created; during this phase, the client configuration is supposed to happen.
During the port to 1.20.2, we needed more time to address these unique structures within the packet layout that Mojang introduced, create APIs that allow modders to use these new packet systems to their fullest, and give access to this unique configuration phase. This PR achieves this.
SimpleChannel and EventChannel
Original Forge contained two different ways of implementing a custom network channel. A simple registration-based approach is known as SimpleChannel, and a system that fires events for each packet received is known as EventChannel.
To simplify using networking and make interacting with new packet layouts easier, it was decided to rework both implementations into a single system, combining the best of both worlds.
Implementation
New network payload handling
The system is based on the
CustomPacketPayload
definition that Mojang uses to represent the content of custom packets. Internally, they register their custom payloads, used mainly for debugging, to a map. We need to extend this map so that a modder can send and receive a custom implementation of thisCustomPacketPayload
. This is what a significant part of this PR achieves. Modders can introduce new CustomPacketPayload implementations by registering them during theRegisterPacketHandlerEvent
.The registrar
Any modder can request a registrar for any namespace he or she desires; however, it is recommended that a modder only request a registrar for his or her namespace. Once a registrar is retrieved from the event, the modder can then configure the registrar with two different options:
versioned(String version)
to configure a version for all payloads registered after the call andoptional()
to mark all payloads registered after that call as not requiring a receiving side. An example of the registrar configuration can be found here:Note
The registrar is an immutable object; calling
versioned(String version)
oroptional()
on an instance of the registrar will cause a new instance with the desired configuration to be created.Caution
The registrar loses its validity the moment the scope of the event has been left. Registering payload handlers outside of the event handling scope will result in those payloads not being known to the system and not being sent over the connection.
The registrar offers six different endpoints, three pairs of 2, to register new payloads. A pair exists for the play phase, one pair for the special configuration subphase, and one pair of methods for both.
Important
It is impossible to register custom payloads that should be sent during the login phase of the connection. And this PR offers, as such, no infrastructure to achieve this.
Within each pair of registration methods for a phase, two variants (as such, six methods) are available. One that registers the same handler for both sides of the connection and one that takes a consumer, allowing for the configuration of single-sided or differentially handled payloads.
An example of the signature of the methods for the configuration phase is as follows:
For the play phase, similar methods exist.
For payloads that are supposed to be sent during both the play phase as well as the configuration phase, there also exists a pair, however here, the handler is a common supertype of the handling callbacks, and the handler only has a reduced common superset of the information available to both phase handlers to it available.
Payload discrimination
When considering this system, you might, right-fully so, ask how the system keeps the different payload types apart from each other. This is achieved by the system it-self. It writes a discriminator ID to the connection before invoking the actualy writing mechanic for a payload. Conversely on the client side, the discrimininator is read first, before a reader is looked up so that the rest of the payload can be read.
Note
The system handles reading and writing the discriminator it-self, as a modder you do not need to take care of this yourself.
The value of the discriminator during writing is retrieved from the
CustomPacketPayload#id()
method, and is not allowed to be null.The value against which the id, read from the connection, is compared to find a reader, is the one that is given to the registrar as the first argument.
It is as such of the upmost importance that you return the same value, as in the same resource location, from both the
id()
method on the payload instance, as well as given the same value to the registrar when registering your payload.Tip
We recommond that you store your ID in a
public static final ResourceLocation ID = new ResourceLocation("mod_id", "payload_id")
field, and reference that in both places.Warning
Given that the id is used as a discriminator, it is important that you use a unique value, especially for the ResourceLocations path section for each payload type. If you try to register the same id twice, the registrar will throw an exception. If you try to register an id with a namespace other then the one the registrar is for, the registrar will throw an exception. You are free to request registrars for other namespaces then your own.
Payload reading
Payload reading happens via the vanilla method, implementing the
FriendlyByteBuf.Reader<T>
functional interface. During the registration of the payload type, as can be seen above, you need to pass an implementation of this interface so that a new instance of the payload can be created by the system when a custom payload packet with it as payload arrives on the receiving end.Tip
As we recommend that your payload implementations are
record
s in java, instead of classes, we also recommend that you create a custom constructor with the record, to read the records fields from the buffer. This constructor can then be passed as a method reference for the reader implementation. So ifSimplePayload(String somethign) {}
is your normal record, then addingSimplePayload(FriendlyByteBuf buf) { this(buf.readUtf()); }
as a constructor to theSimplePayload
record will allow you to pass it as a method referenceSimplePayload::new
to the registar when it asks for an implmentation ofFriendlyByteBuf.Reader<SimplePayload>
Warning
There is no guarantee with respect to which thread the reading callback is invoked upon. It is as such important to note that the method can be called on many threads in parallel if the same packet is received on many connections simultaniously.
Payload writing
The
CustomPacketPayload
interface contains a method:write(FriendlyByteBuf)
method. This method is invoked when it is time to write your payload to the network connection. There is no guarantee on the thread this is invoked from.Warning
There is no guarantee concerning which thread the writing method is invoked upon. It is as such important to note that the method can be called on many threads in parallel if it is being send over several connections at once.
Caution
Payloads are only read and written if sent over a connection. This means that the host of a single-player world (even if exposed to LAN) has packets and, as such, payloads transferred in memory. This means that for those payloads, no write method is invoked, and no reader is called. Only the handler is invoked!
Payload handling
Once a payload has been written, transmitted and read, the payload handler is invoked. This handler is again looked up with the id of the payload, and then invoked with the context of the receiving end. In practice each payload handler takes two arguments:
Warning
Processing of the payload, as in the invocation of the handler callback, happens on the network thread, and can as such happen in parallel with other payloads of the same type being handled. If you need to ensure that the payload is processed on the main thread, serially, see the
ISynchronizedWorkHandler
available in the context under theworkHandler()
method.The context
The context contains information, callbacks and entry points, to access the surrounding network system, the main thread, as well as handling processing other packets, or completing configuration tasks.
ReplyHandler
The reply handler can be used to quickly send a payload back to the sender. It is for example usefull to send and answer to a query packet, or simply send an acknowledge that you received and processed the payload. You still need to register the return payload.
PacketHandler
In case you implement a packet splitting mechanism, whether that splits on full vanilla packets, or custom packet payloads, the
IPacketHandler
interface gives you access the the start of the processing pipeline, allowing you to process other payloads immediatly.Note
This does not transmit payloads, it purely allows for the receiving end to process additional packets or payloads constructed in memory during the processing of your payload
The packet handler also gives you access to a
disconnect(Component)
method allowing you to terminate the connection at will, showing the given component as reason to the user.WorkHandler
The work handler allows you to schedule work on the main thread of the receiving side. This might be the
Minecraft
class instance if the logical receiving side is the client, or theMinecraftServer
instance if the receiving side is the server.The system uses
CompleteableFuture
instances so you can schedule different follow up tasks, if need be.PacketFlow
The context will indicate via the packet flow, what the receiving side currently is. If it is a server bound flow, then handler is currently being invoked in the context of the server. Is it a client bound flow, then the handler is currently being invoked in the context of the client.
ConnectionProtocol
The current active connection protocol. Usefull if you have raw bytes of a packet wrapped in your payload, allowing your handler to decode the inner packet or payload, before passing it to the
IPacketHandler
for processing.ChannelHandlerContext
The netty channel handling context that is currently processing the network connection on which the payload was received.
This context can be used to retrieve the raw underlying connection via
ConnectionUtils
or again be used to process raw bytes of inner packets and payloads.Sender
This is an
Optional
containing a player, if and only if the handler is being invoked on the server side, but not during the configuration phase (there is simply no player instance yet, as such there is no way to get the information)TaskCompletedHandler
This is a special contextual value only available to payloads for the configuration, which are specifically registered as such, to indicate that a specific configuration task has been completed, and that the next one can be started.
Future additions to the contexts
We are fully aware that these entry points might not be all information you as a modder need to process a packet. In general it is pretty easy to extend the interfaces and records. They were specifically designed to allow for simple PRs in the future to add to them, so please do not hesitate to create a quick PR to add your needed data to the context.
Packet Sending
In tandem with the refactoring of the channel registration mechanic we added new tools and systems to allow you to easier send a custom payload to different targets.
PacketDistributor
This wrapper class now has the ability to process custom payloads solely. Its instances and targets can be passed around since they are immutable. Several methods on extension classes will accept these, to facilitate easy transfer of payloads.
Extension objects
We extended several vanilla types allowing for them to accept payloads as well, not just packets. Examples are chunk sections, listeners, entities and players.
Netty information
In contrast with the past, we now store a lot of information related to the connection, for example the negotiated payload types, on the connection object itself. To this accord we added several attributes to store this information in. Allthough the attributes are considered part of the internal API, if somebody wants to dig through the internal guts, they should feel free to do so, however there is no guarantee that we would change them around in future releases, even outside the BC window.
Configuration tasks on client join
Tasks
Vanilla now provides a centralized way to perform tasks and jobs that need to be performed when a player joins. The player won't be instantiated or added to a world untill these tasks are completed.
Under normal circumstances these tasks are implementations of the
ConfigurationTask
interface, with a single method:start(Consumer<Packet<?>>)
. However, this is subpar in our situation. Modders should never really have to touch raw Packets to perform their duties, only payloads. And as such it was decided to have modders implement theICustomConfigurationTask
interface from neoforge itself. This provides a wrapper around theConfigurationTask
signature and allows for sending payloads instead of packets, by implementingrun(Consumer<CustomPacketPayload>)
instead. The given consumer will then automatically convert the payload to a packet and send it to the client that is being configured.Note
In practice an instance of
ICustomConfigurationTask
is also an instance ofConfigurationTask
as one extends the other. But to provide the ability for this to be a functional interface, the methodStart
is implemented as a default implementation. It is not recommended to also override that.OnGameConfigurationEvent
This event is fired to collect all tasks that should be run, and allows for the registration of
ICustomConfigurationTask
instances to the listener. It is not possible to register the vanillaConfigurationTask
instances.Tip
This event is fired on the mod bus, to preserve dependency order. Given that configuration tasks can only be ran in order of registration, you can safely assume that configuration tasks of your dependencies have been ran before yours.
Forge changes:
Moved configuration sync, registry sync, and tier registry sync to configuration phase tasks.
Bundle packet processing
In 1.20.2 Mojang introduced the bundling system for packets, which is a core component that allows for packets to be processed together. And started using sparsely. We anticipate modders want to use this system, so we adapted it to accept custom payloads. You will find a
sendBundled(CustomPacketPayload[] payloads)
method on theServerGamePacketListener
Warning
Packet bundling is only supported during the play phase of the network protocol. It can not be used during the configuration phase of the protocol.
Opening menus with custom data
In the past Forge supported opening UIs from the server side with additional data, via
NetworkHooks.openScreen(...)
. this system has been moved and is now part of the serverServerPlayer
extension. You can call the methodopenMenu
with the same parameters.Spawning entities with custom data
The previous networking implementation allowed for the spawning of custom entities via an overridden method on the
Entity
class:getAddEntityPacket.
Modders that wanted to support custom additional data to be processed on the client side when the entity spawned, could override this method and return a packet from the method:NetworkHooks.getEntitySpawningPacket(...)
This system has now been refactored (as Mojang spawns and processes the entity packets with a bundle). The core of this new framework is the method
sendPairingData(ServerPlayer, Consumer<CustomPacketPayload>)
on theEntity
class. There are now two methods to configure this.Using a custom payload
By overriding the method and invoking the consumer with a custom payload, you are guaranteed that your payload will be processed immediately after the spawning packet. You are free to do whatever you want, however, we recommend you at least transfer the entity id, as vanilla does, to retrieve the entity instance when your packet arrives.
Using the
IEntityWithComplexSpawn
interfaceImplementing this interface on your entity forces you to implement two methods:
writeSpawnData
andreadSpawnData
. These methods, respectively, are invoked when an entity spawn bundle is generated and when the entity has been spawned.Removal of custom entity creation code in
EntityType
It is no longer possible to use the entity spawn packet code mentioned above to create a different entity class on the client side. This was due to the refactoring of the payload mechanics. And the reliance on the vanilla spawning bundle.
Forge Packetsplitter
This PR reimplements the vanilla packet splitter forge, previously contained in Forge, and expands its usage to any packet (other than the packet sent when stuff is split).
Due to the new mechanics, a packet or payload is now roughly split into 8MB chunks, as per the vanilla protocol limitations.
Modlist transfer
Currently, the new protocol does not support sending the mod list across and only works on a channel and registry content basis. However, we do intend to work with the Fabric team on expanding the protocol in such a way that the server is made aware of what kind of mods the client has installed.
We deem this necessary to allow server owners to block mods that would allow cheating, for example, an XRay mod, etc.
This will be added in another PR after further reviewing our stance and implementation possibilities.
Todo:
Follow up PRs