Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Resolved How do I manually accept a connection on the server using a custom NetworkInterface?

Discussion in 'NetCode for ECS' started by Kmsxkuse, May 6, 2023.

  1. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    304
    I am attempting to implement a custom NetworkInterface with an external SDK (Steamworks.Net).

    Going from Client -> Server seems simple, in Bind() simply ConnectP2P using the CSteamID aliased over the input NetworkEndpoint as suggested in this post:

    https://forum.unity.com/threads/transport-how-to-use-steamworks-net.1376205/

    However, receiving this information comes in the form of PollEvent in which the
    connectionStatusChangeQueue
    is dequeued to find:
    ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connecting


    Or in simpler terms, someone is trying to connect.

    Accepting using the Steamworks.Net API is simple, just
    AcceptConnection()
    .

    But now how do I inform NetCode that there is a new connection? It seems like the built in API for NetCode connection handling is internal.

    Not to mention I also need access to the target CSteamID for each packet I send over Steamworks.Net. I can see that there's a pointer to the NetworkEndpoint on each packet processor but where is that set? And how do I set it as it should not be an address and port but a ulong.

    I can see that Relay uses this aliasing of NetworkEndpoint for their AllocationIDs using
    AsRelayAllocationId()
    but the endpoint is obtained from Connections, which is internal.

    Is there no way to implement a custom
    NetworkInterface
    without internal access? If so, I might as well implement a
    NetworkLayer
    and then add it to the
    NetworkStack
    inside
    NetworkDriver
    manually.

    NetworkLayer
    is far more analogous to the Steamworks.Net API than a socket implementation for
    NetworkInterface
    .

    Adding to the
    NetworkStack
    will result in the layer being added after
    TopLayer 
    which might be a problem. Unless I localize the Transport and manually add in my Steamworks Layer into
    InitializeForSettings<>()


    -----------------------------------------------------------------------------

    Edit: I think I can "hijack"
    NetworkInterface
    layer to become an actual Layer with internal access and attaching a reference to the ConnectionList located on the NetworkDriver's NetworkStack to the custom Interface as a field from within
    NetworkStreamDriverConstructor
    .

    A value copy of the ConnectionList should be fine as all (relevant) fields within the list are pointers.

    From there, everything should work smoothly aliasing NetworkEndpoint with CSteamID and using the existing ConnectionList to obtain the CSteamID required to target
    SendMessageToConnection
    found in Steamworks.Net API through the
    EndpointRef
    located on the packet processor (should be the same as the ConnectionList ->
    GetConnectionEndpoint(PacketProcessor.ConnectionRef)
    )

    No localizing of Transport needed (hopefully).

    That
    ref N networkInterface
    in NetworkDriver Create<N>() is going to be the key to all of this.
     
    Last edited: May 6, 2023
  2. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    873
    Kmsxkuse likes this.
  3. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    304
    I̶ ̶s̶t̶i̶l̶l̶ ̶w̶o̶u̶l̶d̶ ̶l̶i̶k̶e̶ ̶t̶o̶ ̶s̶e̶e̶ ̶y̶o̶u̶r̶ ̶t̶h̶o̶u̶g̶h̶t̶s̶ ̶S̶i̶m̶o̶n̶ ̶b̶u̶t̶ ̶I̶ ̶t̶h̶i̶n̶k̶ ̶I̶ ̶m̶a̶n̶a̶g̶e̶d̶ ̶t̶o̶ ̶g̶e̶t̶ ̶s̶o̶m̶e̶t̶h̶i̶n̶g̶ ̶p̶u̶t̶ ̶t̶o̶g̶e̶t̶h̶e̶r̶ ̶w̶i̶t̶h̶o̶u̶t̶ ̶r̶e̶q̶u̶i̶r̶i̶n̶g̶ ̶i̶n̶t̶e̶r̶n̶a̶l̶ ̶a̶c̶c̶e̶s̶s̶.̶

    I̶ ̶s̶i̶m̶p̶l̶y̶ ̶c̶r̶e̶a̶t̶e̶d̶ ̶a̶ ̶"̶C̶o̶n̶n̶e̶c̶t̶i̶o̶n̶L̶i̶s̶t̶"̶ ̶l̶i̶k̶e̶ ̶i̶m̶p̶l̶e̶m̶e̶n̶t̶a̶t̶i̶o̶n̶ ̶t̶o̶ ̶m̶a̶n̶a̶g̶e̶ ̶s̶t̶e̶a̶m̶w̶o̶r̶k̶s̶ ̶c̶o̶n̶n̶e̶c̶t̶i̶o̶n̶s̶.̶ ̶T̶h̶e̶ ̶a̶c̶t̶u̶a̶l̶ ̶c̶o̶n̶n̶e̶c̶t̶i̶o̶n̶ ̶h̶a̶n̶d̶l̶i̶n̶g̶ ̶i̶n̶ ̶r̶e̶s̶p̶e̶c̶t̶ ̶t̶o̶ ̶N̶e̶t̶C̶o̶d̶e̶ ̶i̶s̶ ̶m̶a̶n̶a̶g̶e̶d̶ ̶b̶y̶ ̶S̶i̶m̶p̶l̶e̶C̶o̶n̶n̶e̶c̶t̶i̶o̶n̶L̶a̶y̶e̶r̶

    ̶N̶o̶w̶ ̶t̶h̶e̶ ̶m̶a̶i̶n̶ ̶p̶r̶o̶b̶l̶e̶m̶ ̶i̶s̶ ̶a̶c̶t̶u̶a̶l̶l̶y̶ ̶c̶o̶n̶n̶e̶c̶t̶i̶n̶g̶ ̶t̶h̶e̶ ̶N̶e̶t̶C̶o̶d̶e̶ ̶N̶e̶t̶w̶o̶r̶k̶C̶o̶n̶n̶e̶c̶t̶i̶o̶n̶ ̶e̶n̶t̶i̶t̶y̶ ̶t̶o̶ ̶t̶h̶e̶ ̶l̶i̶s̶t̶ ̶o̶f̶ ̶C̶S̶t̶e̶a̶m̶I̶D̶ ̶i̶n̶d̶e̶x̶e̶s̶ ̶b̶e̶c̶a̶u̶s̶e̶ ̶t̶h̶e̶ ̶n̶u̶m̶b̶e̶r̶ ̶a̶s̶s̶i̶g̶n̶e̶d̶ ̶t̶o̶ ̶e̶a̶c̶h̶ ̶N̶e̶t̶w̶o̶r̶k̶I̶d̶ ̶c̶o̶m̶e̶s̶ ̶f̶r̶o̶m̶ ̶t̶h̶e̶i̶r̶ ̶C̶o̶n̶n̶e̶c̶t̶i̶o̶n̶L̶i̶s̶t̶ ̶C̶o̶n̶n̶e̶c̶t̶i̶o̶n̶I̶D̶,̶ ̶n̶o̶t̶ ̶m̶y̶ ̶s̶t̶e̶a̶m̶w̶o̶r̶k̶s̶ ̶c̶o̶n̶n̶e̶c̶t̶i̶o̶n̶s̶.̶

    C̶o̶n̶n̶e̶c̶t̶i̶o̶n̶I̶D̶s̶ ̶a̶r̶e̶n̶'̶t̶ ̶p̶u̶b̶l̶i̶c̶ ̶e̶i̶t̶h̶e̶r̶ ̶s̶o̶ ̶I̶ ̶c̶a̶n̶'̶t̶ ̶j̶u̶s̶t̶ ̶c̶r̶e̶a̶t̶e̶ ̶a̶ ̶h̶a̶s̶h̶-̶m̶a̶p̶ ̶b̶e̶t̶w̶e̶e̶n̶ ̶U̶n̶i̶t̶y̶ ̶a̶s̶s̶i̶g̶n̶e̶d̶ ̶C̶o̶n̶n̶e̶c̶t̶i̶o̶n̶I̶d̶s̶ ̶(̶a̶v̶a̶i̶l̶a̶b̶l̶e̶ ̶o̶n̶ ̶N̶e̶t̶w̶o̶r̶k̶S̶t̶r̶e̶a̶m̶C̶o̶n̶n̶e̶c̶t̶i̶o̶n̶ ̶e̶n̶t̶i̶t̶i̶e̶s̶)̶ ̶a̶n̶d̶ ̶C̶S̶t̶e̶a̶m̶I̶D̶s̶ ̶(̶l̶o̶c̶a̶t̶e̶d̶ ̶o̶n̶ ̶N̶e̶t̶w̶o̶r̶k̶I̶n̶t̶e̶r̶f̶a̶c̶e̶)̶.̶

    Edit: The non-connection was my fault. It works automatically.
     
    Last edited: May 7, 2023
  4. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    304
    A̶n̶d̶ ̶w̶i̶t̶h̶o̶u̶t̶ ̶a̶c̶c̶e̶s̶s̶ ̶t̶o̶ ̶C̶o̶n̶n̶e̶c̶t̶i̶o̶n̶L̶i̶s̶t̶,̶ ̶m̶y̶ ̶o̶r̶i̶g̶i̶n̶a̶l̶ ̶p̶r̶o̶b̶l̶e̶m̶ ̶o̶f̶ ̶t̶r̶i̶g̶g̶e̶r̶i̶n̶g̶ ̶a̶n̶ ̶i̶n̶i̶t̶i̶a̶l̶ ̶h̶a̶n̶d̶s̶h̶a̶k̶e̶ ̶s̶e̶e̶m̶s̶ ̶t̶o̶ ̶b̶e̶ ̶a̶n̶ ̶i̶s̶s̶u̶e̶.̶

    H̶o̶w̶ ̶d̶o̶ ̶I̶ ̶t̶r̶i̶g̶g̶e̶r̶ ̶a̶n̶ ̶i̶n̶i̶t̶i̶a̶l̶ ̶h̶a̶n̶d̶s̶h̶a̶k̶e̶ ̶c̶o̶n̶n̶e̶c̶t̶i̶o̶n̶ ̶s̶o̶ ̶t̶h̶e̶ ̶r̶e̶s̶t̶ ̶o̶f̶ ̶n̶e̶t̶c̶o̶d̶e̶ ̶r̶e̶a̶l̶i̶z̶e̶s̶ ̶a̶ ̶n̶e̶w̶ ̶c̶o̶n̶n̶e̶c̶t̶i̶o̶n̶ ̶h̶a̶s̶ ̶b̶e̶e̶n̶ ̶e̶s̶t̶a̶b̶l̶i̶s̶h̶e̶d̶?̶

    ̶I̶ ̶h̶a̶v̶e̶ ̶a̶n̶ ̶e̶x̶i̶s̶t̶i̶n̶g̶ ̶c̶o̶n̶n̶e̶c̶t̶i̶o̶n̶ ̶l̶i̶n̶k̶ ̶t̶h̶r̶o̶u̶g̶h̶ ̶S̶t̶e̶a̶m̶w̶o̶r̶k̶s̶ ̶b̶u̶t̶ ̶w̶h̶o̶ ̶d̶o̶ ̶I̶ ̶n̶o̶t̶i̶f̶y̶ ̶t̶h̶a̶t̶ ̶t̶h̶e̶ ̶c̶o̶n̶n̶e̶c̶t̶i̶o̶n̶ ̶w̶a̶s̶ ̶e̶s̶t̶a̶b̶l̶i̶s̶h̶e̶d̶?̶

    Edit: I am dumb. It actually works magically.
     
    Last edited: May 7, 2023
  5. simon-lemay-unity

    simon-lemay-unity

    Unity Technologies

    Joined:
    Jul 19, 2021
    Posts:
    416
    From your recent messages it seems like you might have figured out the problems, but for the sake of completeness (and future readers), I'll answer your different inquiries.

    Put simply, you don't. With the way network interfaces work today, you'd simply accept the connection on the Steam side and, with the communication path between your client and server now established, the simple connection layer will take care of sending a message to the server letting it know that there's a new connection. (There's an unfortunate bit of inefficiency here since on the client the simple connection layer might send connection requests before the connection has been accepted on the server. It's eventually going to work since we retry sending those, but it does mean that connection might take slightly longer to be established that it needs to.)

    For individual packets, the network endpoint can be set via the
    EndpointRef
    property of its
    PacketProcessor
    (obtained form the send/receive queue).

    To set a network endpoint to something that's not an IPv4 or IPv6 address and port, you have two options. First is to use `SetRawAddressBytes`, but looking at the implementation right now I realize that it's uselessly constraining (you're stuck having to specify a correct address family which means settings either 4 or 16 bytes). I'll make a note to address this. The second option is to forcefully cast/copy the endpoint to your custom type using raw pointers. This is what is done for Relay allocation IDs (see
    ToNetworkEndpoint
    method of
    RelayAllocationId
    ). I understand that this is inconvenient however.

    That's a very good question. You are absolutely correct that a network layer would probably be at a better level of abstraction to implement something like Steam sockets support than a network interface. I understand how frustrating it must be to look at all these APIs that are internal.

    Our plan is to expose those publicly eventually. If not the network layers themselves, at least the connection list and provide a way for network interfaces to manage it. We opted not to do so for our initial release of transport 2.0 because this is all new code and we didn't want users depending on it before having it put through its paces ourselves through internal usage. It gives us an opportunity to improve the API in breaking ways without breaking user code. But I understand that in the meantime it forces users like you to rely on the more limited API of network interfaces. I'll look into accelerating our plans to expose more of our APIs.

    I'll also make a note to improve our documentation on the existing network interfaces. I know it's an area that's severely lacking currently. I salute your perseverance in working through this with just the code as a reference.

    If you have any more questions, I'd be happy to answer them. Also, if you feel the inclination to do so, I'd like to read your reflections on your experience implementing a custom network interface. We haven't had many users do so, and I think your feedback would be very valuable to us.
     
    Kmsxkuse likes this.
  6. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    304
    Exactly my experience with the SimpleConnectionLayer. Took me a while just reading the code and trying to figure out how and when it was processing new connections and signalling to the rest of NetCode that a new connection was created. Lots of back and forth, trying to find method calls, tracing where newly establish connections were appended to the new connections list.

    Basically, the first packets sent across any connections will come from SimpleConnectionLayer and will contain a simple ping that the SimpleConnectionLayer on the other side of the connection will recognize as a new connection.

    My main issue was that initial connection establishment on the "source" side where calling Connect() on the client would not register a proper endpoint because a bug in the Entities NetCode connection code was overwriting the actual data I've aliased into NetworkEndpoint. A sanitation function was incorrectly identifying that the game-mode setting was set to Client/Server when it was just Client and the connection would repeatedly fail. I've since temporarily fixed it until the next version of Entities NetCode will come out.
    I had a bit of a confusion for a few hours about what the EndpointRef is, where it was being set, and how to properly read/write it.

    In the "ReceiveJob", packets obtained from Steamworks.Net should be copied (AppendToPayload) into a PacketProcessor obtained from the ReceiveQueue. Only in the ReceiveJob should the EndpointRef be written into and should be assigned to the CSteamID casted into an EndpointRef.

    In the "SendJob", packets from the SendQueue having made a loop through the Transport networking system should be sent via Steamworks.Net using the EndpointRef found on the packet processor.

    Transport thankfully does not do any sanitation on the endpoint value so the endpoint applied first in the ReceiveJob will be the same endpoint that comes out into the SendJob.

    New connection "pings" (process in the first quote) recognized by the SimpleConnectionLayer will be assigned the same endpoint as the one written into the EndpointRef of the ping that it originated from. Endpoint can be anything at all, so long as it fits within 24 bytes (sizeof(NetworkEndpoint)).

    Data thus sent to the connection by Entities NetCode will have the same EndpointRef assigned from the initial ping in ReceiveJob and can casted back and used in SendJob as targeting data for Steamworks' SendPacket(CSteamID, byte[]).

    This is what I had so much difficulty understanding and had to go through SimpleConnectionLayer line by line to figure out. I don't need to register a new connection as this initial ping and correct Endpoint will do so with SimpleConnectionLayer.
    This is what I ended up doing, copying ToNetworkEndpoint()'s process. My main problem actually was from Entities NetCode and the various sanitation methods they apply to validate the NetworkEndpoint. Obviously an Endpoint originating from a different struct and aliased over NetworkEndpoint would not be a valid endpoint but NetCode doesn't know that and if the port or family was 0 (Invalid), it would overwrite the NetworkEndpoint with AnyIpv4 or LoopbackIpv4. Both wiping out the actual valuable targeting data inside NetworkEndpoint.

    Figuring out how to "trick" the sanitation functions was 80% of the work making the entire NetworkInterface and was immensely frustrating. I have an entire comment mapping out which bytes were being read from various sanitation functions, which bytes I was using to encode targeting data, and which were still unused and could pack in a bit more data.

    Without NetCode's NetworkEndpoint sanitation methods, I've would've made this interface work in a day rather than 4 or 5. And it isn't even your fault from the Unity.Transport side. It's all from Entities NetCode and as mentioned earlier, the sanitation functions don't even work in the first place.

    Although there is one part of Transport that is an issue. Specifically on the Connect() call in NetworkDriver:
    Code (CSharp):
    1. if (!Bound)
    2. {
    3.    var nep = endpoint.Family == NetworkFamily.Ipv6 ? NetworkEndpoint.AnyIpv6 : NetworkEndpoint.AnyIpv4;
    4.    if (Bind(nep) != 0)
    5.       return default;
    6. }
    That "nep" assignment to AnyIpv4 or 6 wipes out any data located in the address where my aliased targeting data is located. Making the Bind() call inside network interface completely useless. I had to make my own Connect() that doesn't wipe out the endpoint and call a Bind() using the proper endpoint.
    A bunch of back and forth on this later and I've realized that because SimpleConnectionLayer handles the Unity side of communication, there is no need to access ConnectionList found on NetworkLayers. Kinda.

    What I've encountered is actually an issue with disconnection. Steamworks sends a callback indicating that a connection was closed by remote to the system (leaving the game for example) and terminates the connection. Which is fine. But SimpleConnectionLayer doesn't know that.

    What SimpleConnectionLayer sees is a dropped connection and repeatedly attempts to re-establish the connection using the DisconnectTimeout set via the NetworkConfigParameters. It eventually disconnections but all disconnections are registered as "Timed Out" rather than closed by remote or local through Steamworks.

    I am still trying to figure out why because NetworkStreamConnectSystem should automatically send disconnect signals using SimpleConnectionLayer to the server to indicate that the connection was closed on system destruction. It may be that Steamworks is being shut down before the disconnect signal is sent. I am testing right now to see if by delaying the shutdown by a frame or two the signal is properly applied.
    I have to say, reading my code now that it works (kinda, I still need the MTU size to be variable and set to a lower value; manually editing the package cache and setting the constant is a stopgap measure) it's surprisingly simple and effective.

    Use the API as a socket rather than a full networking layer is the key but all existing examples of using Steamworks or Epic Online Services use their SDK as layers so it's quite difficult to bring to Unity.Transport.

    Basically trim the existing examples for Netcode for GameObjects versions down, all the way down. Cut off the connection list handling, all the "is opened by local or remote" checks. Just open the connection and trust that it all works out in the end. It's a blind leap of faith that is daunting when first writing the NetworkInterface which is why NetworkLayer, being far more analogous to online examples and even documentation of using their SDKs, is so appealing. But it isn't necessary or honestly recommended in DOTS.

    Basically, my experience is thus:

    1. MTU needs to be a parameter I can set. I read from your other comment on my other post that you're looking into this which is great. Epic Online Services in particular does not like 1400 bytes, they max out at 1170. If you try sending 1400 bytes such as initial state communication, EOS will drop the packet. EOS being a dominant networking solution (the big FREE one used by nearly everyone), this is especially important and deal-breaking for production. Yes, a simple package edit works but that's a lazy fix. Not to mention a problem for working in teams and onboarding someone new.

    2. There is actually no need to make ConnectionsList public. Or at least in my experience using NetCode. They make the NetworkConnection public and provide all the required access points for that specific connection to make it all work. Just passing around the resulting NetworkConnection is sufficient even for someone making their own networking solution on top of Transport (i.e., not using Entities NetCode).

    Same goes for NetworkLayer. NetworkInterface is entirely sufficient for establishing a connection using an external networking solution. It just requires something completely different from the examples they publish.

    3. Endpoint sanitation and validation is a constant problem to bypass, if they work in the first place. Relay can skip right to creating a new connection and handling ConnectionList but the publicly facing method all have validation and scrubbing applied to it. Which is bad.

    4. SimpleConnectionLayer works as advertised but it takes a while and a lot of reading to understand. Still, it's the process in which to communicate to the rest of NetCode that a connection was established and everything follows from there. Which leads to my next point:

    5. There's not much documentation on both Transport and NetCode side for all this networking API. About 3 times now I've written up something right here and, with a deep dive into either package's source code, I've realized there is actually a public API providing a solution for that exact problem. Just buried and sitting quietly. I've got a lot to do to fix all the various problems I have that there are actual solutions right now.

    I think the main problem here is that there are very little examples of using these "low level" API exposed by Transport and NetCode so the proper and intended procedure needs to be discovered through trial and error. Some samples would be greatly appreciated although it is a tad late for me. I got a decent grasp on how this all works now.

    6. Just looking at RelayLayer actually does more harm than good, being that to implement a proper NetworkInterface requires none of the connection handling and processing that RelayLayer provides. The key is reading SimpleConnectionLayer over and over again. Reading the UDP/TDP/IPC interfaces on the other hand are way too complicated and does what an external networking solution handles. So you need to implement something in-between the low level UDP socket implementation handled by Steamworks and higher level SimpleConnectionLayer that handles Unity. A true "interface" between the two which there really isnt much examples to go around. Your very short sentences in the linked forum post was the key.

    7. More of a complaint about the SDKs from external vendors than anything from Unity. They're really not designed for threading (jobs) and bursted code. They're all C++ so theoredically they can be bursted but they all use managed classes, strings, and byte arrays. I would have to go into the source code of these SDKs themselves to fix up the bindings to use unmanaged data that Burst supports which is well beyond the scope of what I'm doing.

    I got a lot of Try{} Catch{} around SDK calls inside the job just to ignore some of the null exceptions that pop up because these networking solutions are intended to be used on the main-thread and they're slightly unstable when used inside a job. They work surprisingly but not without some complaining of their own. I think the friction between Unity Jobs and Steamworks might be the source of some packet loss, who knows. It's not enough to be noticable from my very short tests. Or it could be silently compounding race conditions that causes the entire thing to explode in 23 minutes into the game.
     
    Last edited: May 8, 2023
  7. simon-lemay-unity

    simon-lemay-unity

    Unity Technologies

    Joined:
    Jul 19, 2021
    Posts:
    416
    Thanks a lot for your feedback! That's very valuable!

    I'll follow up with the Netcode for Entities team about this. Having network endpoints with custom values is something we've been careful to support in the transport package, but it's a moot effort if our netcode solutions do not handle it.

    Yes, that's one of the reasons we want to expose the connection list to network interfaces in some way at some point. It would enable a way for the interface to be notified that a connection is being closed. It's technically possibly right now by inspecting the data payloads, but that's a brittle solution that I wouldn't exactly recommend. Getting a clear notification from the connection list would be better. Another advantage would be that it would allow network interfaces to initiate disconnections themselves. For example if you encountered an error with the SDK or a socket, having access to the connection list would allow you to "bubble up" a disconnection to the upper layers.

    Yes, I fully agree that this is something we need to provide. As noted in the other thread, we're working on exposing a way to do this.

    Right, we probably wouldn't want to expose the connection list of the simple connection layer, but probably something like allowing network interfaces to manage their own connection list. Upper layers (like the simple connection layer) already handle having layers beneath them that have their own connection list (that's what all these references to underlying connections are for), so it shouldn't be too problematic to have network interfaces manage their own.

    Yeah that layer is very much ill-named as it's probably the most complicated layer we have. Ideally it wouldn't be required to read or understand its code however. I think we should be doing a better job of documenting what kind of responsibilities that layer provides, and that thus don't need to be handled by custom network interfaces. That's a very good point and I'll make sure we address it when we document how to build custom network interfaces.

    Do you think having a sample that implements a custom network interface for some third party SDK would help? Obviously, we'd still need extra documentation to explain the general implementation strategy for a custom network interface. But do you think having an example would make it easier to discover all these APIs that are buried in our code?

    At this point we're pretty much committed to jobs, but I do take note that this could prove problematic for some SDKs. Might not be a bad idea to have an example that calls into native code too for those cases where one wants to use the C++ APIs directly...

    Again, thanks a lot for your feedback! This is very helpful to us and I'm grateful that you took the time to write it down.
     
    Kmsxkuse likes this.
  8. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    304
    Oh, I've been constantly editing my statement with more things as I wake up and read though my various comments I have in my code. Sorry but please re-read again containing my experience. I've bolded my main points as well in the final section.
     
  9. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    304
    I just want to emphasize the code snippet I edited in (which you may not have seen when you wrote this, sorry).

    In NetworkDriver.Connect(), calls to Bind() wipes out the endpoint data with AnyIpv which makes it useless. I had to implement my own code just to initiate a connection using the actual endpoint. Everything else in Transport at least works perfectly. Netcode is annoying and please forward my complaints about their sanitation functions.
    Sorry again but reading through the source again and I realized this is already possible through existing public NetworkConnection. Disconnects from steamworks are done in parallel with disconnects registered through SimpleConnectionLayer. There doesn't need to be any communication between the two, probably. I still have some bugs on my end with this.
    Yessssss? I admit, I'm biased because if y'all wrote the interface, i would never need to read the code anyways because I'll use your solution and trust. Might be helpful for those intending to hook up inhouse networking solutions created akin to the big popular ones but I'm not in the position to really give advice on that (being a solo hobbyist dev).
     
  10. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    304
    Also on the random wishlist @simon-lemay-unity , if you could make NetworkEndpoint larger, say 32 or even 64 bytes, that will be amazing because that's the only custom data that I can persist in a round trip through the networking pipeline that is also readable in standard entities through GetRemoteEndpoint(NetworkConnection). Now if there was a SetRemoteEndpoint(NetworkConnection) that would be even better so I can edit the targeting data of the connection for packets. Although that might not work...

    Alright, that's all for now. Back to fixing bugs in my networking.
     
  11. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    304
    Ugh. I'll look into using the raw C++ SDK and writing up my own binding file manually. I've never done such a thing but it might allow for bursted Send/Receive with external networking solutions.

    Somthing to stick on my long term todo list.
     
  12. simon-lemay-unity

    simon-lemay-unity

    Unity Technologies

    Joined:
    Jul 19, 2021
    Posts:
    416
    Regarding the
    Connect
    call, the intention was that if there is a need to bind to a specific endpoint, it's always possible to call
    Bind
    before calling
    Connect
    . In that situation,
    Connect
    will not call
    Bind
    itself and thus that wildcard address wouldn't be used. But perhaps this is problematic from the Netcode for Entities point of view? Is it even possible to call
    Bind
    before calling
    Connect
    in Netcode for Entities? I'll look more into this.

    Making
    NetworkEndpoint
    a little larger seems pretty innocuous. I'll add that to our backlog. I'm not so sure about giving the ability to set the remote endpoint (I can see the need for it, but there could be weird corner cases), but we'll consider it. Thanks for the suggestions!
     
    PolarTron and Kmsxkuse like this.
  13. PolarTron

    PolarTron

    Joined:
    Jun 21, 2013
    Posts:
    88
    +1 to all of this.

    Steam Datagram Relay is on my wishlist as well as the ability to implement https://www.networknext.com/ made by Glenn Fiedler.

    In think in general, if we are shown how to extend the API with examples then incredible things will happen from the user side. Like for example pushing for minimum latency using the KCP protocol.
     
  14. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    498
    I'm now at the start of this journey to integrate steamworks into a network interface in transport. Reading through these threads is making me want to abandon multiplayer lol. Is Steamworks not deemed popular enough to provide an example for...? :| It hardly needs to be an official example in the package if you're worried about commitment... maybe something in like community contributions (https://github.com/Unity-Technologies/multiplayer-community-contributions),
    or at the very least a forum post.

    It feels bizarre that integrating with steamworks should be so complicated.
     
    Last edited: Nov 13, 2023
    Kmsxkuse likes this.
  15. simon-lemay-unity

    simon-lemay-unity

    Unity Technologies

    Joined:
    Jul 19, 2021
    Posts:
    416
    It's not a question of popularity. At least I don't think it is. I think it's more about resources and prioritization. We just haven't been able to get around to this among our other priorities. I know this can be frustrating to read and I'm sorry that I don't have anything more concrete to offer than platitudes. I'll bring this topic up internally again and see if we can bump its priority and get things moving.
     
    Kmsxkuse likes this.
  16. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    304
    @simon-lemay-unity

    I just want to extend my thanks for all the good changes in Transport 2.1.
    Adding in custom max-packet size and QOL of custom network endpoint aliasing is fantastic.
    Definitely my top two priorities when I was going through this process and it's going to be fun to clear out all the junk required to make it work.

    Just one last wish-list Santa Please item, any news on expanding the size of NetworkEndpoint to 32 or 64 bytes? Larger padding means more information that can be used.

    My use case would be implementing the player-defined custom socket string that both steamworks and EOS support that is used for connection. It's not required, only the remote connection handle is required and that's only 8 bytes. Enough to fit inside the existing NetworkEndpoint but doesn't leave much else.

    The socket string, from what I can tell, is simultaneously a "filter" and a "password" for incoming connections of a maximum 32 ASCII values. As it stands currently, I am either limited to I think 13 characters (24 [sizeof] - 8 [handle] - 2 [port] - 1 [family]) which doesn't make for a good password. I think with the Custom network family you added, I can free up the port bytes as previously it must be a non-zero value to get past the sanitation functions that checked for valid ports, even for custom endpoint implementations.

    It would be very nice if I could maintain this value as part of the network endpoint in conjunction with the remote user handle for a "complete" connection value instead of a hashmap off the the side that kinda does the same thing.

    And of course, the hail mary being the "SetRemoteEndpoint()" but I know that might cause a lot of weird bugs and might break the job scheduling depending on when the method is called.
     
    Last edited: Nov 14, 2023
  17. simon-lemay-unity

    simon-lemay-unity

    Unity Technologies

    Joined:
    Jul 19, 2021
    Posts:
    416
    That's on our to-do list, yes. It turned out to be slightly more difficult than I had anticipated originally (the structure maps to some low-level structures in the engine, so we have to be careful about padding it) which is why it wasn't done along with the changes for custom endpoints in 2.1. But we do intend to increase the size at some point.

    Yeah unfortunately that's more tricky. I'm not too worried about the jobs since the safety system should prevent modifications to internal data structures when conflicting jobs are scheduled. The complexity is that, counter-intuitively, a connection can have multiple endpoints in some cases. For example, connections made to the Unity Relay service use the allocation ID as the endpoint, but at some point this is replaced by the actual IP address of the relay server. A method to change the endpoint would have to contend with that. Still, I acknowledge that this is a legitimate need and something that would be useful to provide. I'll add a task to our backlog to investigate this. Thanks for bringing it up again.
     
    Kmsxkuse likes this.