Search Unity

Does the Asteroids Sample "actually" use Auto Command Target?

Discussion in 'NetCode for ECS' started by adammpolak, May 30, 2022.

  1. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    I am checking out the updated Asteroids sample and it seems like the Server's SpawnSystem is setting the CommandTargetComponent targetEntity to the player entities when spawning players:

    https://github.com/Unity-Technologi...mples/Asteroids/Server/Systems/SpawnSystem.cs

    I see that the Ship prefab has "Support Auto Command Target" enabled
    upload_2022-5-30_10-51-17.png
    1. Would the Asteroids sample not work if the Server did not set the CommandTargetComponent on the Network Connection Entity?
    2. I see that the ShipGhostSpawnSystem.cs has been removed so I am guessing that means that the client does not need to set the CommandTargetComponent targetEntity but the server does?
     
  2. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    894
    Asteroids use auto-command target.
    We left the code that set the CommandTargetComponent because this way we could also test the sample in case the auto-command-target feature is not used (IIRC) or we just forgot to remove the code.

    It is not necessary to set the CommandTargetComponent ether on the server when the auto-command target is used in general.
    It is possible to send multiple command data stream using both the AutoCommandTarget and CommandTargetComponent at the same time.
     
  3. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    AutoCommandTarget does not work for thin clients since thin clients do not spawn any ghosts and the AutoCommandTarget relies on ghosts. So thin clients have a special code path where we do set the CommandTargetComponent - which is why it is still left on the server. If you remove it thin clients will not move but everything else should work.
     
    adammpolak likes this.
  4. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    I have been working through a project and I realize that I am not as clear on this as I thought.

    "AutoCommandTarget does not work for thin clients since thin clients do not spawn any ghosts and the AutoCommandTarget relies on ghosts."

    In InputSystem.cs

    It seems that thin clients do indeed spawn ghosts for themselves (?).

    1. In the sample project it says "
    // Special handling for thin clients since we can't tell if the ship is spawned or not
    ", how is this different than a normal client?
    2. If the thin client hits space bar (which it does every 100 frames), and it does not have a ghost (which it could tell by
    TryGetSingletonEntity<ShipCommandData>(out var targetEntity);
    ), then it should send an RPC no? I guess the question is why does
    TryGetSingletonEntity<ShipCommandData>(out var targetEntity);
    not work for a thin client?
     
  5. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    894
    Thin clients just send the input stream to server, They don't have ghosts and they don't decompress snapshots.
    They can send RPC as normal client does (in case of asteroid, for the initial spawn and level loading).

    Thin clients usually fake the present of a "Player" entity. In case of asteroids, we create an entity (that is not a ship) to which we add a command buffer (the ShipCommandData), This entity is then set as targetEntity to the
    CommandTargetComponent to make everything work.

    You can see that in the ThinInputSystem, where the entity is created once.
     
  6. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @timjohansson @CMarastoni

    I just tried to reimplement the Asteroids sample without setting the Network Connection Entity CommandTargetComponent's targetEntity field.

    What I learned is that this actually breaks the flow, the reason being is this line here in InputSystem:
    Code (CSharp):
    1. TryGetSingletonEntity<ShipCommandData>(out var targetEntity);
    • This expects there to be only 1 Player
    • But without seeing the CommandTargetComponent in SpawnSystem, the Server will respond multiple times to the PlayerSpawnRequest
    • That is because this check no longer works on the server side:
    • Code (CSharp):
      1.                 commandBuffer.DestroyEntity(entity);
      2.                 if (!playerStateFromEntity.HasComponent(requestSource.SourceConnection) ||
      3.                     !commandTargetFromEntity.HasComponent(requestSource.SourceConnection) ||
      4.                     commandTargetFromEntity[requestSource.SourceConnection].targetEntity != Entity.Null ||
      5.                     playerStateFromEntity[requestSource.SourceConnection].IsSpawning != 0)
      6.                     return;
    • This is because it will take a few frames for the ghost to appear on the client
    • While the client is waiting for the ghost to appear, it is sending more RPCs
    • This causes the server to spawn more ghosts
    So it appears that in the Asteroids sample not only did setting the CommandTargetComponent on the Network Connection Entity serve to send ICommandData, it seems to also act as a boolean "this Network Connection Entity already has a player, do not send them another one".

    This seems like it can be solved by:
    1. predictively spawning the player (so the client stops spawning players because
    Code (CSharp):
    1. TryGetSingletonEntity<ShipCommandData>(out var targetEntity);
    will be able to grab the entity next frame)
    2. Use PlayerStateComponentData to track if a player has been spawned (1 = spawned), and then set this back to 0 when destroying a player

    Is there another way for the server to know that the ghost has been "sent" to the client? This could be possibly a better 3rd solution.
     
  7. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    894
    Asteroid code with the assumption the command target is going to be set, since we make it work that way by using the presence of CommandTargetComponent to track some "state".
    If you remove setting the target, obviously some of the checks we have need to be changed.

    For the server in particular, once the the ship is spawned, it is added to the LinkedEntityGroup of the connection (index 1).
    On the server, instead of checking for the targetEntity != null it is possible to check for the length of the group for example. Or, because on the server we have the PlayerStateComponentData, just setting the entity there or setting a flag (and reset it when the ship is destroyed in the ClearShipPointerJob).
    In either cases you need to set that you spawned an entity for that player somewhere, and reset that whence the player disconnect.
    To check that you need to still store the entity. So, you already know that the player ship has been spawned.

    On the client side you may want to implement a simple state machine to avoid sending multiple requests. A way may be to track that on the connection (or another entity). You should remember to reset the state whence the ship is de-spawned of course.
    You may encounter some edge cases that need special handling, like for example the fact you requested a ship and never get a entity for it (ex: the ship spawned and right after killed, before the client received and ack the snapshot, very rare..).
    Limiting sending the spawn request with some client-side timer (ex: 1 request per second) is way more simple and robust (avoid many problems altogether)
     
    adammpolak likes this.
  8. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    This was great thank you