#Networking Protocol
1 messages · Page 1 of 1 (latest)
yep plans changed
Nice 🙂 Let me know of any questions. Im quite happy to help test it as well.
yea just getting set up
Okey
We can solve the “is this a compatible server check”, by using a different networking protocol versioning scheme if forge is an incompatible server. Current suggestion is to simply negate the vanilla protocol version. A vanilla client would as such reject the connection, while a forge enabled client would be aware of this
Assuming you are talking about the server list info, that shouldn't be necessary, the additional ping data we attach to theClientboundStatusResponsePacketshould be sufficient for that. Adding that is fine for a vanilla client because the data gets sent as JSON and we just modify the codec to add an optional entry. In fact, checking for the presence of this optional data is already how the server list compat check tests for vanilla vs. Neo connection
Actually I am not .......
The ServerStatus object can do that magic because the ClientIntentPacket already filters out all the bullshit before hand
So we know what kind of connection it is / can be
But I am currently debating ripping clientintent out
And doing that kind of check differently
However ServerStatus could likely stay the same
As it is a JSON structure that is being send
I am trying to determine
Where the whole FML logic of: Is this a compatible connection
Should live
And in what place in the connection
We have to rip out the crap in client intent because it either automatically makes us incompatible with a vanilla server in a way that produces nonsensical results for the user or requires crap like the host name hack. We also have to keep in mind that a proxy/hub server could use the config phase to move a player between a vanilla and a modded server, so we can't use the early login stuff to detect the server type and compatibility.
Actually
A proxy server can't do that
Wheter it uses the configuration phase or not
At least not right now
Why wouldn't it?
Right now: Because all connection properties and connection channel configuration happens way earlier
It happens during handshake
At the moment I am investigating what the concequences of it are when we move all of that back into the configuration phase
As far as I can tell
It should be possible
Cause nothing really of interest uses those properties earlier
Yeah, I'd say there's nothing stopping use from moving all our stuff to the config phase
Technically it is used during STATUS as well
But only implicitly
And it only works because of the JSON hack
The status packet is, as far as I can tell, only used in the STATUS mode and that's the only packet where the JSON hack applies.
Absolutely
What I find particularly stupid is the fact that mojang does not encode the string length before it writes a UTF string
but always hardcodes the limit XD
Actually, they do write the length, it's hidden in Utf8String.write(). Took me a minute to notice as well though
Right now I am trying to decern wether there is a better solution
So why did they set the limit to 32000+- characters then?!?
That is stupid as all hell
No idea whatsoever 😄
What I am trying to figure out is, is if it is possible to figureout what kind of client the server is talking too and what kind of server the client is talking to, from a combination of the handshake and server status packets
wait, are we questioning why Mojang has a limit to strings instead of being potentially boundless 
No
(or practically a very chonky string)
I am wondering why it is 32000-+ characters
100000 is perfectly fine
And then a modded environment would likely never run into any issues what so ever
But that is just me
both are arbitrary numbers
and the former is probably because it's near to a power of two
and that it's probably well sufficient for a vanilla environment, which is what they mostly program in mind for
so largest positive value for a signed short
it's max short
Yeah
It is max.short
Why it is a short
I don't know
Because shorts are on the VM level just ints......
but they aren't shorts across the wire 
They don't go across the wire
there's probably some sort of compatibility concern at play
That is the point
The number that does go on the wire is a varint

