Search Unity

DOTS NetCode 0.3.0 released

Discussion in 'NetCode for ECS' started by timjohansson, Aug 21, 2020.

  1. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    One idea is to print out all the keys to see what's in
    overrides
    .
     
  2. Justin_Larrabee

    Justin_Larrabee

    Joined:
    Apr 24, 2018
    Posts:
    106
    All of these are a concern for me, but memory usage and authoring especially. We've recently moved beyond pre-production and already are close to having more assets than we can load into memory at once -- especially on resource constrained platforms like iOS/tvOS.

    This change seems focused on optimizing CPU time for conversion flow, but makes the prefab situation even more static than it already is in that I have to bake my entities into a subscene now. Additionally, this doesn't help solve the issue of blowing your memory budget does it? Entities in that subscene could be referencing meshes, textures, etc, and sure they can be stripped in a server build, but how is my client prevented from loading gigabytes of data when the subscene is loaded?

    I definitely understand the rationale behind why the ghost collections exist. There needs to be a way to map a stable identifier to an entity prefab that exists on both client and server. As it's currently designed it seems like the simplest approach, which is a good starting point for building a system this complicated.

    From a "I'm making a shippable game with a medium sized team and a 7-figure budget" point of view this is what I would want when working with production level version of this package:
    • I can have 500 potential entities that could be synced, but only N of them may be active in a world at once. The changes in ghost field serialization already seem to be supportive of this need.
    • Some of these entities are prefabs and some of them are only baked into a scene
    • I am not required to manually generate collections of these entities because I have numerous content creators building scenes and it is error prone and merge conflict hell to have to go and update every single ghost collection when a new entity is added.
    • If an entity prefab being spawned isn't loaded, the system figures out how to load it for me (addressables integration?). Less ideal, but fine, would be if there was a runtime way for me to specify what needs to be loaded in advance and it's my problem if I do that incorrectly. If this means manually assigning globally unique integers to prefabs and scene entities, so be it, it's still more flexible and less error-prone than a giant baked subscene containing everything in my game. Conceivably this assignment of IDs could even be automated in editor or during build.
    • Memory/resource issues aren't any different than regular Unity, in that if I do something dumb by trying to load too much it's my fault not the package's.
    These requirements are the difference between making a demo and shipping a MOBA with fifty heroes, multiple maps, with a big team, big budget, and high cost of failure.

    I don't intend to be negative at all with the state of this package, this 0.3 release is a *massive* improvement in usability with the overhaul of ghost field serialization. I can't wait to see where things go from here. Congrats and thank you!

    With how ghost collections work right now though, and the requirements of our game (which fits most of the description above), we'd be forced into doing some hackery to work around the limitations of ghost collections.

    We'd probably end up creating network proxy entities for our primary archetypes (unit, hero, building, etc etc). These would be stuck in a mostly-unchanging ghost collection. The server would then spawn and keep these proxies in sync with its real gameplay entities, and the client is given enough knowledge to know what kind of visuals or other data to "attach" to it's copy of the proxy. Not ideal at all, as this approach feels very much against the grain of your goals of "it just works".

    I could be totally off-base here on the present and future of how ghost collections function and how a game's entities should be designed around them, so please correct me if I am completely mistaken.
     
  3. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    I can confirm that there is a bug with IGhostDefaultOverridesModifier where setting attribute.prefabType on any of the overrides has no effect. All overrides are treated as having GhostPrefabType.All regardless of what you set it to.
     
  4. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    It should be the fully qualified name, but you cannot access a property that does not exist in a dictionary like that, if the key does not exist (and only translation and rotation exists by default) you have to add it to the dictionary with something like this (I did not compile this so there might be errors)
    Code (CSharp):
    1. var comp = new GhostAuthoringComponentEditor.GhostComponent
    2. {
    3.     name = "Unity.Physics.PhysicsVelocity",
    4.     attribute = new GhostComponentAttribute{PrefabType = GhostPrefabType.Server, OwnerPredictedSendType = GhostSendType.All, SendDataForChildEntity = true},
    5.     fields = new GhostComponentField[0],
    6.     entityIndex = 0
    7. };
    8. overrides.Add(comp.name, comp);
    9.  
     
    Occuros likes this.
  5. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    I think this will be close to being sort-of possible in the next release or the one after assuming you're ok with only changing the collection before going in-game and loading the prefabs manually. It's still pretty complicated and you might run into bugs preventing it from working since I have not tried it yet.
    You would not have a GhostCollectionAuthoringComponent in your scene, then at runtime load the entity prefabs you want to use for a session - either through streaming in individual sub-scenes with one or a few prefabs in each, or runtime conversion - and add them to a GhostCollectionComponent singleton you create manually.

    Dynamically loading prefabs as required is a much larger task and I would not expect that short term.

    Thank you for the clear description of the problem, it's very helpful
     
  6. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    I'm really happy with the current version. Writing stable multiplayer code has never been better for me.

    I got a question about server channels. Basically I'd have more than 1 game running on the server.
    I've added this in the really old FPS-Sample and worked pretty good but required a lot of changes to the netcode.

    Can server channels be used in some sense without changes to the netcode package?
    I thought about multiple server instances but as it's small scale I think bundling as much as you can in 1 instance is more performant.
     
  7. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    All the netcode logic is tied to the entities world, so if you create multiple server worlds (by calling ClientServerBootstrap.CreateServerWorld) you'll get multiple games running in the same unity instance.

    The workflow for scene loading and streaming in subscenes is still in development so you might need to work around some issues there, and you need to make sure each server world has a different port.
     
    Enzi and Lukas_Kastern like this.
  8. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    I see! Thank you.
    That's quite elegant.
     
  9. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    I'm looking for an Id that I can use in a target system to target players/mobs.
    Can I use the ghostId as that Id? Would be convinient. Before I have rolled my own WorldIdSystem which would also be fine.
     
  10. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Depends on what you want to use it for, the ghost id is re-used pretty aggressively so you should probably use ghost id + spawn tick from the ghost component in that case.
    Why are you not just using the Entity though? As long as the entity is a ghost you can put a [GhostField] on a field of type Entity and get a weak reference sent to the client.
     
    bb8_1 likes this.
  11. bobbaluba

    bobbaluba

    Joined:
    Feb 27, 2013
    Posts:
    81
    With the asteroids example, the asteroids disappear several frames after they are hit (when the bullet is already way past them). Is this a bug, or something that is just not done yet?
     
  12. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    Welcome to multiplayer games and lag.
    You'd need to add client side collision prediction
     
  13. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    There are 2 issues:
    - the asteroid is a sphere collider so there is some inaccuracy
    - the collision system is not predicted on client so there's lag
     
  14. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    Hey Tim!
    Thanks that's great info. TBH I've never realized this works. Since when is this automatic Entity handling in the Netcode or was it always there?

    As I'm acquiring the target on the client and not the server, I did the following:
    - get the ghostId from the entity that was clicked on client
    - create a hash of the ghost with ghostId, ghostType and spawnTick
    - send this hash as SetTarget RPC command to the server
    - server SetTarget goes through ghosts and compares hash until the correct server entity ghost is found and updated in the players Target comp
    - Target gets synced to clients automatically back as entities

    It seems to work.I still have to test more cases how robust it is.

    Does anyone have another way, a better one, more safe? I'm all ears.
     
    florianhanke likes this.
  15. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    What exactly does this do? I feel like it could be used to create 'patches' for any package so that you don't need to keep a local copy, but I might be interpreting the usage of the word Overrides wrongly.
    Being able to make small modifications to specific source files of packages, without needing to keep a local copy of the whole package, would be greatly appreciated, and this seems to be at least a step in said direction.
    Hopefully I am not wildly interpreting what this could achieve.
     
  16. jasonbakker

    jasonbakker

    Joined:
    Jan 13, 2015
    Posts:
    8
    I think it was introduced in 0.2 - if you mark up an Entity field as a GhostDefaultField(0.2)/GhostField(0.3) it'll try to serialise it across the network using the ghost id (because it won't be the same entity id across the server/client).

    Just putting another hand up for ghost DynamicBuffers, also - or if there's a suggested temporary workaround for collections of components that are connected to an entity, that'd be good to know.
     
  17. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    Does this entity<->ghost resolving only work for GhostField? It would be nice for RPCs. I tried and didn't get it working.
     
  18. jasonbakker

    jasonbakker

    Joined:
    Jan 13, 2015
    Posts:
    8
    Yeah I just checked that today as well, looks like it doesn't work for RPCs yet - would be good to know if that's on the horizon. For the moment Enzi, you can serialise the ghost as an id+spawnTick using the entity's GhostComponent, then World.GetOrCreateSystem<GhostReceiveSystem>().SpawnedGhostEntityMap where you receive the RPC to convert from the id+spawntick to the Entity. You can look at how CopyFromSnapshot/CopyToSnapshot in the snapshot templates work for an example.
     
  19. jasonbakker

    jasonbakker

    Joined:
    Jan 13, 2015
    Posts:
    8
    Discovered this only works on the Client, because GhostReceiveSystem is a client-only system (kinda makes sense). If you want to map the id+spawntick on the server, I haven't found an easier way to do it, so on the Server I'm just iterating through entities with GhostComponents to find the match for now (the GhostComponent has both the id and the spawntick).
     
  20. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    I've written a GhostLookupSystem, the ghost lookup can be easily get in all places now with:
    Code (CSharp):
    1. var ghostLookup = World.GetExistingSystem<GhostLookupSystem>().ghostLookup;
    I'm just not sure where to put in the update sequence, so I commented out [UpdateBefore]. Right now it's working but the order could be a problem in the future. Maybe it should run after the GhostSpawnSystem.

    Another thing is that I have to watch out for a specific hash to ignore because before any connection is made, all ghosts have 0/0/0 values for id, type, tick. Can this be fixed in NetCode?

    Something I struggled yesterday. Is there any way to sync dynamic buffers?
    I've a lot of buffs/debuffs or combat effects. In my previous version this was a MonoBehaviour with an INetworkSerializable, so the previous version of RPCs.
    I could deserialize lists there but now it's not so obvious to me to do the same in an IComponentData.

    I thought about modeling combat effects as ghosts but this seems like overkill.

    Right now my solution is a workaround to the buffers so I create a new entity with every RPCCall of an effect. Then a system goes over all effects and builds a new buffer for the affected avatars every frame on server/client. Not an absolute fan of the solution in terms of syncing but also not too bad as it's decoupled at least.

    edit: Added the GhostHelper for completion
     

    Attached Files:

    Last edited: Sep 1, 2020
    bb8_1 and florianhanke like this.
  21. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    Looks interesting, thank you! What is the reason for
    hashCode != 4459
    – is this specific to your game?
     
  22. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    4459 is the hash when the ghost has an id, type and spawnTick of 0 and is invalid. Only happens when no clients are connected and no ghosts are really active.
     
    florianhanke likes this.
  23. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    Thank you!
     
  24. Kelevra

    Kelevra

    Joined:
    Dec 27, 2012
    Posts:
    87
    Hello @timjohansson !
    I've found that with 20% packet loss simulation settings misprediction chance significantly increased for 1 frame events like button pressed event.
    Can the fixed input buffer be the reason for that? Maybe it will be better to send all inputs from last ack snapshot/input?
    CommandSendSystem::k_InputBufferSendSize = 4

    As I understood, I can write my own send/receive system instead of the default one, am I right?
     
  25. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    It stores a dictionary of component type name -> GhostComponentAttribute. It is not a generic patching method, and we have no plans to make it a generic patching method, it only works for overriding the GhostComponentAttribute and GhostFieldAttribute based on name.
     
  26. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    It is only for GhostField right now, with the latest changes to it it would be possible to support it for RPCs so we are planning to do that at some point, but have not prioritized it against other tasks yet
     
  27. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Is 20% packet loss a condition you expect to happen and work well in your game? That seems extremely high

    I don't think just bumping the input buffer will fix this. The server is not waiting for inputs from the client, it is up to the client to make sure commands arrive in time.
    We try to make the commands arrive 2 ticks before they are needed, so if you loose 2 packets in a row you will still get those commands on the server, but they will arrive after the server has run the simulation and the commands will not be used.

    A more reliable fix would be to store the total count of key presses or last tick the button was pressed in the command data instead of if it is pressed or not. That way you can handle the press on the server with a few ticks delay instead of missing it.
     
  28. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    Maybe i confused myself in the process.

    Basically I would like to use a component with ghost fields and a field that is saved in prediction but not synced.
    I'm using a refence to a spell entity which is not a ghost and either the reference field is synced and gets overwritten in prediction updates or not synced, but then I don't have the correct state for some ticks. Right now I've it unsynced and use it very conservative. I couldn't make it work reliable.

    Is there any design in place I could use? I tried working from the code gen file and set the values in CopyFrom/ToSnapshot but I was unable to debug if it's working correct.

    I used the NetDbg which is pretty cool. What would be interesting is to know the server and client snapshot values in prediction errors.

    edit: I'ved worked around the issue. It wasn't good design anyway. :)
     
    Last edited: Sep 7, 2020
  29. khalid_mightybear

    khalid_mightybear

    Joined:
    Sep 10, 2017
    Posts:
    36
    How would I go about implementing a 2d translation and rotation with the new setup? I noticed in the sample it has been commented out. Is it still possible?
     
  30. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    Some feedback on the Codegen:

    I ran into a trap that I hope noone else runs into. The codegen writes to the GhostComponentSerializerRegistrationSystem.cs with all the ghost components but when you have copy/pasted a serializer it doesn't play nice and will generate one regardless, making a conflict between the generated and manual serializer. (I didn't found any attribute to prevent this)

    BUT when you turn the codegen off for the comp it'll also not be added to the RegistrationSystem, therefore no syncing at all and in my case I didn't even realize for the longest time. (I did question my understanding of prediction systems and component rollbacks till I realized... lol)
     
  31. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    I implemented "channels" utilizing CreateServerWorld and when connecting to this new server world I get "RpcSystem received invalid rpc from connection 0" on the client.
    I'm not really understanding the error, must be even happening before the GoInGameRequest.

    I tried setting up the new server world differently.
    So I'm starting the game, create a snapshot of the server data and apply this to the new server world. Everything should be the same data wise except listening ports.
    Yet, when connecting to the port of the new server world, the connection is created in the original server world.

    Any tips to proceed?
     
  32. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    I got it working. Yay! :D
    The system that made the server snapshot of all server entities was too crude. I reversed the logic and only copy relevant data. I could bring it down to GhostComponent, GhostChildEntityComponent and GhostPrefabCollectionComponent.

    @timjohansson
    Currently when I'm switching servers/channels I request a disconnect and then re-connect to the new available port. Any better options available?
     
  33. FakeByte

    FakeByte

    Joined:
    Dec 8, 2015
    Posts:
    147
    Your other server world uses a different NetworkDriver, you probably could switch all players to the new server with just taking the NetworkDriver with you to the new server.

    Otherwise you could create a class that implements INetworkInterface and replace the default network interface, in the ScheduleReceive method you could evaluate which server this player belongs to and then use the correct servers NetworkPacketReceiver to append the packet, this way you wouldn't loose any performance and the user won't disconnect.
     
  34. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    Thanks for your post FakeByte! You sound familiar with it. Have you implemented something like this?

    So if I understand correctly, I can just re-use the existing NetworkDriver for the new server? I imagined that would trip the systems up not knowing where one packet should be routed, server A or B or both. Would this be an issue?

    That sound like an elegant solution but TBH I don't know where to start with it yet. If you have any tips or resources I'd appreciate it.
     
  35. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    You probably need to go through the connect flow to get everything setup correctly anyway, so what do you mean by better? Which problem are you trying to solve and what are you optimizing for?
    If you want to use the same port for all server worlds you could potentially use an IPCNetworkInterface for the server worlds and a relay system in the default world which owns the real socket and forwards events to the correct world, but it would add some additional overhead and lots of additional complexity.
     
  36. FakeByte

    FakeByte

    Joined:
    Dec 8, 2015
    Posts:
    147
    I am currently developing a custom NetworkInterface which uses SteamSockets, that way I can use the Netcode package with Steam. I have it almost working, just fighting with a few memory leaks before its done.


    A NetworkDriver holds a NetworkInterface with a socket. If you could somehow manage to move it to a different server world then it would take its socket and all its connection with it, but I don't know if it is achievable.


    You can check the custom network interface example out here:
    https://github.com/Unity-Technologi...project/Assets/Samples/CustomNetworkInterface

    2 weeks ago I asked about this topic and timjohansson gave a good explanation here:
    https://forum.unity.com/threads/netcode-change-transport-socket.959859/

    If you start testing of the example then you could save a static long socketHandle in your NetworkInterface and make every server world use the same socket. In the CreateAndBind and the Bind method you can check if that variable is populated and if it is then do not create a new socket and set the NativeArray<long> m_UserData which holds the socket handle for a specific interface to the socketHandle you already have, this way all interfaces should use the same socket, mind that if you try to call the client connect function this will not work, if you need server and clients in one process then you could create a new socket when calling the client connect function and handle this case.

    You would also need to save somewhere which client belongs to which server, the client could send you a message with the server he wants to join or you handle it on the server side, I guess there are many ways to do this. Maybe a Dictionary with NetworkInterfaceEndPoint as key and server ID as value would work.

    The trickiest part might be modifying the ReceiveJob, I think you could get all packets from the native socket and save them seperatly and process the packets in the next frame. That way in the second frame you have a list with all packets, now each NetworkDriver calls ReceiveJob and you use the network endpoint to decide to which server this packet belongs, then each network driver appends the packets that belong to it to its NetworkPacketReceiver.

    Maybe @timjohansson could tell us if this approach would be viable.
     
  37. Kelevra

    Kelevra

    Joined:
    Dec 27, 2012
    Posts:
    87
    Thank you. Maybe I overestimate possible conditions for the mobile network a little bit but I'm trying to imagine the worst scenario.

    I will investigate this question deeper thank you for ideas.
     
  38. _met44

    _met44

    Joined:
    Jun 1, 2013
    Posts:
    633
    Preset ghost collection forbid using bundled prefabs with DOTS netcode, whereas runtime constructed / editable collection could allow it...
     
    Last edited: Sep 17, 2020
  39. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    I don't know what UNC means in that context. In case you are talking about asset bundles, why would ghosts need one? When graphics are coupled to the ghost, just have an individual presentation entity.
     
  40. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    962
    TBH, the more I thought about it, the less sense my question made. In case I'd want to switch to channel that isn't in the server instance I have to go through a reconnect anyway so it's more of an optimization that's doesn't seem worth the cost.

    Anyway, I think the current design with server worlds, ports and connect flow is pretty great! Very flexible and scalable.

    Can you tell us anything about the focus of the next updates? :)
     
    bb8_1 and FakeByte like this.
  41. Extrys

    Extrys

    Joined:
    Oct 25, 2017
    Posts:
    345
    No release notes?
    upload_2020-9-17_11-34-2.png
     
    FakeByte and bb8_1 like this.
  42. matpaul

    matpaul

    Joined:
    Jul 24, 2019
    Posts:
    5
    @timjohansson Hi, I have a question
    I have Owner Predicted ghost Player with Stats component

    Stats component has property health [GhostValue]
    Code (CSharp):
    1.  
    2. [GenerateAuthoringComponent]
    3. public struct Stats : IComponentData
    4. {
    5.     [GhostField]
    6.     public int health;
    7. }
    8.  
    When game starts server instantiates player and sets his health.

    The problem is when I calculate health after damage (I want to have it predicted on all clients)
    -> one client (owner) calculates it correctly
    -> server corrects
    -> another client (not owner) receives new value from server and applies the same diff to it, but not the previous value that it has locally

    so for example health 100 attack 10
    owner final hp after attack - 90hp
    server - 90hp
    another client - 80hp
     
  43. FakeByte

    FakeByte

    Joined:
    Dec 8, 2015
    Posts:
    147
    upload_2020-9-17_21-16-39.png
    Seems it is also using a new Unity Transport version
     
  44. bb8_1

    bb8_1

    Joined:
    Jan 20, 2019
    Posts:
    100
  45. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Not sure I understand the problem correctly, but if the ghost is not predicted you cannot calculate values for it in the GhostPredictionSystemGroup. Predicted ghosts will be rolled back to last received state and simulated forward by the prediction system group. Interpolated ghosts will not be rolled back, so if you calculate state for them in the prediction loop you will apply that change multiple times. You would have to either predict all players, or separate the health to a different ghost which is always predicted by all clients that the players reference.
     
    bb8_1 and matpaul like this.