Search Unity

DOTS Multiplayer discussion

Discussion in 'NetCode for ECS' started by PhilSA, Jun 14, 2019.

Thread Status:
Not open for further replies.
  1. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    We are planning to release 0.3.0 soon, but it is a big change containing a rewrite of ghost authoring so I don't know how long it will take to get it out
     
    PhilSA and optimise like this.
  2. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    You can add additional types by extending GhostSnapshotValue and adding an it to GhostSnapshotValue.GameSpecificTypes - see https://github.com/Unity-Technologi...es/Asteroids/Transform2dGhostSnapshotValue.cs .
    You can also add override which fields are serialized by default without modifying the source by adding an GhostAuthoringComponent.GhostComponent to the dictionary GhostAuthoringComponentEditor.GhostDefaultOverrides .
    Both of these will change significantly in netcode 0.3.0 though, but it will still be possible to do something similar

    For the physics types specifically I think it might be tricky to implement serialization for some of those directly since they are likely to contain entity and rigid body references. We do support weak references to entities, but only if the entities are ghosts and you have to be aware that the entity might resolve to null even if it does exist since the resolve is very conservative
     
  3. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    I think that would make sense, we have just not had time to actually do it yet
     
    Mikael-H, PhilSA and bb8_1 like this.
  4. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    You can keep a serialized AddedComponents : IComponentData with flags for which components are available and add / remove according to that. The main benefit of that over an RPC is that the entity is guaranteed to exist, with an RPC the rpc can arrive before the entity is spawned and you generally do not time the add / remove of component with other data changes.
    If you are serializing data for the components you add / remove it gets much trickier though.
    We are looking at adding support for marking components as optional in the ghost and have netcode automatically add / remove them - but I don't have an ETA for that yet.
     
    bb8_1 and Lukas_Kastern like this.
  5. pavelmo4alov

    pavelmo4alov

    Joined:
    Mar 13, 2013
    Posts:
    14
    Enzi and Kelevra like this.
  6. GambitMonkey

    GambitMonkey

    Joined:
    Apr 5, 2016
    Posts:
    65
    With 0.3.0 being released are you all at any point going to update or consolidate the many samples that are out there?

    This is one I have learned from in the past https://github.com/Unity-Technologies/DOTSSample, but there are a lot of changes in the animation package (0.6.0) and after messing with it for about 2 hours today I have decided it isnt worth it. Updating the netcode wasn't horrible, but the animation stuff is just broken.

    I know stuff is still in preview, but the samples are worth while to the community. The Unity Sample Core stuff are good simple samples, but need a bit more meat. :)

    Thanks!
     
  7. FakeByte

    FakeByte

    Joined:
    Dec 8, 2015
    Posts:
    147
    The multiplayer sample here https://github.com/Unity-Technologies/multiplayer are always updated to the latest release.

    The DOTS sample project is used by unity internally to validate and test their new versions and they mentioned they often use unity engine versions that are ahead of the public versions and thats why they cant update it often, that said unity said they would update it last month, but didn't do it, maybe if you are lucky it will receive an update soon.
     
  8. Ghosthowl

    Ghosthowl

    Joined:
    Feb 2, 2014
    Posts:
    228
    How do handle GhostSnapshotValue with Netcode 0.3? It seems all remnants of GhostSnapshotValue have been removed, but there is nothing in the changelog about what to use now or how this is handled.

    Also the links in this post are not available for me. Perhaps I need to be included in some sort of organization to see them?
     
    bb8_1 likes this.
  9. Stroustrup

    Stroustrup

    Joined:
    May 18, 2020
    Posts:
    142
    in netcode3 there is a bug where if irpcommand struct is either set to internal or private, project becomes stuck in an endless code generation loop and crashes after a bit of time.
     
  10. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    olenka_moetsi likes this.
  11. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Subscenes is the recommended workflow for entities in general, so it is just following ecs best practices.
     
  12. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    That sounds bad, can you please file a bug for it?
     
  13. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    You need to create an assembly with a name ending with ".NetCodeGen", implement `IGhostDefaultOverridesModifier` in that assembly and add your new GhostSnapshotValues in the ModifyTypeRegistry method.
     
  14. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @timjohansson probably not the best place to ask but... where is the right place to look on documentation on how to handle the subscene workflow?

    I checked out the ECS/Netcode/Transport documentation and the Unite videos and haven't found a good "what and why" for the best way to structure a project with scenes/subscenes for ECS.
     
    olenka_moetsi, bb8_1 and davidgilbert like this.
  15. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    For an introduction there's a sample in the EntityComponentSystemSamples repo on github. For more advanced use-cases I don't know of the top of my head, probably better to ask that in a separate thread.
     
    adammpolak and bb8_1 like this.
  16. Ashkan_gc

    Ashkan_gc

    Joined:
    Aug 12, 2009
    Posts:
    1,124
    Just wanted to add to this the fact that for priorities, we need to be able to set an entity's chunk based on something other than distance and somehow use a per chunk component to say how that chunk's priority changes.

    In a game similar to say Halo with lots of objects and based on object importance, some very close object, a grenade on ground with a good distance, is not as important as the rocket coming toward you from far away.
     
  17. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @timjohansson I wanted to double check that the docs in "Getting Started" is out of date and we shouldn't use ConvertToClientServerEntity component.

    upload_2020-9-23_10-1-53.png

    It seems like the docs further down in "Getting Started" are updated with the new patterns so I wanted to make sure I didn't misunderstand something.
     
    Last edited: Sep 23, 2020
    olenka_moetsi likes this.
  18. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    The getting started doc has not been updated to use sub-scenes. Sub-scenes is the recommended way to author content, ConvertToClientServerEntity still works but is deprecated
     
    adammpolak likes this.
  19. pavelmo4alov

    pavelmo4alov

    Joined:
    Mar 13, 2013
    Posts:
    14
    Help me please, how to change PrefabType at GhostComponent generated by unity? At my own components i can add [GhostComponent(PrefabType=...)] attribute, but how do this at other components ? For example - PhysicsCollider, PhysicsMass etc
     

    Attached Files:

  20. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @timjohansson I was wondering about the BuildSettings folder in the multiplayer example...

    1. Is there a place I should look to understand what are good approaches to "BuildSettings" for networked projects? (I am pretty sure my next questions are rudimentary...)

    2. Why does OSX have 3 different BuildSettings files, while Linux and Win have 2.
    upload_2020-10-1_18-31-8.png

    3. What is up with "Base, Client, Server" and how does that relate to the platform specific files?
    4. If I wanted to deploy to WebGL would I have to make a WebGL - Client BuildSettings file?
     

    Attached Files:

    olenka_moetsi likes this.
  21. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    You need to create an assembly with a name ending with ".NetCodeGen", implement `IGhostDefaultOverridesModifier` in that assembly and modify the dictionary passed to Modify, add new entries for component full name -> the attribute you want to use.
     
    pavelmo4alov likes this.
  22. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    I don't know what the best place for that is. I mostly just look at which components are available and check the docs for the ones I think I would like to use (click "add componnet" on a build setting to see what's available)

    Looks like we just forgot to add ClientServer for Win and Linux, I'm guess the change was made on mac initially. There is nothing preventing CLientServer from working on other platforms.

    It's inheritance. Base defines the project name, sets of scenes to use etc. Client and Server both extend Base and adds NetCodeConversionSettings with different values for Target. You use the guid for the client and server files to tell the sub-scene streaming system which version of the generated subscene files to load. They also add some defines.
    The platform specific files extend either Client or Server and adds a ClassicBuildProfile setting the platform to use. Everything else is just inherited.
    It's mainly setup this way so we can change the shared setting in a single place instead of having to make changes to every single platform if we for example wanted to add an additional scene to the builds.

    Yes, you would have to create a WebGLClient which has Client as a base (under Shared configurations), add a ClassicBuildProfile component and set the platform.
    Keep in mind that we do not have a transport backend for WebSockets though, so it would not be able to connect to a server and might even fail to build.
     
    adammpolak likes this.
  23. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @timjohansson

    Another addition I noticed with the github multiplayer update is:

    - ConfigureEditorSystem.cs
    - ConfigureSystems.cs

    upload_2020-10-10_9-57-13.png

    1. Is there any where I can go that explains these systems?
    I checked out the ECS documentation and wasn't able to find any information about the addition of these systems.

    2. Are these systems now required for netcode, what do they do?
    (I see what the system does but why is it now needed in netcode 0.4)

    3. What is up with the the hash?
    upload_2020-10-10_9-59-11.png

    Thank you!
     
    olenka_moetsi likes this.
  24. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @timjohansson

    Hoping to understand if I am understanding the multiplayer Asteroids example correctly for the server loading a level to a client.

    From LoadLevelSystem.cs (server folder)
    Code (CSharp):
    1. protected override void OnUpdate()
    2.         {
    3.             ...
    4.             JobHandle levelDep;
    5.             var commandBuffer = m_Barrier.CreateCommandBuffer();
    6.             var level = m_LevelGroup.ToComponentDataArrayAsync<LevelComponent>(Allocator.TempJob, out levelDep);
    7.  
    8.             var requestLoadJob = Entities.WithNone<LevelRequestedTag>().WithReadOnly(level).WithDisposeOnCompletion(level).
    9.                 ForEach((Entity entity, in NetworkIdComponent netId) =>
    10.             {
    11.                 commandBuffer.AddComponent(entity, new LevelRequestedTag());
    12.                 var req = commandBuffer.CreateEntity();
    13.                 commandBuffer.AddComponent(req, new LevelLoadRequest
    14.                 {
    15.                     width = level[0].width,
    16.                     height = level[0].height,
    17.                     playerForce = level[0].playerForce,
    18.                     bulletVelocity = level[0].bulletVelocity
    19.                 });
    20.                 commandBuffer.AddComponent(req, new SendRpcCommandRequestComponent {TargetConnection = entity});
    21.             }).Schedule(JobHandle.CombineDependencies(Dependency, levelDep));
    22.             Dependency = requestLoadJob;
    23.             m_Barrier.AddJobHandleForProducer(Dependency);
    24.         }
    - Because we want to pass through LevelComponent data to a job (the job will be sending an RPC to the client) we need to get the data in a NativeArray
    - We use `ToComponentDataArrayAsync` to put the component data in a NativeArray and because this is Async we also save the dependencies of this work to `levelDep`
    - We are able to pass through the NativeArray data to the job through the `.WithReadOnly` method and are able to ensure we clean up our memory using the `.WithDisposeOnCompletion` method
    - We are able to query to the Network Connection Entities by querying for entities with a NetworkIdComponent
    - We know which Network Connection Entity (NCE) to send RPCs through based on the NCE not having a LevelRequestedTag

    Here is where I am hoping you could clarify...

    1. I am having a tough time wrapping my head around this line here:

    `.Schedule(JobHandle.CombineDependencies(Dependency, levelDep))`

    From the documentation on the Dependency property:
    By default, a system manages its ECS-related dependencies using its Dependency property. By default, the system adds each job created with Entities.ForEach and [Job.WithCode] to the Dependency job handle in the order that they appear in the OnUpdate() function. You can also manage job dependencies manually by passing a [JobHandle] to your Schedule functions, which then return the resulting dependency. See Dependency for more information.

    Before OnUpdate(), the Dependency property represents the combined job handles of any job that writes to the same components that the current system reads -- or reads the same components that the current system writes to. When you use Entities.ForEach or Job.WithCode, the system uses the Dependency property to specify a job’s dependencies when scheduling it. The system also combines the new job's JobHandle with Dependency so that any subsequent job scheduled in the system depends on the earlier jobs (in sequence).​

    We are able to combine both the dependencies of the job and the memory allocation job with .CombineDependencies(Dependency, levelDep)

    Does this mean that Unity has already added the requestLoadJob's dependencies to Dependency before `.CombineDependencies` has been called? OR, is Unity combining the dependencies of before the OnUpdate loop with the dependencies of levelDep, and then requestLoadJob's dependencies are added automatically and not explicitly.

    2. I have updated the code to remove returning a JobHandle

    Code (CSharp):
    1. protected override void OnUpdate()
    2.         {
    3.             ...
    4.             JobHandle levelDep;
    5.             var commandBuffer = m_Barrier.CreateCommandBuffer();
    6.             var level = m_LevelGroup.ToComponentDataArrayAsync<LevelComponent>(Allocator.TempJob, out levelDep);
    7.  
    8.             Entities.WithNone<LevelRequestedTag>().WithReadOnly(level).WithDisposeOnCompletion(level).
    9.                 ForEach((Entity entity, in NetworkIdComponent netId) =>
    10.             {
    11.                 commandBuffer.AddComponent(entity, new LevelRequestedTag());
    12.                 var req = commandBuffer.CreateEntity();
    13.                 commandBuffer.AddComponent(req, new LevelLoadRequest
    14.                 {
    15.                     width = level[0].width,
    16.                     height = level[0].height,
    17.                     playerForce = level[0].playerForce,
    18.                     bulletVelocity = level[0].bulletVelocity
    19.                 });
    20.                 commandBuffer.AddComponent(req, new SendRpcCommandRequestComponent {TargetConnection = entity});
    21.             }).Schedule(JobHandle.CombineDependencies(Dependency, levelDep));
    22.             m_Barrier.AddJobHandleForProducer(Dependency);
    23.         }
    The code runs, is this "lucky" or expected behavior?

    3. If this was a game where 2,000 clients could connect and play simultaneously, would there be anything from preventing the requestLoadJob to use ScheduleParallel() (to speed up sending out RPCs to clients)?

    4. Finally, I was wondering if there is documentation on all the components that a Network Connection Entity comes with, or needs at some point?


    For some reason I cannot find in the manual or the API documentation what components "come with" a NCE (like NetworkIdComponent). You can find things like NetworkIdComponent in the documentation, but not that its parent is NCE, or that it "comes with" an NCE.

    NCE
    - CommandTargetComponent
    - NetworkIdComponent
    - NetworkStreamInGame
    - GhostConnectionPosition
    - NetworkStreamDisconnected
    - OutgoingRpcDataStreamBufferComponent

    Also in the Asteroids sample there are different ways of getting to the NCE using a .ForEach. LoadLevelSystem on server uses "NetworkIdComponent", InputSystem on the client uses "OutgoingRpcDataStreamBufferComponent". What is a good approach and when?

    Knowledge of how to interface with the NCE seems to be key to netcode, would be great if there was something to explain how NCE's are made (I know its from a socket connection, but needed to be inferred) and what they come with.
     
    Last edited: Oct 11, 2020
    olenka_moetsi likes this.
  25. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @timjohansson

    A few questions on LoadLevelSystem on the client/server, trying to understand how RPC's are being handled by netcode.

    My understanding:

    From Network connection documentation:
    upload_2020-10-10_18-8-45.png
    - So the Network Connection Entity (NCE) has 2 buffers for RPCs, 1 for incoming RPCs and 1 for outgoing RPCs

    From the RPC documentation:
    upload_2020-10-10_18-5-54.png

    From the Asteroids sample (in LoadLevelSystem.cs in server):
    Code (CSharp):
    1. var req = commandBuffer.CreateEntity();
    2.                 commandBuffer.AddComponent(req, new LevelLoadRequest
    3.                 {
    4.                     width = level[0].width,
    5.                     height = level[0].height,
    6.                     playerForce = level[0].playerForce,
    7.                     bulletVelocity = level[0].bulletVelocity
    8.                 });
    9.                 commandBuffer.AddComponent(req, new SendRpcCommandRequestComponent {TargetConnection = entity});
    - So we create an entity (req) and add the RPC component and the SendRpcCommandRequestComponent, great

    From RPCs documentation:
    In the previous example, the RpcExecutor.ExecuteCreateRequestComponent<OurRpcCommand>(ref parameters); function call to the IRpCommand creates an entity that you can filter on. To test if this works, the following example creates a system that receives the OurRpcCommand:
    ...
    The RpcSystem automatically finds all of the requests, sends them, and then deletes the send request. On the remote side they show up as entities with the same IRpcCommand and a ReceiveRpcCommandRequestComponent which you can use to identify which connection the request was received from.
    ...
    1. When is RpcExecutor.ExecuteCreateRequestComponent<LevelLoadRequest>(ref parameters) called? Who calls it? When is this triggered, and what triggers it?
    I have read and re-read the netcode documentation on RPCs and the scripting documentation and can't make sense of the sentence "the RpcExecutor.ExecuteCreateRequestComponent<OurRpcCommand>(ref parameters); function call to the IRpCommand creates an entity that you can filter on".

    2. What is up with TActionSerializer, TActionRequest?
    From documentation:
    upload_2020-10-10_17-56-9.png
    I get from the Asteroids sample that in order to use GetRpcQueue you need to enumerate the RPC twice (.GetRpcQueue<RpcLevelLoaded, RpcLevelLoaded>()) but I don't understand why it needs to be enumerated twice. Why are we calling the Rpc TActionSerializer and TActionRequest?


    From the LoadLevelSystem on the client:
    Code (CSharp):
    1. var commandBuffer = m_Barrier.CreateCommandBuffer().AsParallelWriter();
    2.             var rpcFromEntity = GetBufferFromEntity<OutgoingRpcDataStreamBufferComponent>();
    3.             var levelFromEntity = GetComponentDataFromEntity<LevelComponent>();
    4.             var levelSingleton = m_LevelSingleton;
    5.             var rpcQueue = m_RpcQueue;
    6.             Entities.ForEach((Entity entity, int nativeThreadIndex, in LevelLoadRequest request, in ReceiveRpcCommandRequestComponent requestSource) =>
    7.             {
    8.                 commandBuffer.DestroyEntity(nativeThreadIndex, entity);
    9.                 // Check for disconnects
    10.                 if (!rpcFromEntity.HasComponent(requestSource.SourceConnection))
    11.                     return;
    12.                 // set the level size - fake loading of level
    13.                 levelFromEntity[levelSingleton] = new LevelComponent
    14.                 {
    15.                     width = request.width,
    16.                     height = request.height,
    17.                     playerForce = request.playerForce,
    18.                     bulletVelocity = request.bulletVelocity
    19.                 };
    20.                 commandBuffer.AddComponent(nativeThreadIndex, requestSource.SourceConnection, new PlayerStateComponentData());
    21.                 commandBuffer.AddComponent(nativeThreadIndex, requestSource.SourceConnection, default(NetworkStreamInGame));
    22.                 rpcQueue.Schedule(rpcFromEntity[requestSource.SourceConnection], new RpcLevelLoaded());
    23.             }).Schedule();
    24.             m_Barrier.AddJobHandleForProducer(Dependency);
    3. Any specific reason we are destroying the entity using DestroyEntity(nativeThreadIndex, entity) instead of DestroyEntity(entity)?
    Wasn't sure if there was a reason or it was arbitrary. Why we going the ThreadIndex route?

    4. Any specific reason we are adding a component using AddComponent(nativeThreadIndex, requestSource.SourceConnection, new PlayerStateComponentData()) instead of AddComponent(requestSource.SourceConnection, new PlayerStateComponentData())?

    Why are we using threadIndex's here?

    5. Why are we sending an rpc with rpcQueue.Schedule(rpcFromEntity[requestSource.SourceConnection], new RpcLevelLoaded()) instead of sending it how the server did, with a SendRpcCommandRequestComponent? Is there any situations/benefits for using the OutgoingRpcDataStreamBufferComponent approach?
    The server sent the LevelLoadRequest rpc by creating an entity with that component and adding a SendRpcCommandRequestComponent, pretty simple.
    The client is doing a lot more to send an RPC.
    From the client code:
    Code (CSharp):
    1. private RpcQueue<RpcLevelLoaded, RpcLevelLoaded> m_RpcQueue;
    2. m_RpcQueue = World.GetOrCreateSystem<RpcSystem>().GetRpcQueue<RpcLevelLoaded, RpcLevelLoaded>();
    3. var rpcFromEntity = GetBufferFromEntity<OutgoingRpcDataStreamBufferComponent>();
    4. var rpcQueue = m_RpcQueue;
    5. rpcQueue.Schedule(rpcFromEntity[requestSource.SourceConnection], new RpcLevelLoaded());
    6. I thought the only way to bring local variables (like rpcFromEntity, LevelFromEntity, levelSingleton) into a job was through a NativeArray. How are they getting passed through into the job without being in a native array?


    From the RPCs Documentation:
    The IRpcCommandSerializer interface has three methods: Serialize, Deserialize, and CompileExecute. Serialize and Deserialize store the data in a packet, while CompileExecute uses Burst to create a FunctionPointer. The function it compiles takes a RpcExecutor.Parameters by ref that contains:

    • DataStreamReader reader
    • Entity connection
    • EntityCommandBuffer.Concurrent commandBuffer
    • int jobIndex
    Because the function is static, it needs to use Deserialize to read the struct data before it executes the RPC. The RPC then either uses the command buffer to modify the connection entity, or uses it to create a new request entity for more complex tasks. It then applies the command in a separate system at a later time. This means that you don’t need to perform any additional operations to receive an RPC; its Execute method is called on the receiving end automatically.​
    A lot of questions here, not sure what a lot of the terms are referring to.

    7. What does "Because the function is static, it needs to use Deserialize to read the struct data before it executes the RPC." mean, why is that required?

    8. Is RpcExecutor.Parameters automatically created (the DataStreamReader reader, Entity connection, etc) or does it have do the values have to be explicitly defined somehwere?

    9. It says there are 3 methods, Serialize, Deserialize, and CompileExecute, I do not see CompileExecute anywhere, is that method handled somewhere else?


    From RpcLoadLevel:
    Code (CSharp):
    1. [BurstCompile]
    2. public struct RpcLevelLoaded : IComponentData, IRpcCommandSerializer<RpcLevelLoaded>
    3. {
    4.     public void Serialize(ref DataStreamWriter writer, in RpcLevelLoaded data)
    5.     {
    6.     }
    7.  
    8.     public void Deserialize(ref DataStreamReader reader, ref RpcLevelLoaded data)
    9.     {
    10.     }
    11.  
    12.     [BurstCompile]
    13.     [MonoPInvokeCallback(typeof(RpcExecutor.ExecuteDelegate))]
    14.     private static void InvokeExecute(ref RpcExecutor.Parameters parameters)
    15.     {
    16.         var rpcData = default(RpcLevelLoaded);
    17.         rpcData.Deserialize(ref parameters.Reader, ref rpcData);
    18.  
    19.         parameters.CommandBuffer.AddComponent(parameters.JobIndex, parameters.Connection, new PlayerStateComponentData());
    20.         parameters.CommandBuffer.AddComponent(parameters.JobIndex, parameters.Connection, default(NetworkStreamInGame));
    21.         parameters.CommandBuffer.AddComponent(parameters.JobIndex, parameters.Connection, default(GhostConnectionPosition));
    22.     }
    23.  
    24.     static PortableFunctionPointer<RpcExecutor.ExecuteDelegate> InvokeExecuteFunctionPointer =
    25.         new PortableFunctionPointer<RpcExecutor.ExecuteDelegate>(InvokeExecute);
    26.     public PortableFunctionPointer<RpcExecutor.ExecuteDelegate> CompileExecute()
    27.     {
    28.         return InvokeExecuteFunctionPointer;
    29.     }
    30. }
    From RPCs documentation:
    upload_2020-10-10_19-25-5.png

    10. What is [MonoPInvokeCallback(typeof(RpcExecutor.ExecuteDelegate))] mean?
    Why was it not needed in the example in the RPC documentation?

    11. Why would you need to "create an entity that holds an RPC"?
    I am guessing this is for reasons like when you send data so you can then grab it, like the client did with the level data. If you don't need to grab any data from an RPC I am guessing that you do not need to have this happen, which we don't when we send the RpcLevelLoaded rpc to the server.

    12. Why is var rpcData = default(RpcLevelLoaded); rpcData.Deserialize(ref parameters.Reader, ref rpcData); necessary?
    It doesn't contain any "values" like LevelLoadRequest does. I am guessing the only data being "deserialized" that the server didn't already know is the NetworkConnectionEntity it came from (parameters.Connection)?

    I am guessing we could have made a system that received the RpcLevelLoaded request and added the PlayerStateComponentData, NetworkStreamInGame, GhostConnectionPosition to the NCE by using the ".SourceConnection" but that would have required an entire system instead of the rpc just running on its own.

    13. Why are we using AddComponent(parameters.JobIndex, parameters.Connection, new PlayerStateComponentData) instead of AddComponent(parameters.Connection, new PlayerStateComponentData)?
    What is up with using index's in this workflow?


    I know that is a lot sorry @timjohansson but I am hoping that the answers to these questions "ends" any confusion with RPCs.
     
    Lionious and olenka_moetsi like this.
  26. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @timjohansson

    A few questions about spawning. But first....

    WOW! Great improvements on the spawning flow for player objects and player spawned objects!!! Super appreciate the simplification of workflows from the netcode team! Much easier to understand than extending a partial class and the other methods from before!!
    I have been making a guide on how to set up a dots/netcode game and i was able to cut out over 50% of explanations on spawning.

    From SpawnSystem in /server

    Code (CSharp):
    1.         protected override void OnUpdate()
    2.         {
    3.             if (m_Prefab == Entity.Null)
    4.             {
    5.                 var prefabEntity = GetSingletonEntity<GhostPrefabCollectionComponent>();
    6.                 var prefabs = EntityManager.GetBuffer<GhostPrefabBuffer>(prefabEntity);
    7.                 for (int i = 0; i < prefabs.Length; ++i)
    8.                 {
    9.                     if (EntityManager.HasComponent<ShipTagComponentData>(prefabs[i].Value))
    10.                         m_Prefab = prefabs[i].Value;
    11.                 }
    12.                 if (m_Prefab == Entity.Null)
    13.                     return;
    14.                 m_Radius = EntityManager.GetComponentData<CollisionSphereComponent>(m_Prefab).radius;
    15.             }
    1. Why is if (m_Prefab == Entity.Null) return; included in the netcode 0.4 update?
    Noticed this line was added. Is this in case the assignment doesn't work a couple lines up?
    Line 12&13 above.

    Code (CSharp):
    1.                 if (!playerStateFromEntity.HasComponent(requestSource.SourceConnection) ||
    2.                     !commandTargetFromEntity.HasComponent(requestSource.SourceConnection) ||
    3.                     commandTargetFromEntity[requestSource.SourceConnection].targetEntity != Entity.Null ||
    4.                     playerStateFromEntity[requestSource.SourceConnection].IsSpawning != 0)
    5.                     return;
    2. I am trying to understand what each of the 4 conditionals are meant to check for and why all of the 4 are necessary? Does each conditional check for a different possible issue that can arise?
    - PlayerStateComponentData is added during loading a level, is this to make sure the level is loaded?
    - CommandTargetComponent check is confusing, doesn't that always come with a NCE and if we have an RPC it means we have an NCE?
    - targetEntity not equaling Null is to make sure that there isn't already a ship spawned I assume?
    - PlayerStateComponentData should not = 0 because it means the ship is spawning, can this happen if the RPC does not get destroyed before the OnUpdate loop is run again?

    From ComponentShipGhostSpawnSystem in ShipGhostSpawnSystem in /client
    Code (CSharp):
    1.         Entities
    2.             .WithNone<GhostShipState>()
    3.             .WithAll<ShipTagComponentData, PredictedGhostComponent>()
    4.             .ForEach((Entity entity) =>{
    5.             var state = commandTargetFromEntity[playerEntity];
    6.             state.targetEntity = entity;
    7.             commandTargetFromEntity[playerEntity] = state;
    8.             commandBuffer.AddComponent(0, entity, new GhostShipState());
    9.         }).Schedule();
    (big fan of ".WithAll<ShipTagComponentData, PredictedGhostComponent>()" being shorthand for "which one is my ship"?)

    3. What is the purpose of commandTargetFromEntity[playerEntity] = state? This seems like the reverse of two lines up and I can't wrap my head around why it is necessary. Didn't that assignment already take place?
    I understand that you need to assign the client's NCE CommandTargetComponent's targetEntity to the Ship.
    Doesn't state.targetEntity = entity do that?

    4. What is the purpose of GhostShipState?
    I know it is a ISystemStateComponentData, but why is this applied to the prefab?
    It seems to be used to find a player controlled ghost that does not have snapshot data and update the NCE CommandTargetComponent targetEntity null... what is that about? I have read about SystemStateComponentData in the ECS documentation but I can't figure out the link for why these types of components are used for Ghosts that have been "deleted" by the server. Why remove the GhostShipState component on the client if the entity is to be "deleted" anyway?


    Code (CSharp):
    1.             Entities.WithAll<ShipSpawnInProgress>().
    2.                 ForEach((Entity entity, in PlayerIdComponentData player) =>
    3.                 {
    4.                     if (!playerStateFromEntity.HasComponent(player.PlayerEntity) ||
    5.                         !connectionFromEntity[player.PlayerEntity].Value.IsCreated)
    6.                     {
    7.                         // Player was disconnected during spawn, or other error
    8.                         commandBuffer.DestroyEntity(entity);
    9.                         return;
    10.                     }
    11.  
    12.                     commandBuffer.RemoveComponent<ShipSpawnInProgress>(entity);
    13.                     commandTargetFromEntity[player.PlayerEntity] = new CommandTargetComponent {targetEntity = entity};
    14.                     playerStateFromEntity[player.PlayerEntity] = new PlayerStateComponentData {IsSpawning = 0};
    15.                 }).Schedule();
    4. What checks are happening in "!playerStateFromEntity.HasComponent(player.PlayerEntity)" and "!connectionFromEntity[player.PlayerEntity].Value.IsCreated)"?
    !playerStateFromEntity.HasComponent(player.PlayerEntity)
    so player.PlayerEntity is the Network Connection Entity
    playerStateFromEntity.HasComponent(Network Connection Entity), I don't understand what that is doing... it is checking if a component has a component which is an entity....? I don't know if the syntax is getting to me but I can't figure out the logic of that line. Same with !connectionFromEntity[player.PlayerEntity].Value.IsCreated)


    From SteeringSystem:
    Code (CSharp):
    1.             if (m_BulletPrefab == Entity.Null)
    2.             {
    3.                 var prefabEntity = GetSingletonEntity<GhostPrefabCollectionComponent>();
    4.                 var prefabs = EntityManager.GetBuffer<GhostPrefabBuffer>(prefabEntity);
    5.                 var foundPrefab = Entity.Null;
    6.                 for (int i = 0; i < prefabs.Length; ++i)
    7.                 {
    8.                     if (EntityManager.HasComponent<BulletTagComponent>(prefabs[i].Value))
    9.                         foundPrefab = prefabs[i].Value;
    10.                 }
    11.                 if (foundPrefab != Entity.Null)
    12.                     m_BulletPrefab = GhostCollectionSystem.CreatePredictedSpawnPrefab(EntityManager, foundPrefab);
    13.             }
    5. Double checking, so all player spawned predicted entities that are prefabs will need to go through this process of GhostCollectionSystem.CreatePredictedSpawnPrefab(EntityManager, prefab) in order to be used?
    Way better than the previous approach but wanted to make sure wasn't misinterpreting.


    From SteeringSystem
    Code (CSharp):
    1. var level = GetSingleton<LevelComponent>();
    2.             var commandBuffer = m_Barrier.CreateCommandBuffer().AsParallelWriter();
    3.             var deltaTime = Time.DeltaTime;
    4.             var displacement = 100.0f;
    5.             var playerForce = level.playerForce;
    6.             var bulletVelocity = level.bulletVelocity;
    7.             var bulletPrefab = m_BulletPrefab;
    8.             var currentTick = m_PredictionGroup.PredictingTick;
    9.             var inputFromEntity = GetBufferFromEntity<ShipCommandData>(true);
    10.             Entities.WithReadOnly(inputFromEntity).WithAll<ShipTagComponentData, ShipCommandData>().
    11.                 ForEach((Entity entity, int nativeThreadIndex, ref Translation position, ref Rotation rotation,
    12.                 ref Velocity velocity, ref ShipStateComponentData state,
    13.                 in GhostOwnerComponent ghostOwner, in PredictedGhostComponent prediction) =>
    14.             {
    15.                 if (!GhostPredictionSystemGroup.ShouldPredict(currentTick, prediction))
    16.                     return;
    17.                 var input = inputFromEntity[entity];
    18.                 ShipCommandData inputData;
    19.                 if (!input.GetDataAtTick(currentTick, out inputData))
    20.                     inputData.shoot = 0;
    21.  
    22.                 state.State = inputData.thrust;
    23.  
    24.                 if (inputData.left == 1)
    25.                 {
    26.                     rotation.Value = math.mul(rotation.Value,
    27.                         quaternion.RotateZ(math.radians(-displacement * deltaTime)));
    28.                 }
    29.  
    30.                 if (inputData.right == 1)
    31.                 {
    32.                     rotation.Value = math.mul(rotation.Value,
    33.                         quaternion.RotateZ(math.radians(displacement * deltaTime)));
    34.                 }
    35.  
    36.                 if (inputData.thrust == 1)
    37.                 {
    38.                     float3 fwd = new float3(0, playerForce * deltaTime, 0);
    39.                     velocity.Value += math.mul(rotation.Value, fwd).xy;
    40.                 }
    41.  
    42.                 position.Value.xy += velocity.Value * deltaTime;
    43.  
    44.                 var canShoot = state.WeaponCooldown == 0 || SequenceHelpers.IsNewer(currentTick, state.WeaponCooldown);
    45.                 if (inputData.shoot != 0 && canShoot)
    46.                 {
    47.                     if (bulletPrefab != Entity.Null)
    48.                     {
    49.                         var e = commandBuffer.Instantiate(nativeThreadIndex, bulletPrefab);
    50.  
    51.                         commandBuffer.SetComponent(nativeThreadIndex, e, position);
    52.                         commandBuffer.SetComponent(nativeThreadIndex, e, rotation);
    53.  
    54.                         var vel = new Velocity
    55.                             {Value = math.mul(rotation.Value, new float3(0, bulletVelocity, 0)).xy};
    56.  
    57.                         commandBuffer.SetComponent(nativeThreadIndex, e,
    58.                             new GhostOwnerComponent {NetworkId = ghostOwner.NetworkId});
    59.                         commandBuffer.SetComponent(nativeThreadIndex, e, vel);
    60.                     }
    61.  
    62.                     state.WeaponCooldown = currentTick + k_CoolDownTicksCount;
    63.                 }
    64.                 /*else if (canShoot)
    65.                 {
    66.                     state.WeaponCooldown = 0;
    67.                 }*/
    68.             }).ScheduleParallel();
    6. Why are we allowed to bring in local variables like playerForce and bulletVeolcity into the job without a NativeArray?

    7. What is going on with
    if (!input.GetDataAtTick(currentTick, out inputData))
    inputData.shoot = 0;?

    We already did the "ShouldPredict" check a couple lines up, what is this logic meant to accomplish?
    Are we pulling the ShipCommandData from the buffer that is at the current tick and storing it in inputData? If there isn't data at the current tick are we putting inputData.shoot = 0 and leaving all the other data values as null?


    From BulletGhostSpawnClassificationSystem:
    Code (CSharp):
    1. var spawnListEntity = GetSingletonEntity<PredictedGhostSpawnList>();
    2.         var spawnListFromEntity = GetBufferFromEntity<PredictedGhostSpawn>();
    3.         Dependency = Entities
    4.             .WithAll<GhostSpawnQueueComponent>()
    5.             .WithoutBurst()
    6.             .ForEach((DynamicBuffer<GhostSpawnBuffer> ghosts, DynamicBuffer<SnapshotDataBuffer> data) =>
    7.         {
    8.             var spawnList = spawnListFromEntity[spawnListEntity];
    9.             for (int i = 0; i < ghosts.Length; ++i)
    10.             {
    11.                 var ghost = ghosts[i];
    12.                 if (ghost.SpawnType == GhostSpawnBuffer.Type.Predicted)
    13.                 {
    14.                     for (int j = 0; j < spawnList.Length; ++j)
    15.                     {
    16.                         if (ghost.GhostType == spawnList[j].ghostType && !SequenceHelpers.IsNewer(spawnList[j].spawnTick, ghost.ServerSpawnTick + 5) && SequenceHelpers.IsNewer(spawnList[j].spawnTick + 5, ghost.ServerSpawnTick))
    17.                         {
    18.                             ghost.PredictedSpawnEntity = spawnList[j].entity;
    19.                             spawnList[j] = spawnList[spawnList.Length-1];
    20.                             spawnList.RemoveAt(spawnList.Length - 1);
    21.                             break;
    22.                         }
    23.                     }
    24.                     ghosts[i] = ghost;
    25.                 }
    26.             }
    27.         }).Schedule(Dependency);
    From Ghost Snapshots Documentation:
    You need to implement some specific code to handle the predicted spawning for player spawned objects. You need to create a system updating in the ClientSimulationSystemGroup after GhostSpawnClassificationSystem. The system needs to go through the GhostSpawnBuffer buffer stored on a singleton with a GhostSpawnQueueComponent. For each entry in that list it should compare to the entries in the PredictedGhostSpawn buffer on the singleton with a PredictedGhostSpawnList component. If the two entries are the same the classification system should set the PredictedSpawnEntity property in the GhostSpawnBuffer and remove the entry from GhostSpawnBuffer.​
    8. I am having a tough time making the link between the explanation in the documentation and
    "if (ghost.GhostType == spawnList[j].ghostType && !SequenceHelpers.IsNewer(spawnList[j].spawnTick, ghost.ServerSpawnTick + 5) && SequenceHelpers.IsNewer(spawnList[j].spawnTick + 5, ghost.ServerSpawnTick))". What is going on over here with the spawnTick and the + 5 and updating different lists?

    The reason we do not need to do this same type of check and clean up for the player spawning is because we spawned the player prefab on the server, not the client, correct?


    @timjohansson let me know if you would like me to submit a PR with the answers to all the questions I submitted this weekend commented in line. Super appreciate you taking the time to explain the current functionality.
     
    Last edited: Oct 12, 2020
    Lionious, olenka_moetsi and bb8_1 like this.
  27. Stroustrup

    Stroustrup

    Joined:
    May 18, 2020
    Posts:
    142
    .WithChangeFilter< >() is always triggered even when the value doesn't change when the type has a ghost field. will this be amended in the future?
     
    olenka_moetsi and adammpolak like this.
  28. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    Has anyone been able to build the Multiplayer Asteroids sample?

    When I try to build and run for mac:

    upload_2020-10-20_9-53-5.png

    @timjohansson
     
    olenka_moetsi likes this.
  29. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    There is not a lot of documentation on it yet. The system tells the scene system if it should load the client or the server version of sub scenes when streaming them in. The GUID specified is the GUID if the build config file which has the NetCodeCoversionSettings component with a client or server value.
    You can skip that system and the NetCodeConversionSetting in the build config, but that will give you client/server data in a single sub-scene which is modified at runtime to the correct layout. So you will do more loading and more processing at runtime instead of at authoring time.
     
    olenka_moetsi likes this.
  30. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    They are added automatically before OnUpdate based on what you access in the job.
    I can't remember of the top of my head if Schedule(JobHandle) automatically sets the Dependency member or not, if it does it should work fine.
    I don't think so, it should work if you make the command buffer a ParallelWriter.
    We do not have docs for that yet, but it is on the list of things we want to add.
    You should pretty much always check for a NetworkIdComponent. The NetworkIdComponent is added when the connection is fully established and ready to be used, if you do not have that it is still connecting
     
    bb8_1, olenka_moetsi and adammpolak like this.
  31. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    If you use code-gen for rpcs it is generated automaticall, if not you call in in the method you return from IRpcCommandSerializer's CompileExecute method.
    The execute method is called by RpcSystem when an RPC is received from the network. That method will create a new entity with the serialized rpc component and a receive request so you can process it in another system.
    The rpc struct itself and the serializer for the rpc struct are separate interfaces. This is required for code-gen where you specify the rpc struct and we auto generate the serializer for it. The system in asteroids creates an rpc which has a single struct implementing both the rpc and the serializer for the rpc because that's how it worked before we had code-gen.
    The ParallelWriter - which is the only thing that works in a ParallelFor - requires that you specify a sortKey.
    No, it has just not been converted to the new workflow. Using the queue directly has slightly less latency, but it is not going to be a big difference.
    No, you can pass a copy of any blittable data to the job. The FromEntity things are native containers - similar to NativeArray.
    The execute method is static, so you cannot access "this", you need to deserialize the data for the current instance before you can do anything.
    It is auto created.
    Not sure I understand, the sample right below has a CompileExecute in it. The CompileExecute method is only called once when registering the rpc with the RpcSystem.
    It's required for il2cpp and burst aot (building players) to find the method correctly iirc
    The function processing the RPC can't really do much, so generally you create a new entity so you can write a system that triggers when the rpc has been received. Since you are in a job you cannot just start modifying other data easily.
    You don't need to call deserialize if there are no fields, but not doing so means that if you do add a serialized member after the fact and forget that you didn't call deserialize it will just not work.
    It's required when working in parallel jobs
     
  32. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    It's there as a safeguard for when the prefab does not exist.
    It's pretty defensive and checks all the things the code assumes, I don't expect all of those cases are actually possible.
    ComponentDataFromEntity does not support ref returns, so somethingFromEntity[ent].value = 0; will create a copy of the coponent and modify that copy - not the component on the entity.
    If something has a system state component deleting the entity will remove all non-system state components instead of actually deleting it. This allows you to write systems which operate on "deleted" entities. In this case we use that to clean up some state when a ship is destroyed.
    Yes, if you are predictive spawning it on the client. If you just wait for hte server to spawn it for you there is no need to use it.
    That has always been ok for blittable data, but you get a copy of the values, modifications to them will not be visible outside the job.
    If the tick you asked for does not exist (can happen on server) you get the closest command before the tick you asked for. We do not want to repeat one time events like shooting, so we clear it out when we did not get the exact input we asked for.
    That is used to detect if it is the same ghost. If it is the same ghost type and the spawn tick is within 5 frames. If that is the case we assume it is the same entity and the updating of the spawn lists happens within that if
     
  33. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    We always apply the snapshots, we have no short term plans to change that in the generic case. It does work in some cases when using static optimization mode if no fields of the ghost was modified since we skip applying the data in that case.
     
    bb8_1, olenka_moetsi and adammpolak like this.
  34. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    That looks like you built using the build menu, you have to use the build configs in Assets/BuildSettings to build any dots project with sub-scenes
     
    bb8_1, olenka_moetsi and adammpolak like this.
  35. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @timjohansson

    I am making a 3D version of the Asteroids sample.

    - I have instantiated 4,000 spheres (prefabs)
    - if an asteroid goes out of a cube bounds, it is destroyed and a new one is generated

    At 400 spheres, they seem to move smoothly
    At 4000 spheres, they seem to "jump" to the next location (low frame rate)



    Checking out the entity debugger I see that RenderMeshSystemV2 is the issue
    Screen Shot 2020-10-21 at 8.35.38 AM.png
    And it is taking ~38 ms to complete

    1. Am I using the hybrid renderer correctly?
    2. Why does the RenderMeshSystemV2 not "get counted" when calculating the frame rate?
     
    Last edited: Oct 21, 2020
    bb8_1 and olenka_moetsi like this.
  36. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    It looks to me like it is not a CPU perf issue at all, but a problem with the asteroids being sent at too low frequency. You should look in the profiler rather than the entity debugger for performance issues.

    Assuming it is a send frequency problem there are several ways you can work around it.
    1. If possible, spawn the asteroids over a larger area and use distance based priority. That will make mean you have fewer asteroids close to the camera, and those asteroids are sent more frequently. The asteroids far away will still have uneven movement, but if they are far enough away it doesn't really matter that they move in 1 meter increments, that's still less than 1 pixel on screen.

    2. Base your work on the static optimization feature used in asteroids. In this mode we only sync the initial position and velocity, current position is calculated from that on both client and server as needed. This means we don't need to send any data per asteroid except when they spawn (or change velocity if you want to support that).

    3. This is more of a hack / workaround that I would not recommend outside of testing, increase NetworkTimeSystem.KInterpolationTimeNetTicks so you are more likely to have received a snapshot (we will make it easier to tweak this in the future)

    4. Even more of a hack / workaround would be to increase the default packet size so you send multiple packets per frame and can send all data. You might be able to fit all asteroids in a single snapshot that way, but both server runtime cost, bandwidth and packet loss will be several times worse.
     
    adammpolak and bb8_1 like this.
  37. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @timjohansson thank you for the heads up! Will give the static optimization a shot.

    1. Does static optimization work with the new Physics package?
    - I have added physics shape and physics body to the asteroids
    - Will the asteroids "bouncing" off each other still work?

    2. What is the best way to interpret the profile to find the "jittering"?
    - The profiler modules "Network Messages" and "Network Operations" seem to be empty
    upload_2020-10-21_8-54-13.png
     
  38. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    It should, but in this specific case it means you cannot just synchronize initial position + velocity, and since the asteroids are always moving the ghosts will never be static and the optimization will not kick in. If they were objects falling to the ground and then staying there it would have worked.

    For CPU spikes, look at the top graph with frame time to find spikes, then look at the main thread to see why there was a spike. In this case it looks like it is caused by gc. Do you have full leak detection on?
    You should probably investigate and fix that, but generally you are well above 30fps so it is most likely not what is causing the jitter.
    If you want to dig into what is happening on the networking side you should use Multiplayer > Open NetDbg . If you enable the graphs for "Show interpolation delay" and "Show snapshot age" on the client world you should see if the snapshot age goes above the interpolation delay. Whenever that happens you will get jitter because it is trying to display a state which has not arrived yet and clamps to the most recent stat it got instead.
     
  39. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @timjohansson sorry I am sure I am being dense here but to clarify...

    Static optimization is meant for objects that stay at rest (0 velocity)?
    - Seems that way from the name and your last comment but I thought because we have position + velocity it means the optimization can be used for moving objects?

    If from an position+velocity client & server can calculate next position using static optimization, if client & server are both using Physics, should they not also be able to calculate the objects bouncing of each other as well?
    - If that is not the case I am guessing on collisions the server should synchronize each collision with a new position and velocity so static optimization can continue on the new trajectories?
     
  40. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    1. I think the garbage collection has to do with a warning that appeared when implementing LoadLevelSystem.cs and asking it to update in ServerSimulationSystemGroup "JobTempAlloc has allocations that are more than 4 frames old"
    https://forum.unity.com/threads/job...e-more-than-4-frames-old.693394/#post-6433985

    - I am implementing full stack traces for leak detection and will check it out to confirm

    2. I can't seem to find where the snapshot age is compared to the interpolation delay. I have snapshot age turned on but it doesn't seem to appear on the graphs below. How can I make the comparison?
    upload_2020-10-21_9-58-36.png
     
  41. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Yes, static optimization are for objects where the snapshot contains 0 changes.
    For asteroids we remove serialization of Translation, synchronize InitialTranslation and Velocity. Every frame we calculate Translation = InitialTranslation + Velocity*ticksSinceStart.
    So all data we synch over the network is static.
    Yeah, if you update the initial position + current velocity on the server constantly it might be possible to get it working, but it is significantly more complex since you cannot easily make sure the client and server calculate the position the exact same way.
     
    adammpolak likes this.
  42. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    At the very bottom of the screenshot there are a few lines (under all the bars. The red line is interpolation delay, the purple line is snapshot age. In this screenshot the snapshot age stays under the interpolation delay so I would expect movement to be smooth - and I'm guessing this is the 400 asteroids case. If the purple line goes above the red one is when you start seeing jitter. You can select those frames and the text details should give you the exact values for both.
     
  43. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @timjohansson that was actually from the 4,000 asteroids example with a lot of jitter.

    I turned on the full stack trace and ran the NetDbg again


    It seems like the Snapshot Age literally went off the graph and was no longer visible.

    It seemed like the Snapshot age line diverged?
     
  44. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Oh, right - I forgot we added that, the graph shown minimum and maximum age. The line at the bottom is the age of the newest ghost you received, the top line is the age of the oldest ghost you have. The lines might diverge when running without full stacktrace too, but since the game is running fast that happens before you manage to connect netdbg.
    You should double check the raw numbers, select a frame by clicking one of the bars and look for "Snapshot age" in the text that shows up when you select it.
     
    adammpolak likes this.
  45. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @timjohansson

    1. So it seems like the remaining option is to use distance based priority to reduce jitter (due to Snapshot delivery) if the Asteroids must be interpolated?

    - seems like static optimization and sending initial position and velocity might be complicated and make stability difficult

    If the asteroids initial positions and velocities can be sent to the client, and the system of the asteroids bouncing into each other are deterministic (outside of a player shooting the asteroids) is there a way to have the client show what it "expects" to happen unless the server says otherwise?
    2. Can the clients "predict" the Asteroids' paths in order to remove the jitter?
    - So this way the state that has not arrived yet won't clamp the recent stat?
    - And if there is a difference in path (because an Asteroid was actually destroyed) it will correct?

    3. Taking a step back, why would a game designer want interpolated asteroids vs. predicted asteroids?
    - I understand why we need interpolated "players" (because players are not deterministic) but for deterministic systems is there a reason to not have clients predict the deterministic objects/projectiles/etc?
     
  46. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    That is what I would personally start with - it will give you lots of benefits even if you do something else on top of it, prediction / static optimization / etc, since it will mean you have more accurate data close to the camera where it matters the most.

    Not sure I follow exactly what you mean, but it to me it sounds like you are saying have all asteroids predicted. That would be fine, but we currently do not support running physics simulation in prediction (raycasts are ok though) since it requires moving the physics systems around and ghosting the correct physics components for roll-back. We are planning to work on physics simulation in prediction in the future, but that work has not started yet.
    If you do not run physics on the client I would expect that the asteroids visually overlap a bit on the client before the server corrects it, and when it does the asteroid will snap to the new position.
    We are also currently investigating how to support smoothing the miss-prediction corrections which would remove the snapping on server correction.
    Finally we are planning to look at extrapolation of snapshots, that would mean that if you did not get any new data from the server the client can supply a function that extrapolates the values based on the last 2 received snapshots. In this case that could give you similar results to running prediction without physics on the client. It might be possible to flag asteroids that did collide on the server and give them higher priority when sending to get rid of that - but I think that would require some extensions to netcode.

    The main reason to use interpolation is to reduce complexity in the simulation code and to save CPU cycles on the client, it can also save some bandwidth since prediction often requires that you synchronize more state.
    If you have enough CPU resources on the client and bandwidth you could even predict other players, but it would require that you send the inputs for all remote players, and you would probably want the smoothing of miss-predictions since there will be plenty of those. It is currently not easy to do mostly due to the inputs, but we are currently working on improving that.
     
    adammpolak and bb8_1 like this.
  47. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    966
    Netcode forum
    Who do we have to bug to get this work done?
    We've been asking for months. (Is it really that complicated? :))
     
  48. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    966
    Has anyone done any stress testing of NetCode 0.4? I'm currently preparing for it, hope everything goes well and I'm not leaving disappointed. :)

    I'm interested in pretty much any number. Cpu times under load with X amount of connections. Scalability, limits, technical constraints.
    Right now I'm at the stage were it works pretty well, I just don't know how well. My first goal is to get 100 stable connections with hopefully not a lot of CPU stress, then 1k and ultimately 10k per server instance. (those connections aren't all in 1 server world, 1 server world has around 4-6 players.)
     
    Lionious, FakeByte and bb8_1 like this.
  49. optimise

    optimise

    Joined:
    Jan 22, 2014
    Posts:
    2,129
    Any plan to make Enable / disable component Entities package feature working out of box into Netcode package when this feature releases? With this feature I will not longer need to create AddedComponents : IComponentData with flags to sync add / remove components between client and server. I can just Enable / disable component and netcode will automatically sync it. I guess need to add one more bool attribute called [GhostComponent(Sync = true/false)] to tell Netcode whether this component need to sync across network when Enable / disable this component.
     
  50. SebLazyWizard

    SebLazyWizard

    Joined:
    Jun 15, 2018
    Posts:
    234
    So speaking of physics, what is the recommended way to "hack" working physics prediction in? (until it will be officially supported)
    I'm struggling to develop functional workarounds to make it happen, so any advice or guideline would be nice.
     
    bb8_1, Lionious and adammpolak like this.
Thread Status:
Not open for further replies.