I gotta leave for now. I'll be taking another look over the ideas either later tonight or tomorrow depending on how late I'm home again
Question: Do we want to keep Event based networking around?
how do they currently work?
Via NetworkInstance
does anyone other than maty know how to query waifu? If so we could try to see what mods make use of that so that we can see if there is some use case that isn't covered by the non-event based networking
I hate these events @mental ingot
Cool
I don't see a big reason for them, I just figure we may as well look if there is some use case that isn't satisifed by other means? Given we have the technology @velvet whale
You get a single event on the modbus to register your packets and listeners
And that is it
Btw I think what xfact suggests with keeping the fml client/server detection for the config phase makes sense
Good
I already designed that in mind
And I also ripped the compression hack from server status
wait does this mean we don't have to manually index them anymore?
I found a much much much better way
No you need to giv them a resloc
ah interesting
Or for the event a single string is used
We basically rip everything out
And just use the mojang implementation
Cause that is literally what it does itself
That system just holds a map: resloc <-> packet/listener
And just looks up the resloc of the packet (as its id) in that map
And then invokes the listener
so they kept mods in mind while rewriting their networking. How kind of them
since the switch to grafana, I don't even think I know how to do it
remind me, why did waifu switch from metabase to grafana
because metabase doesn't have general oauth in the CE, just google and ldap
@opal ferry In your Fabric PR and its Diagramm, you do a bunch of "minecraft:register" and "ping/pong" packets, combined with "c:version" and "c:register". Me @errant summit are trying to understand what their function is? And why Forge would need something like that?
I understand that it is used to detect whether or not it is a modded client at all that we are using
And to potentially disconnect them if need be
You need to know what channels the client/server can send/recieve on during configuration, and before play starts.
Our new design does not support custom channels
And solely relies on the Vanilla CustomPacketPayload / CustomPacketQuery system
You cannot start a configuration task such as registry sync unless you know the client will be able to handle it. It will stall the login otherwise.
channels are part of the custom payload packet system, Orion
Aaahh
Okey You are basically saying
Make sure the client and server have the same handlers list
To ensure that shit does not stall
Basically I need to ask for a mod list
And disconnect the client if it mismatches
Doesn't need to be the same, the other side needs to know if the receiver is able to handle the packet, it can either choose to send it, do nothing, or disconnect.
There is no way to know that for sure
A handler implementation could do jack shit
Im surprised you didnt already have something for this? Using the existing minecraft:register packets?
Nope
c:register is the list of client-mandatory channels the server has basically
we have yes
I dont see how you couldnt not have this?
This was the API have for configuration tasks. ServerPlayerConfigurationTask is a vanilla class.
minecraft:register is the core of the whole idea of disconnecting clients that have incompatible versions
Actually it is not..... that is handled completely differently in the handshake, me and @errant summit Already found that code hours ago
The critical bit is the ServerConfigurationNetworking.canSend
In fabric?
Thats part of our API, and is driven by the minecraft:register packets
Yeah
But functionally that is not really relevant
I mean
As long as a task is ran, and eventually triggers the "finishCurrentTask" method in the listener
It does not matter
Right?
But if you start the task and the client cannot complete it you stall the login
I could send an opertunistic packet from the server to client
Immediatly continue
And call it a day
But why is that something Forge needs to care for. I mean in practice a mod could do 100 things that prevents the queue from being drained stalling the login
Vanilla does it
The whole point is you can wait for the client to finish that configuration task before moving onto the next one.
that's a faulty mod then
Yeah
But there is not really anything we can do to prevent that
I understand the concept
I am just saying that it is redundant
But what if the mod wants to still support a vanilla client, or a client without the mod?
See that is what makes more sense
But that exchange of information
Does not need to happen on minecraft:register
It can literally happen with any packet
As long as we collect that information ahead of time, before the task queue is build
Because architecturally that is a massive headache!
How come?
Right now, the architecture for the network is a single event
and that event does all
Literally all
Well actually
I might be able to shoe horn the channel in there somehow
Maybe you need to change that architecture, thats up to you how to want to do that. It should support minecrft:register. We managed it without any problems.
Actually I would argue the other way around
The API should simply be: this.connection.send(somepayload)
That is all that should be needed
But its not, you need to know if the other side can receive
Sure
But I know that even without that channel
Because I can do that with a custom payload
And if I don't get a payload back
Before I get the pong
I have the same inforamtion
Without ever touching that channel
The whole idea of minecraft:register is to send that information in a platform agonistic format.
Its been that way since it was added, 10 years ago
That is nice, but the age of the thing is not really relevant to me right now, I want a functional reason why it needs to be that packet........
A forge client can not and will likely never connect to a fabric server or vice versa........
The modloader/plugin server sends one of those packets for all of the channels registered by mods. You wouldnt expect everyone to have their own packets for this.
What about a forge client connecting to a spigot/paper server? There is no reason why a forge client cannot connect to a fabric server.
The point is that our new architecture has no channels other then the ones mojang has it self
Okey hold on
I am starting to see my miscommunication
i'd have to agree with modmuss here -- I see no good reason why a Forge client cannot connect to a Fabric server (assuming both sides have no mods installed that add things like blockstates)
Sure it still can
That makes no sense to me? A channel being mod_id:some_packet or minecraft:register right send via the custom payload? And the phase can be login/configuration/play
Except we don't have mod_id:some_packet
Why not? I dont see how that can even work?
if a mod registers that channel, yes we do
They are all custom paylaods
Yes, and each custom payload gets its own channel. I think there is some confusion somewhere
I thought we clarified already earlier that channel in this reference means the channel used in the custom payload packet
You can not set the Packet channel equal to the Payload Id
Sorry sci, but please stop talking
I am trying to wrap my head around the definition of a channel
I think we have 2 definition's of a channel 😄
Because when I read the old EventNetworkChannel
which I am trying to define and clarify here
so excuse me if I get offended at your attempt to shut me up while trying to clarify it for your sake
The problem is that you, schurli and modmuss are all talking at the same time
i'm bowing out of this conversation now, good luckl
And that is simply one person to many
Thanks sorry if I offended you, but I needed one less source of information
Okey
if you are talking about SimpleChannel we just removed that whole system in favour of using CustomPacketPayload directly
i will still bow out of this conversation, as it seems my effort to help is unwanted
again, good luck
I only barely remember the networking stuff, but isn't the difference here that fabric uses a separate packet ID for each mod, or something along those lines, and forge bundles them together and differentiates the packet internally?
Your effort is wanted, just not know in parallel with other people
Yeah that is what I knew
And what was implemented before
Which is why I am several confused
Because the concept of a channel and the concept of a payload id
Are significantly different
what we have now (after the rewrite) is that modders directly use CustomPacketPayload and not SimpleChannel or EventNetworkChannel
If i understand modmuss
This might be the confusion, The custom payload id is the channel id in fabric.
So yeah
The concept of a channel on forge and fabric are different
But not anymore 😄
Cause we are now in the same line as you
Because now the custom payload id is the channel id
Ok, cool. Thats what makes the most sense imo.
It does
Which is why we went with it
Now the question becomes
You said the "minecraft:register" payload id was/is the packet that is send to the client to request the handlers, and then also the id of the packet send back?
Server sends its handlers in minecraft:register, and then client responds with its.
Okey
I mean technically only thing we would need is the information from the client
So that when the configuration task list is build mods are aware what is available on the other side
We also use this to detect a vanilla client but sending a ping to the client right after minecraft:register, and seeing if we a minecraft:register or pong back first.
Yeah that ping/pong I understand
But technically it suffices to send a "request" packet with what the client knows
I mean yeah we can do that in the same packet
But that feels like sending unneeded information over the wire
But what ever
Here is what we have so far: https://hackmd.io/@neoforged/SkL_ikSXa#New-implementation
I am still trying to design a system for "versioning" of individual packets
schurli is that hackmd being actively updated as you guys further finalize things? (if so we probably want to pin it to this thread so that people can easily see the high level overview of the system if they look at this thread)
Yes
Also
We are pretty close to this working
If a mod registers at least one of these, then the server is considered not compatible with vanilla
this might be problematic for some vanilla+ use cases
and mods with optional server or client components
For the people wondering, what are the currently planned changes:
- Removal of EventNetworkChannel
- Removal of SimpleChannel
- Addition of Payload type registration
- Request of known payload handlers at the client side
- Ability to construct custom configuration tasks and process them
- Modders will get the list of known client handlers exposed, as well as a way to send them and immediatly continue without waiting for a response (allows optional configuration tasks to be added always)
That has been softened already
We are now querrying the remote handlers, as modmuss suggested
great
And wether or not the connection is vanilla capable will be determined based on a more advanced configuration system
Allthough I am still struggling to define "versioned networking"
Primarily when it comes to changes within a given packet
IMO each mod should be able to give a protocol version
and we disconnect if the protocol versions don't match
which is basically what happened in 1.20.1 and earlier
You can already do that, by simply using a different proper name for that particular payload.....
yes but there it was per channel and more flexible than a ==
that's not great
does that mean that payloads are required to match? not sure how that works
having to change the name of your packet if it changes seems suboptimal
Yes obviously....
or for mods like mek where I want to ensure client and server versions match regardless of if the networking changed as it makes things a lot easier to debug and stuff
At best I can probably offer from kind of versioning system were you supply a number per packet
And those have to line up
Actually I think I can offer something better
I have a design in mind
That however means I am going to need to do some modifications to our internal code
But the new system won't be as flexible as it is right now
Well actually
@opal ferry Question: Why is channel version negotiation after the configuration phase?
In my mind it should be before the configuration phase
Right after the minecraft:register and ping/pong pogo
not really that different -- in Forge, the *Channels (SimpleChannel and EventNetworkChannel) correspond to a channel ID in the custom payload packet
and then SimpleChannel have multiple packets through the use of the IndexedMessageCodec (which prepends a discriminator before each message payload, to identify what packet it is)
which is what I wanted to say earlier, before I got angry
according to the above and iirc past conversations I've had, other modloader frameworks (and technically vanilla) consider each channel ID on the custom payload packet as a separate 'packet' (in the sense of a SimpleChannel packet)
I think it can go there in the current spec, its not made very clear in the diagram. I can double check. The reason its not, is there isn't really need for it atm as we only use it to send the play phase channels.
I do agree that it may make more sense (for future addions?) to go sooner, I can possibly change that on our end.
Well the point i am asking is that the whole "minecraft:register" dance is done to guarentee to the best of our abilities that it does not hang yes?
But what if two channels exist with the same id
But which are incompatible with each other
Say V1 and V2 of that particular channel
Let me see what we do, I have a feeling we either crash, or overwrite and warn.
Yeah, if 2 mods register a handler for the same channel, 1 will win.
What do you mean diffrent versions?
registerGlobalReceiver(Identifier channelName, ConfigurationChannelHandler channelHandler) is the API we have?
No, both configuration and play
Like the c:versions stuff?
Okey, but why are you doing c:versions for configuration after you have used those channels already to do the configuration?
We don't need them for configuration, they are used to prepare for the play phase during configuration.
but how do you send data inside a configuration task? (which is needed for them to complete)
Like
YOu do the whole minecraft:register dance to guarantee that all tasks complete
But you do not check that the channel in use are actually compatible with each other?
The version in c:versions is basically only for the c: packets. I do think moving it before regsync and what not makes sense.
so if a mod has changed a packet that is used during configuration what will happen if the server has one version and the client another?
undefined behavior on fabric
that's why I suggest introducing the concept of a "mod network protocol version" or whatever you want to call it, that modders can bump every time they make an incompatible change to their networkign protocol
Are modders going to be bothered to do that 😄
debatable 😄
SimpleChannel has its own protocol version, and no i havent touched mine since it was implemented 😛
having to bump the versions manually definitely isnt impossible to do, its just another step that needs to be thought about
Id default something like that to requiring a matching mod version. And then for the people that can be bothered have something to allow them to do what they want.

