Search Unity

Question Confusion about what entities are associated with certain components

Discussion in 'NetCode for ECS' started by coffeebuyer, May 24, 2023.

  1. coffeebuyer

    coffeebuyer

    Joined:
    Jan 31, 2023
    Posts:
    9
    In the below ServerSystem, OnUpdate() method I am confused about what entities are associated with certain components. I understand that an entity is created by a code-generated system when an RPC is received, and that entity has the <ReceiveRpcCommandRequest> component, but it also has a <GoInGameRequest> component?

    Code (CSharp):
    1. // When the rpc is received, an entity that you can filter on is created by a code-generated system
    2. foreach (var (reqSrc, reqEntity) in SystemAPI.Query<RefRO<ReceiveRpcCommandRequest>>().WithAll<GoInGameRequest>().WithEntityAccess())
    On the ClientSystem a GoInGameRequest component was added to the RPC entity before sending it to the server side so I'm abit confused about why it would be added to a new entity created on the server side.

    I thought the .SourceConnnection value was the "connnection" entity from the ClientWorld with the <NetworkStreamInGame> component. But the entity index and versions do not match up in the debugger.

    Code (CSharp):
    1. // reqSrc.ValueRO.SourceConnection is an entity;
    2. commandBuffer.AddComponent<NetworkStreamInGame>(reqSrc.ValueRO.SourceConnection);
    3. var networkId = networkIdFromEntity[reqSrc.ValueRO.SourceConnection];
    Apologies for the excessive code comments in advance...
    Code (CSharp):
    1. //[BurstCompile]
    2.     public void OnUpdate(ref SystemState state)
    3.     {
    4.         var prefab = SystemAPI.GetSingleton<CubeSpawner>().Cube;
    5.         // GetName()
    6.         state.EntityManager.GetName(prefab, out var prefabName);
    7.         var worldName = state.WorldUnmanaged.Name;
    8.  
    9.         var commandBuffer = new EntityCommandBuffer(Allocator.Temp);
    10.         // This ComponentLookup<T> is cached by a system across multiple system updates, .Update() performs the minimal updates to make the type handle safe.
    11.         networkIdFromEntity.Update(ref state);
    12.  
    13.         // When the rpc is received, an entity that you can filter on is created by a code-generated system
    14.         foreach (var (reqSrc, reqEntity) in SystemAPI.Query<RefRO<ReceiveRpcCommandRequest>>().WithAll<GoInGameRequest>().WithEntityAccess())
    15.         {
    16.             // reqSrc.ValueRO.SourceConnection is an entity;
    17.             commandBuffer.AddComponent<NetworkStreamInGame>(reqSrc.ValueRO.SourceConnection);
    18.             var networkId = networkIdFromEntity[reqSrc.ValueRO.SourceConnection];
    19.  
    20.             Debug.Log($"'{worldName}' setting connection '{networkId.Value}' to in game, spawning a Ghost '{prefabName}' for them!");
    21.  
    22.             var player = commandBuffer.Instantiate(prefab);
    23.  
    24.             // GhostOwner
    25.             commandBuffer.SetComponent(player, new GhostOwner { NetworkId = networkId.Value });
    26.  
    27.             // Add the player to the linked entity group so it is destroyed automatically on disconnect
    28.             commandBuffer.AppendToBuffer(reqSrc.ValueRO.SourceConnection, new LinkedEntityGroup { Value = player });
    29.  
    30.            
    31.             commandBuffer.DestroyEntity(reqEntity);
    32.         }
    33.         commandBuffer.Playback(state.EntityManager);
    34.     }
     
  2. NikiWalker

    NikiWalker

    Unity Technologies

    Joined:
    May 18, 2021
    Posts:
    316
    For posterity, here is the answer I gave directly in the discord.

    RPCs can be sent by either the client or server by adding:
    1. The RPC component type. In this case, the example is `GoInGameRequest`. It doesn't always add this component, it's just this specific example. We should be clearer about that.
    2. The `SendRpcCommandRequest`. On the server, you can use this component to specify whether or not the RPC is a broadcast (server to all clients) or only sent to a specific client.

    Then, code generated NetCode systems will:
    1. Serialise that RPC entity, adding the serialised data to the reliable UTP pipeline stage.
    2. Destroy said entity automatically.

    When a world receives a reliable RPC, it'll:
    1. Create a new entity (with no association to the entity on the other world at all).
    2. Add the RPC component type you specified, and deserialize the RPC data back into said component. In this example, it is again the `GoInGameRequest`.
    3. Add the `ReceiveRpcCommandRequest` component (which tells you who sent the RPC).

    Thus, you're able to write a system that checks for those 2 components, and process ALL RPCs of that type on this frame in an ECS manner (e.g. via a bursted job).

    Tertle was alluding to the fact that IF you were to add an Entity reference as a FIELD of an RPC, AND that entity points to a ghost, NetCode will automatically "patch" said field reference so that it'll point to the "same" ghost on the other world.

    Just like we do for GhostField Entity component fields.

    But note:
    - The ghost entity on the client is not the same entity as on the server. They are two different entity instances because they're in different worlds.
    - We (NetCode) use the `GhostInstance` component to create a mapping, so that we can LINK the ghost entity on the server with the corresponding Ghost entity on the client.
     
    BackgroundMover and coffeebuyer like this.
  3. coffeebuyer

    coffeebuyer

    Joined:
    Jan 31, 2023
    Posts:
    9
    Thank you very much for taking your time to explain these details! I have gotten over a hump in understanding the communication portion of the NetworkedCube example.

    In the GoInGameServerSystem OnCreate() method, why exactly is this EntityQuery cached?:
    Code (CSharp):
    1. var builder = new EntityQueryBuilder(Allocator.Temp)
    2.             .WithAll<GoInGameRequest>()
    3.             .WithAll<ReceiveRpcCommandRequest>();
    4.  
    5. state.RequireForUpdate(state.GetEntityQuery(builder));
    Is it so that the SystemAPI.Query in the OnUpdate() method will return faster?:
    Code (CSharp):
    1. foreach (var (reqSrc, reqEntity) in SystemAPI.Query<RefRO<ReceiveRpcCommandRequest>>().WithAll<GoInGameRequest>().WithEntityAccess())
     
    NikiWalker likes this.
  4. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    900
    The
    Code (csharp):
    1.  
    2. state.RequireForUpdate(state.GetEntityQuery(builder));
    3.  
    is used to instruct ECS to run this system only if the are entities that match the given query. The system OnUpdate is not called (early exit) in case no entities of this kind exists.

    We could not use the [RequireMatchingQueriesForUpdate] attribute on the system because we also requires that the NetCubeSpawner exist.

    As soon as you use RequireForUpdate, only the required queries are checked and the system does not run if any of these is empty.

    In general, you can't mix and match RequireForUpdate and RequireMatchingQueriesForUpdate attribute.

    If we didn't have that requirement, you could have just specified the system to run if there are matching queries like this:

    Code (csharp):
    1.  
    2. [BurstCompile]
    3. [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation)]
    4. [RequireMatchingQueriesForUpdate]
    5. public partial struct GoInGameServerSystem : ISystem
    6. {
    7.    [BurstCompile]
    8.    public void OnUpdate(ref SystemState state)
    9.    {
    10.        ...
    11.    }
    12. }
    13.  
    And this work because the SystemAPI generated underlying queries that are added to the system when iterating over entities.
    Code (csharp):
    1.  
    2. foreach (var (reqSrc, reqEntity) in SystemAPI.Query<RefRO<ReceiveRpcCommandRequest>>().WithAll<GoInGameRequest>().WithEntityAccess())
    3.  
    And these queries are the one that the RequireMatchingQueriesForUpdate attribute allow to check.

    I would suggest to look at the https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/systems-overview.html for a more comprehensive information about how the system update work.
     
    coffeebuyer likes this.
  5. coffeebuyer

    coffeebuyer

    Joined:
    Jan 31, 2023
    Posts:
    9
    Thank you, I think I understand, is it correct to say that?:

    The RequireForUpdate() method will override any other queries cached by a system including a [RequireMatchingQueriesForUpdate] attribute. And since we are using the RequireForUpdate() method for the cube spawner, we must add another RequireForUpdate() that matches the entity with <GoInGameRequest> and <ReceiveRpcCommandRequest> components.

    While I did find [RequireMatchingQueriesForUpdate] in the API, I have not been able to find general documentation for system attributes in the manual. Does this exist for the current version 1.0.10?
     
  6. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    900
    coffeebuyer likes this.
  7. ScallyGames

    ScallyGames

    Joined:
    Sep 10, 2012
    Posts:
    47
    Sadly the documentation is somewhat lacking in clarity on what actually breaks RequireMatchingQueriesForUpdate (I've submitted the issues through the respective "report a problem" options in the docs).

    Does RequireForUpdate<SomeSingleton>() also break RequireMatchingQueriesForUpdate? If I'm not mistaken, usually when dealing with RPCs you also need access to EndSimulationEntityCommandBufferSystem.Singleton to destroy the entity containing the ReceiveRpcCommandRequest component.
    Does that mean I should always specify

    Code (CSharp):
    1. var rpcQuery = new EntityQueryBuilder(Allocator.Temp)
    2.                       .WithAll<SomeRpcCommand>()
    3.                       .WithAll<ReceiveRpcCommandRequest>();
    4. state.RequireForUpdate(state.GetEntityQuery(rpcQuery));
    5. state.RequireForUpdate<EndSimulationEntityCommandBufferSystem.Singleton>();