I have the simple channel protocol version gradle-filled-in to be exactly the mod version, because it solved many issues of server-client-with-different-version bugs that kept getting reported.
having the mod versions require to match would be identical (preferrable) behavior
thats actually something i should implement into my mod for the time being, because i also get a lot of similar bug reports
read a few messages up... SimpleChannel is gone
i know, the point of the message was that modders are not often bothered to change their protocol versions manually
I'm myself on version 5 for my mod
Okey question, likely open here for for like 18 hours:
Working on the versioning scheme for the new protocol system, if you had to chose between implementing several records, all with the same interface that represent your packet, which is then passed to a single handler, or several handlers but one packet type for your versioning strategy, how would you go about it?
Or better said which would you prefer
Vote on this message with 1️⃣ to indicate a single handler with multiple message types, or 2️⃣ for the opposite, a single message type but multiple handlers. Note this is just an informal questioning.
- I don't particularly care about protocol versioning because I expect people to use the same version of my mods on both sides.
Why would we drop simpleimpl?
(but if it's about how one would contribute network-related things to neo, then I would pick 1️⃣ , I think)
It is basically simple. The main difference is that you don't have an int. But resloc
Are you going to send packet names with each packet or negotiate an ID during the initial connection?
Right now we already do. So what we basically designed is to strip the intermediary "index" from the payload
do you mean getting rid of IndexedMessageCodec, and its use of the discriminator/index prepended to the message payload
(I mean 'message' in the sense of a SimpleChannel packet, to differentiate from 'packet' in the sense of the custom payload packet)
is SimpleChannel gone, or refactored heavily?
Gone
But its spirit exists in the new design
That is what a new registration on a play packet looks like
ALlthough it is its internal representation
Basically a resloc for its id (is needed for the vanilla map), the class to type the relevant registation, and do casts related to the payload type, a handler for when the payload arrives, and a reader to read it from the network
I am currently designing a wrapper around this
That allows you to fluidly define
Your versioning strategy
While also keeping a simple registation method
For people that want something very similar to the simple channel
That has literally 0 versioning what so ever XD
But is usefull int he case where you do: Hey I need a need a simple packet, don't care about the rest
Just send it
Somebody asked for javadoc?
There readable in read mode
Already fixed some mistakes 😄
Yes you can obviously send packets from the client to the server during config phase
i shall stab at thine javadocs once the PR's ready for review 
Yes, I am debating adding an option that generates the name dynamically from the class passed in
it's fine
I mean for me, maybe someone else has 200 packets an they will be like "fuck me"
Packet1, Packet2, ..., Packet199, Packet200 

well they already have classnames so...
yes I mean if they have to convert their names to resourcelocations
eh if it was me I'd just write a little script that will turn TitleCase into snake_case and be done with it
Does Vanilla already have that method? For the NBT changes datafixer
Except that your payload object has to return the same id
there is an id() method on the payload
Which has to return the value
orion did you push your changes?
I noticed
bump
I have not yet reached the state where I can gen patches
Okey
Work quited down
I just implemented the Negotiator
So that the channel can be negotiated
now needs to write unit tests for this :D
It is fine
It is just a class that checks for optional channels and all the other components
Immediatly found a bug
Hmmf
Strugling to create a way to deserialize packets per connection
The handlers (and with that the packets that can be processed) are declared statically
@errant summit You areound?
Okey, further progress, every connection, now stores its Network setup, aka all available custom payloads on the connection, as an attribute. This required some modifications to the connection protocol system to support deserialisation with a context (aka what channel the deserialization is happening on)
The client and server now negotiate the what version of a channel it is suppose to use
The following rules are now used to determine if two channels are compatible:
Once this is done, I can reimplement the missing payloads that forge previously had.
Do we expect mods to support multiple versions of a given channel?
They can if they want to
Is it worth the complexity? Is what I am asking
It is what several people asked for
Hmmm, where? I thought people said it'd be hard to manage
@errant summit Requested it
And right now it is not multiple version support
It is support one version on each end
But being able to support an entire range if need be
So say the client has v4 and the server v5
You can tell it that the server supports reading v4 and v5
However that logic in my current implementation is borked
s/none optional/non-optional, or even better, s/none optional/required
also, what's the return type of that method? the docs aren't too clear on that
NegotiationResult
and the NegotiationResult contains a nullable Component field?
makes me think Either<NegotiationResult, Component>
or possibly even DataResult<NegotiationResult>...?
We are not doing DFU types
We are not doing any data processing here
And that blocks any option in the future to extend the returned information
next thonk would then be a sealed class NegotiationResult with Success and Failure subclasses
It is a record
the idea i'm getting at here is to make sure that the return is statically known to be a success or a failure, without intermingling both types of results into a single object (and relying on a isSuccess() or such)
Stop trying to make this way more complicated then it actually needs to be
99% of this gets slattered with APIStatus.Internal
And then it does not matter what the return type is or is not
if the NegotiationResult contains both types for succeses and failures, the return type doc for the method should just say returns "the result of the negotiation"
and the docs for the NegotiationResult record should be descriptive itself
Can we do this when I make this public?
And not in the middle
I posted the jdoc
To get feedback on the rules
Not on the design of the API
Which I left specifically out of it
So that this would not happen
That fifth bullet in the docs could be simplified by being "the following conditions must all be true, otherwise negotiation fails:"
eh, fine
But yeah that section can indeed be reworked
But I realised 2 minutes after posting the screenshot
That my logic to base it of the preferred version is flawed
for the record (heh), I suggested doing the split of the result using sealed clases and success/failure subclasses partly for nullability safety
so instead of relying on a field being non-null to know its related fields are non-null (which may be contrary to their nullability annotations), we explicitly rely on subclasses which enforce non-null values for all their fields
would be useful if the failure state has additional information, such as what channels/components failed negotiation and such
Except that it does not matter, it introduces an additional check + cast
It contains a translateable component
That gets given the channel name of the failure
no programmatic access to the components/channels that caused the failure?
No, by design!
even by Forge's own prettified server disconnection screen?
so it'd just be one lump of info in a single translatable component?
There is a screen for incompatible mods
I am not aware of a screen for incompatible channels
Especially not since no code path on the client and server have been found by me and schurli that indicate such a thing
The server just triggers a disconnect message
But I might have overlooked it
That said
The new system allows us to technically implement this purely on the client side as well
my previous understanding, alongside my now-reading of the code, tells me the mod mismatch disconnection screen uses the mismatch information of the mod channels
https://github.com/neoforged/NeoForge/blob/c0f6ec7739f273e37e11e3e69110b152170748f3/src/main/java/net/neoforged/neoforge/network/HandshakeHandler.java#L224-L249
see how the information transmitted to the client is the channel mismatch info
you can even see how the client and server both disconnect with the same (literal) component message about the mismatched mod channel list
I see you've acknowledged the disconnect the server does, but I fail to see how the other code surrounding that disconnection was overlooked
unless you're talking about some other disconnection message which I don't know about, which is possible 

but still a regression
and one which I'm inclined not to like, since players may be accustomed to the new screen, and may find it jarring to be replaced with a simple message
I would tend to agree when it comes to incompatible mods
But network channels seems a stretch to mme
But yeah I can probably reimplement this tomorrow
mod (in)compatibility is partly determined by network channel (in)compatibility
(other determining factors being e.g., registries data)
at the very least, we shouldn't regress and hide away the channel mismatch info that we previously showed
particularly given iirc your previous statement on the possibility of using the mod version as the network channel version (in lieu of an explicitly set network channel version)
That is not actually possible
The network channel version is an int
I changed my mind on that
ah
It was a lot easier to implement
i expect the reasoning for moving from string versions to int versions in the PR 
Given that channel version bumps inherently imply a change in the logic of parsing or encoding channel data, a single int makes perfect sense to me
It's like using just the first version of semver
it's gonna suck for me, as someone who leverages the string version for stuff in Concord 
Whether or not it's backwards compatible, it implies a change in the logic of what that channel is sending
but I think I have a good enough workaround in mind, so continue
There's also vanilla precedent for this sort of stuff, though not networking - the resource/data pack versions
Did I miss the "disconnect" option on the damn client
Looks like there is no packet that is send to the server to gracefully terminate a connection
Wtf
Am I blind
Yeah
The protocol literally has no, disconnect gracefully option
The fuck
Okey
With that bullshit out of the way
I now implement all protocol changes needed
What is remaining is now all the packets and the configuration tasks
As well as some fixes to the negotiator to support the "modded connection" be broke screen
Okey packet filtering as modmuss described it is now implemented
And I documented the entire network registry
@buoyant pagoda So that custom screen will for sure get a regression
The best I can do is reasonable print out what is missing
The new system is much stricter with the data on the client side
And it is completely enforced from the server side
The client now gets a "hey: this is why you failed to connect per broken channel"-message
But that is it
Information about what mods exists etc
Are all not available to the client
does it not know what channels there are on the server? because that's a massive regression I'd consider a deal breaker
No
But the server tells it where it fails
The server basically now asks: Hey what channels do you have (id, preferred version, acceptable range, optional)
It then finds the common ground between its set of channels and the clients returned values
If successfull
It will send the a message back in the sense of: These are the channels we are going to use, with X as version (if applicable, a channel is not obliged to have a version)
If failure:
It will send a message back in the sense of: These are the incompatible channels, and for each channel the reason why, which could be anything from a mismatched version line, to a missing channel.
Allthough as it states right now: If there are missing channels which are none optional then the server won't do version finding
And only return the missing channels as failure reasons, which could potentially be changed.
Okey implemented the screen in a similar fashion now
Okey packet splitter has been implemented
For people wondering, this is how the packet registration API looks at the moment
What do you guys think?
what does common mean?
Useable in both configuration and play phases
So a common packet here
Because packet splitting can happen in both phases
that's gonna need clarification, because of the whole use of "common" with regards to sides, y'know
But there is a phase specific method as well
If you know a better term, I am all ears
I have no preferenence for common
hmmmm, will have to think on that
how do you specify the network direction for the packet?
No
It is automatically supported on both directions
Because vanilla makes no pratical difference when it comes to the payload serializatino
so mods cannot register packets that may only be received at one side?
because that's a regression
No
Why?
There is no functionality removed
Well the api surface is different
But there is no blocker to send the payload or not
FML allowed a packet to have a specific network direction, to guard against the possibility that a (malicious) client sends packets of the clientbound variety to the server, causing it to crash
i'd like an explanation, please
It just invokes the handler....
that doesn't sound like it resolves the issue I just described
You need to remember that these are not packets!
They are payloads
Within the concept of vanilla's packet system
you also need to remember that even in the past system, 'packets' were also just payloads carried over the vanilla custom payload packet
so I fail to see what situation has changed to invalidate checking payloads' reception side
In what way?
There is no concept of a side on the payload
It is just a pipe for data
With readers and senders on both ends
In what way can that fail?
Sure if you actually have the concept of unidirectional packets
Then yeah a receiving end would potentially not know how to read a payload coming the opposite side
But that really does not exist in this new design
So.....?
...what?
You always have a reader on both ends
You say you need a check on the receiving end to prevent it from reading an unknown packet.
Yes, yes you do.
And that is handled, by vanilla, if it is an unknown payload, it will literally discard it. If it is too big, it will discard it.
So I don't quite understand what is missing in your opinion?
imagine this scenario:
take a mod that registers a 'packet' (I am using the packet terminology here, but remember that this is and has always been a custom payload in the custom payload packet) which is handled by code that invokes client-only functions
in normal play, the mod expects that 'packet' to be sent only from the server to the client -- it would previously enforce this notion through a Optional.of(NetworkDirection.SERVER_TO_CLIENT) guard (which Forge enforces)
without that guard, a client may construct such a packet, send it to the server, which deserializes it and calls the handler
and the handler's reference to client-only functions will cause a crash
(and the Forge enforcement of the guard is "I will terminate this connection if I receive a 'packet' that's not meant for my side")
in your current API, as far as I can tell, you've removed that NetworkDirection (nevermind the Optional) parameter, which means mods have to manually implement that feature themselves, else they risk their packets being used for that purpose
It is indeed not in the registration, but in the handler context available
I am thinking
In practice however I wonder if that check suffices......
so you'd have mods duplicate the same set of code for every packet they have, rather than having this option be built-in to (and enforced by) Forge itself?
I mean especially in SimpleChannel the callback is a functional interface
Referencing the method alone would trigger the classload right?
I mean during registration
the side validation in Forge is done directly after it finds the associated NetworkInstance for the payload ID
I know
But that is not what I am referring to
SimpleChannel currently accepts a lambda to the handler of the packet
Correct?
Using a functional reference alone would trigger the classload and as such the above scenario
And immediatly crash the server on boot
yes, mods still bounce their client-only functions to a bouncer as per recommended modding practices
Okey
but irregardless, that means you're passing the buck of handling improperly sent/received packets from Forge itself to every single handler
So if they already need to do the whole if (client) { doClientShit() } in each handler anyway
There is no real loss then
I understand your concern
But that system alone would increase the complexity massively
And I am considering trying to balance that vs its needs
Especially, given that each modder already needs to do the above if-check anyway
they don't do a if (client) -- most mods (as I believe) just invoke doClientStuff() directly in the handler body, on the basis that their packet will only be invoked on the proper side due to Forge's validation
That 100% crashes though with SimpleChannel, so I highly doubt that
it doesn't
Sure it does
re: method name - perhaps rename common to anyPhase since it refers to phases rather than sides?
It doesn't because because the bouncer is static and therefore only causes class-loading on call
It is a lambda reference during registration, which triggers the classload of the method and as such the CNFE
Ahh right
Okey
Then I will need to think about this
Guarding against the call going through is one thing. But IMO a case like this should always cause a disconnect because it always either indicates a serious bug or malicious intent
and note that Forge's enforcement of the side is to terminate the connection -- because if a client is sending the wrong packets, it should be let go as its trying to crash the server (intentionally or not)
you would suggest mods either silently discard packets, or manually implement disconnect code
common is the name mojang uses, iirc
That is why it is named currently common 😄
Fine by me, people can also read javadoc 😛
See this is were my misunderstanding was
I thought that a static call is not sufficient
It has to be a WRAPPED static call
So if you did something like this:
Handler::onReceived
That it would still classload the static method
Because it is not wrapped
no, it is super lazy
static void handle(NetworkEvent.Context ctx) { ClientDoer.clientStuff(); } with a Packet::handle as the handler
that's what I meant by "just invoke doClientStuff() directly in the handler body"
Well that means I need to investigate this shit some more
This is the kind of structure we are referring to: https://github.com/XFactHD/FramedBlocks/blob/1.20/src/main/java/xfacthd/framedblocks/common/net/OpenSignScreenPacket.java#L24-L34. This thing would also work fine without the explicit side check because the method in ClientAccess is static
I understood that, but I also thought that that would crash when the handle method was referenced
funnily enough, we had a discussion on classloading in #maintenance-talk, which you did pop in for
sometimes I've wondered if the whole sidedness issue should just be solved by shipping client classes on the server lol
That is the pattern, I had envisioned people using
But sci somewhat rightly says that it should not be so verbose / copy-paste for this specific case
We actually do 😄
Check the binary patches for once 😄
You will notice some weird shit being created when you install a server
plus, the whole "wrong-sided packets should cause connection termination, because naughty client"
in theory you could just ship the client only ones as a stub that fails on initialization, which would allow you to reference them just fine as long as you never trigger initialization
(if a mod does that intentionally somehow, that's a very big bug on the mod's part)
We send them as full, and then have a transformation service that crashes when you try to load one 😄
I am trying to track this down how far this goes.......
oh so the whole RuntimeDistCleaner thing is just a massive hack to emulate client classes not being present? 😛
I mean does that then mean that a single sided connection like this, requires different processing for version compatibility
Or does it only matter for whena packet arrives
I think if someone changes the sidedness of a packet from one version to the next, that is their problem
The disconnection behaviour is the important part in my eyes. Telling everyone to include the side-guard shown in the code I linked is fairly easy, but having everyone add disconnection on incorrect receipt side won't go well
https://github.com/neoforged/NeoForge/blob/c15150b2b5d536a19d531067aa861cfa7ad3146b/src/main/java/net/neoforged/neoforge/network/NetworkHooks.java#L67-L73 is what handles the side validation and then dispatch to the NetworkInstance
I know - I simply wonder how possible it is to ship stubs that fail on initialization instead - which would mean you'd have to worry about full-on initializing the class, not just accidentally having its existence checked and thus loading it. Probably a hacky nightmare though
It shouldn't, but currently it is
I mean i could always check if the payload has the same sidedness set on the client and the server
.....
Okey, give me like 20 minutes
ideally, 'packets' (custom payloads, but essentially packets to mods) should be done in the same form as vanilla -- a packet is sent by one side and received by the other, with the reverse being invalid
and two separate packets exist for things that need sending from either side
Need to dig up the API and do the check
Yeah that is what is currently implemented
I just discard invalid packets
Which is the same vanilla does
It does not do the disconnect
but that is different from what Forge did, which was disconnect for an inappropriate (known) packet
Forge only did that for MODDED packets
Vanilla does disconnect on invalid packets, which is the whole reason why a Neo client currently cannot connect to a vanilla server
Only for Packets, not payloads!
if you send an invalid payload
You get a DiscardedPayload instead
Which is considered unknown
And just logs an entry in the log that it received an invalid payload
Without further processing
as far as vanilla is concerned, a custom payload packet with an unknown payload ID is no biggie (maybe the server is trying to see if the client knows that ID and replies back, or the same with the client to server)
an unknown packet, on the other hand, is a connection termination
Yeah
But since we do not have the concept of "packets" anymore
Only payloads
I followed what vanilla does
And am not terminating if somebody sends shit to any end
But just discard it
in the context of mods, payloads are packets -- it's more that we build a networking tunnel consisting of packets between the sides through the custom payload packet mechanic
Well not anymore!
I tend to agree that in the past, that the NetworkInstance concept behaved more like a Packet
I will see what I can do
But no promise on the termination
I will however add a check for the correct expected flow
they were and still are, though
there is no practical difference to a mod between the concept of a 'payload' and a 'packet'
You are kind of right
And kind of wrong
But that is the problem
Mojang has made this area unclear
With their own use of payloads as packets
And vice versa
I fail to see how I am wrong in this regard
Well there is a clear difference in how mojang treats packets
Vs how it treats payloads
The new system allows the modder only to define Payloads. Not packets
Even if that was what you consider it to be in the past
But to be 100% perfectly clear: They are payloads. And mojang simply does not terminate on mismatched arrival
I think the question here is this: are these payloads, as modders would likely use them, doing the sorts of stuff vanilla does with packets or what vanilla does with payloads?
See that is the core question: and the answer is both
Because mojang has started using the payloads themselves on the newer version, for what would have been in the past a custom packet
That is what I ment with mojang has muddied the water
I would say there is a clear difference: vanilla does not use the custom payloads for mission-critical packets, mods however do
Hmm, is there a good example of this?
That is actually a good point
This is a good point, and was the point of my question - if vanilla is now using payloads for "critical"/major stuff like that it would be good to have an example
Mojang only transfers debug information using the payloads
If I can make the disconnect work
I will do so
But that might not be possible
Assuming you know the intended side-of-receipt, it should be trivial since the packet listener (i.e. ClientPacketListener) gives you access to the Connection on which you can just call disconnect() with a reason
Yeah no it is not that simple
the listener never gets invoked if the packet is technically invalid
Because it has been discarded remember
So I would still need to parse the packet
Even if it is invalid
Poking at prior art here, fabric also has something like this implemented, right? Do they just stick with the vanilla behaviour on unknown payloads and discard it, or do they bother to include that logic to disconnect?
If the point where you take over the payload from vanilla code is the same as it currently is (i.e. ClientPacketListener#handleUnknownCustomPayload() or the relevant equivalent depending on phase and side), then you are already in the listener I'm talking about. If not, then where are you capturing the payload?
Not 100% but I am pretty sure they either don't have such a system or they just discard. I have been digging through their net implementation but have not found that, but it is also likely that I just missed it.
There is also the reader....
Technically vanilla says: if payload invalid then discard
Not even parse it
Just completely discard it
Well, yeah, but the point here is to specifically handle known packets which are received on the wrong side and in that case you should end up in a state where the payload has gone through the listener and is in your custom infrastructure such that checking this while having access to the Connection is possible
Okey I think I figured it out
after reading the conversation:
- the concept of sided packets is there since some packets are only registered to one of the sides of the packet set
- a safeguard that a packet can only be sent one way would be good because for a unidirectional packet you don't want to have to check if it is actually received on that side
I already added it
Okey, completely implemented the flow processing logic.
Okey reimplemented the packet distributor
And all remaining core features are implemented
Missing now are the packets
So I hope to have those by tomorrow evening
For the packets I have one single wish: no more mega classes per phase holding all packet types for that phase as inner classes
(assuming you are talking about Neo's own packets like the open container stuff)
👌
But really the amount of packets is probably 6
2 for negotiation
1 for split
1 for registry
1 for spawn entity
1 for open container
And that is it
Oh and potentially one for the extended server status
I think I found a way to make the server status not use the stupid json hack
With the compression
So that it should result in a much better experience and implementation
But I still need to test and research it
i will brook no slander against PlayMessages and HandshakeMessages! /jk
orion can you push the working state and open a draft PR for it (so we can track progress)
Once I can generate patches......
Cause right now I still can't
I thought you have the system itself working and only need to do the usages?
I have but the payloads are missing
And you can only generate patches
If everything compiles
so it is the neo source and not the mc source that is currently not compiling
(just comment the usages of the payloads out to gen patches)
That is what is missing 😄
Okey
Hold on to your hats
Actually that won't happen
I have over 100 erros
@errant summit Ever seen this:
Nearly all of those 100+ errors are this
And I don't understand what it wants from me
Fixed
Something I did 😄
are you sure? i was able to unpackSourcePatches without having the mc sourcet compiling
(for better or for worse)
Hmm maybe?
It failed for me
But I was also tweaking NG7 to deal with some shit
Realistically it does not matter at the moment
I am down to less then 30 errors
Implementing them is trivial
at first glance, I read this as "100 euros"
threw me for a loop
until I reread it and saw the error in the word
@errant summit I just pushed the feature branch
oh lawd, it's a feature branch
I am still working on the payloads and the feature branch
Yeah because I want to publish a testable version from it
can you open a PR so that we can start leaving review comments?
I too have over 100 euros
sadly, I have naught a euro to my name
I will do that when I am ready....
you can also open a draft ¯_(ツ)_/¯
That is simply not how I work....
@buoyant pagoda This now supports directional filtering
Using the flow(PacketFlow) and bidirectional calls
It is possible to tell the registrar that any following new packet registrations are only allowed to flow in one particular direction
Next to the fact that it is additionally possible to now specify a handler for a given direction if you do not want to use a common handler
That gives the modder now 2 different routes to deal with packets arriving at the wrong side
If the modder uses the flowing(PacketFlow) and bidirectional() methods as shown above, then the packet won't be parsed at all, but the client will be disconnected if the packet arrives at the wrong side.
If he or she instead forgoes that option and uses sided handlers, while only registering a handler for a given side, that means that the packet parser will be invoked on both sides, but that the handling is discarded and no method call will be made on the invalid side
This should provide enough flexibiltiy to allow for nearly everybody whishes to be fullfilled
@mental ingot the TC build is intended right?
alright alright... then I don't have to kill it 😄
It will fail untill I fix all the compile errors
But untill then it can just chuck away at it
👍
Okey added the check for the enforced variant of packet flow management now also into the negotiator
This should as such now also show up nicely in the mismatch screen
Okey I am of to bed
Tomorrow I need to figure out how to get the negotiated channel version into the write handling of payloads
some thoughts at present:
-
that call to
flowing/bidirectionaldoesn't look... natural to me? as in, how they affect subsequent calls to the registrar looks like a possible point of misconfiguration -
what happens when a payload configured for e.g. CLIENTBOUND flow only has a server handler? do we gracefully handle that?
-
do we really need to have that whole specification on what direction those payloads flow? could we not just infer it from the handlers -- if a handler is not present for a payload on reception, then assume it was sent improperly and terminate the connection
in that way, those who want the behavior of 'if received on the wrong side, discard and continue' should just need to make a no-op handler, which is better at communicating the intention of the dev (that they wish to discard if it's received on that side)
the flowing/bidirectional seems like unnecessary additions to the API, when we can infer the directionality of a payload based on the existence of handlers for the receiving side
plus, the API wouldn't need to confirm that e.g. a clientbound payload has a handler for the client-side, and not a handler for the server-side (since it will never receive it on the server)
i.e., remove flowing/bidirectional, and change behavior such that receiving a known payload (i.e., the ID is known to us) but with a missing handler causes a disconnection
how long do you think before the networking refactor is ready for review? I'm considering going with XFact's suggestion in https://github.com/neoforged/NeoForge/issues/205 of the band-aid fix, in order to fix vanilla<->NF compatibility at present while this refactor is still ongoing
The implementation will be done in a couple of days actually. I am just missing some additional payloads to deal with the config sync and the custom entity with additional payload stuff
custom entity with additional payload?
it sounds...familiar
but I can't quite put my finger on the name of the class
anyway, if the main substance of the rework is finished and all that's left is hooking up NF-added APIs to use it, consider making a draft PR so we can look at the details of the new APIs and impl. while you finish up the last few bits
So the chained call is not needed a modder can do this as well:
@SubscribeEvent
public static void register(final RegisterPacketHandlerEvent event) {
final IPayloadRegistrarWithAcceptableRange registrar = event.registrar()
.withVersion(buildNetworkVersion());
registrar
.flowing(PacketFlow.CLIENTBOUND)
.configuration(
FrozenRegistrySyncStartPayload.ID,
FrozenRegistrySyncStartPayload.class,
FrozenRegistrySyncStartPayload::new,
handlers -> handlers.client(ClientForgeRegistryHandler.getInstance()::handle)
)
.configuration(
FrozenRegistryPayload.ID,
FrozenRegistryPayload.class,
FrozenRegistryPayload::new,
handlers -> handlers.client(ClientForgeRegistryHandler.getInstance()::handle)
);
registrar
.bidirectional()
.configuration(
FrozenRegistrySyncCompletePayload.ID,
FrozenRegistrySyncCompletePayload.class,
FrozenRegistrySyncCompletePayload::new,
handlers -> handlers.client(ClientForgeRegistryHandler.getInstance()::handle)
.server(ServerForgeRegistryHandler.getInstance()::handle)
);
}
or:
@SubscribeEvent
public static void register(final RegisterPacketHandlerEvent event) {
event.registrar().withVersion(buildNetworkVersion())
.flowing(PacketFlow.CLIENTBOUND)
.configuration(
FrozenRegistrySyncStartPayload.ID,
FrozenRegistrySyncStartPayload.class,
FrozenRegistrySyncStartPayload::new,
handlers -> handlers.client(ClientForgeRegistryHandler.getInstance()::handle)
);
event.registrar().withVersion(buildNetworkVersion())
.flowing(PacketFlow.CLIENTBOUND)
.configuration(
FrozenRegistryPayload.ID,
FrozenRegistryPayload.class,
FrozenRegistryPayload::new,
handlers -> handlers.client(ClientForgeRegistryHandler.getInstance()::handle)
);
event.registrar().withVersion(buildNetworkVersion())
.bidirectional()
.configuration(
FrozenRegistrySyncCompletePayload.ID,
FrozenRegistrySyncCompletePayload.class,
FrozenRegistrySyncCompletePayload::new,
handlers -> handlers.client(ClientForgeRegistryHandler.getInstance()::handle)
.server(ServerForgeRegistryHandler.getInstance()::handle)
);
}
So it is completely up to the modder to chose the format he or she wants to use. They can store the registrar in between, call it in one long chain, or store it. In practice it is just a builder for the internal registation record.
So with respect to a CLIENTBOUND flow payload with only a server handler: If it arrives at the client it will be discarded and nothing happens, if it arrives at the server it will not be parsed from the friendly bytebuf and instead disconnect the server, never invoking the handler. It might in deed be better to create some feedback during the setup loop to ensure that modders don't shoot themselves in the food.
(1/2), next message will follow.
The API is specifically designed, to not have to assume anything. You don't need to use the:
.flowing(PacketFlow.CLIENTBOUND)
.configuration(
FrozenRegistryPayload.ID,
FrozenRegistryPayload.class,
FrozenRegistryPayload::new,
handlers -> handlers.client(ClientForgeRegistryHandler.getInstance()::handle)
);
Syntax, something like this is also possible:
.flowing(PacketFlow.CLIENTBOUND)
.configuration(
FrozenRegistryPayload.ID,
FrozenRegistryPayload.class,
FrozenRegistryPayload::new,
ClientForgeRegistryHandler.getInstance()::handle
);
The aim with this registrar design is to not lock the user into a specific design or use pattern. In practice it can supports all.
So I did not want to assume anything from the handles which are registered.
The handlers -> based api, is in practice just a call to the second API above, with a handler that has this code defined:
@Override
public void handle(ConfigurationPayloadContext context, T payload) {
if (context.flow().isClientbound()) {
if (clientSide != null) {
clientSide.handle(context, payload);
}
} else if (context.flow().isServerbound()) {
if (serverSide != null) {
serverSide.handle(context, payload);
}
}
}
for a configuration packet.
eh I don't like the current builder design, it somehow feels clunky
Give me a better one
But I am not exposing the raw underlying registration object
Because that is internal API
And if we ever change something related to the networking registration code
I want to have that ability even in a breaking changes window
I don't think all the features will be used by everybody
But I think there is a group of people which will do versioning
Some will do enforced flow
Some will do optional processing
Some will do exact phase packets
Others will be, meh screw it, i will just register a common packet
I will once I have everything compile
And at least you know can get into the world
I have a stupid feeling i missed something
And might need to refactor shit.....
i still feel like this is overreaching...? in its attempts to have a 'flexible' API
when instead we can restrict it a bit (require the handler to be specified to client and/ server, for lack of ambiguity)
and derive a benefit from it (naturally concluding that if a handler is not present, the payload must not be intended for that side),
and have no loss of flexibility on the user side (if a payload should be received but discarded, then register a noop handler -- or even provide a specific method for that, if we want to avoid deserialization in that case)
i don't see a practical benefit of a 'common' handler (as I assume the one with no handlers -> means), because (a) I do not forsee many mods reusing the same packet for both network flows, and (b) even in that case, mods can just declare the same handler for both sides -- making their intention more explicit (that the payload is intended for processing on both sides)
I do, and many mods do......
Realistically I will just be doing:
event.registrar()
.play(
FrozenRegistrySyncStartPayload.ID,
FrozenRegistrySyncStartPayload.class,
FrozenRegistrySyncStartPayload::new,
ClientForgeRegistryHandler.getInstance()::handle
);
And go home
I have never liked the idea of an unsided packet, in almost all cases it should be two packets because each direction serves a different purpose
Why?
If you have to pass a class you shouldn't need to pass an ID, and/or vice versa
Forge uses them now, but has a spplit handler
Not possible, the internals need the ability to cas1
And the resourcelocation is needed for lookup
Not something vanilla supports, it needs the id for the lookup
Then you need to do the cast everything
Why do you need a cast? Can't it be generic so the consumer (silently) casts without having to see anything
Something needs to make the cast to your specifically typed handler
And that needs to happen in the internals
Before I invoke your generic handler
nah you just ignore the generic and use raw types internally
........ I am not even dignifying that with an answer.....
The way I have my packets right now is through an API I created that acts as a helper over simpleimpl, which lets you implement packets by implementing an interface and registering it: https://github.com/Shadows-of-Fire/Apotheosis/blob/1.20/src/main/java/dev/shadowsoffire/apotheosis/ench/table/ClueMessage.java
the type is erased so the receiver will cast again regardless
It is actually true
But so dirty.......
I question the existence and purpose of that hypothetical packet
It already exists today......
Why not?
You are not supposed to register packets outside of your namespace to prevent collisions
I still question its purpose
since I find it difficult to believe that there is some common realistic use-case that involves a mod which exchanges the same structured data (in form and in use) between the client and server
In the particular case here it is an empty packet
It is an aquital packet
That indicates that the server send all registries to the client
And the client acknowledges back that it received them
What is the event solving for that? IMO anything using the "magic" event-modid context is shit design and should be axed from the universe
magic static context is a horrible anti-pattern
lol
Nvm
Give me another design that prevents Mod A from registering shit in Mod Bs namespace
yes but since presumably the owner provides both the deserializing factory and the handler (if it works like somplechannel works now), their <T> will by necessity be consistent, so you can internally ignore the generics altogether in the registration record without loss of consistency. dirty, yes, but a few suppresswarnings shut the compiler up abd then you look away. ;p
We don't need to care about this. Babyproofing like that is what gets us to "potentially unwanted namespace" garbage
Its dirty but it works....f..
Just let people create a helper object with their modid and have it automatically prepend it to created objects
such a packet can still be implemented in the API I've suggested and described above, and would even be more explicit in specifying "this packet is received on both sides"
var reg = new MessageRegistrar(MODID);
reg.whatever(name, [packet info]);
Babyproofing is fucking needed here!
I had no versioning and no sidedness checks in the firstplace in this API. And people complained that it is possible to crash the server by sending unknown or not well structured packets to the server by a malicious or broken client!
If you can register shit to somebody else, you can get very undefined and unexpected behaviuor.
So either we baby proof shit with a proper API, or we don't do it at ALL. But not this in between shit
We shouldn't do it at all
it doesn't prevent anyone from doing it
it just makes a bunch of noise
I was of that opinion
I'm sorry do you currently error on that? Respectfully, that's a terrible pattern. The warning with registration was annoying and this is annoying.
Use case: mod A provides some tools for registering packets that other mods can use.
Baby proofing is not needed here
But also you should not be able to crash the server by sending malformed packets, that indicates a lack of security on the serverside
the bad packet should be discarded and the connection terminated
(as is the current case, iirc)
If it's just a warning it's not much use and just causes annoyance
What do you want me to tell you, that is how vanilla parses its packets from the friendlybytebuf, It won't crash, but you can sure as hell deadlock, twice over.
If it's an error it makes legitimate use cases painful
Well we can fix that :p
Possible with a lot of work
And I agree that a simpler API, without versioning, without Sidedness is much simpler
It would allow the same systems
But only implicitly
But then again I point to scis and mine discussion in this thread a couple of days ago......
Though wait a second, can you just crash a vanilla server with a bad packet?
Yeah
That should be considered a serious flaw in their design
Send it a custom payload packet
With a known id
And something that needs to parse a map
is there a mojira ticket on that? That's an insane oversight
And make the map pretty large
I had no versioning and no sidedness checks in the firstplace in this API. And people complained that it is possible to crash the server by sending unknown or not well structured packets to the server by a malicious or broken client!
on the point of the sidedness check: that is a regression in your draft of the API, with respect to the current API, which was properly raised in order to prevent that regression and have a redo of that commit which added the packet network direction feature in the first place
It was properly raised, but it is babyproofing!
Vanilla does not do this
I think w.r.t. the current api (simpleimpl) most people don't register a direction bc its optional
and tutorials glance over it
And there is literally no reason to do it in the first place......
vanilla doesn't do this because it has separate packets for everything
it is literally impossible to send a clientbound packet to the server
Semantics, you can still send a malformed packet as long as the packet id lines up it will attempt to parse it!
I am of the opinion there should be no double-sided packets, modders tend to be bad enough with designing packets as-is
You should be forced to set the net direction on packet registration
I always use the direction and in my wrapper API I even enforce it on the sending side
and it will fail at the parsing stage and kill the connection
not carry on over to the handler and kill the server
Other question, why not just keep simpleimpl
Because it does not work anymore
I could, but then I also need to make event work
And that is a whole thing
And you need a different simpleimpl for when you want config packets
And a different event for that phase as well
It gets you in a whole mess
The point is to have one entry point
Where you can see
I have one packet
And I need to set it during config or play
That was literally all the original api had
Very simple
Yes it had no such thing as side enforcement
Or versioning
And I am still of the opinion that neither is needed
I understand your hesitation with respects to the event
Might I ask for a usecase to register in a different mods namespace for packets?
I could trivially give you a method to create one for something other then your namespace
There isn't one (that I could reasonably think of), I just don't think its worth the time or engineering effort to write a bunch of lockdown code like that
The modid-implied context thing available to the mod bus events is poor design and should be abolished in general
I just fire it in a different bus, with a different bus entrypoint..... It is not more or less difficult
But I mean given that it is trivial to add
If somebody comes up with a reason to have it
I can easily add it
Mod that does a bunch of logic for you in setting up packet stuff, that other mods are querying to set up their own packets
And fallback to the current mod if callers don't care
That is actually quite true, do you have an example?
also registering payloads to other mod ids should be possible (see WorldEditCUI)
The short of my point is that anything using ModLoadingContext.get().getActiveNamespace() is poorly written and should be removed
No, it was just the first thing that popped into my head. I imagine Architectury API would want that though
It is a serial dispatch
It should be trivial then
I can just add a method to the event
And yeah, this
That allows you to get a registrar for an arbitrary namespace
And if you don't supply a namespace
It will just fallback to the active one
Context dependent namespace is 