Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question DOTS Manually listening / connecting with ClientServerBootstrap

Discussion in 'NetCode for ECS' started by Ezro, Jun 1, 2023.

  1. Ezro

    Ezro

    Joined:
    Jun 7, 2013
    Posts:
    32
    Hi everyone,

    I'm using the ClientServerBootstrap and have UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP set but am running into odd behavior when listening / connecting manually.

    Currently I have UI buttons that are calling the following functions accordingly:
    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.NetCode;
    3. using Unity.Networking.Transport;
    4. using UnityEngine;
    5.  
    6.  
    7. public class ClientServerLauncher : MonoBehaviour
    8. {
    9.     const ushort PORT = 7979;
    10.  
    11.     public void StartHost()
    12.     {
    13.         StartServer();
    14.         StartClient();
    15.     }
    16.  
    17.     public void StartServer()
    18.     {
    19.         ClientServerBootstrap.DefaultListenAddress.Port = PORT;
    20.         Debug.Log($"Listening on port {PORT}");
    21.         World serverWorld = ClientServerBootstrap.CreateServerWorld("ServerWorld");
    22.         NetworkEndpoint ep = NetworkEndpoint.AnyIpv4.WithPort(PORT);
    23.         {
    24.             using var drvQuery = serverWorld.EntityManager.CreateEntityQuery(ComponentType.ReadWrite<NetworkStreamDriver>());
    25.             drvQuery.GetSingletonRW<NetworkStreamDriver>().ValueRW.Listen(ep);
    26.         }
    27.     }
    28.  
    29.     public void StartClient()
    30.     {
    31.         ClientServerBootstrap.AutoConnectPort = 0;
    32.         World clientWorld = ClientServerBootstrap.CreateClientWorld("ClientWorld");
    33.         NetworkEndpoint ep = NetworkEndpoint.LoopbackIpv4.WithPort(PORT);
    34.         var drvQuery = clientWorld.EntityManager.CreateEntityQuery(ComponentType.ReadWrite<NetworkStreamDriver>());
    35.         drvQuery.GetSingletonRW<NetworkStreamDriver>().ValueRW.Connect(clientWorld.EntityManager, ep);
    36.         Debug.Log("Connected.");
    37.     }
    38. }
    When I start host mode I can see the log messages for both "Listening on port" and "Connected" but then it seems like a thread (the main thread?) is being blocked. My GoInGameSystem is supposed to log:
    Code (csharp):
    1.  
    2.     public void OnUpdate(ref SystemState state)
    3.     {
    4.         Entity playerPrefab = SystemAPI.GetSingleton<PlayerSpawner>().Player;
    5.         FixedString128Bytes worldName = state.WorldUnmanaged.Name;
    6.         EntityCommandBuffer commandBuffer = new EntityCommandBuffer(Allocator.Temp);
    7.         networkIdFromEntity.Update(ref state);
    8.         foreach (var (reqSrc, reqEntity) in SystemAPI.Query<RefRO<ReceiveRpcCommandRequest>>().WithAll<GoInGameRequest>().WithEntityAccess())
    9.         {
    10.             commandBuffer.AddComponent<NetworkStreamInGame>(reqSrc.ValueRO.SourceConnection);
    11.             NetworkId networkId = networkIdFromEntity[reqSrc.ValueRO.SourceConnection];
    12.             Debug.Log($"'{worldName}' setting connection '{networkId.Value}' to in game");
    13.             Entity player = commandBuffer.Instantiate(playerPrefab);
    14.             commandBuffer.SetComponent(player, new GhostOwner { NetworkId = networkId.Value });
    15.             commandBuffer.AppendToBuffer(reqSrc.ValueRO.SourceConnection, new LinkedEntityGroup { Value = player });
    16.             commandBuffer.DestroyEntity(reqEntity);
    17.         }
    18.         commandBuffer.Playback(state.EntityManager);
    19.     }
    20.  
    This log message doesn't appear until after I stop play. Even weirder is that my presentation GameObject that's supposed to spawn when the player goes into the game isn't being spawned until after I hit the "Entities Hierarchy" tab _after_ the playmode has already been stopped.

    Has anyone experienced this before or have advice on how to properly manually listen / connect using the ClientServerBootstrap?
     
  2. Ezro

    Ezro

    Joined:
    Jun 7, 2013
    Posts:
    32
    I'm not sure if anyone was able to get this working but so far the only way I've been able to get the Console.Debug() logs and presentation GO to instantiate correctly is when I use the AutoConnectPort (and have it auto-connect).

    Does anyone have any ideas on what I can try?
     
  3. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    882
    Hey Ezro,

    First: which version of entities are you using ? I suppose 1.0.10 or something like that but just in case..

    Indeed UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP remove a lot of stuff in the editor that usually are there. Lot of logic for handling enter/exit playmode, entity journaling and profiler among other things.
    That should not cause a stall or having the the server world or client worlds not running.

    After you have inserted them in the player loop with the CreateServer and CreateClient calls they should run as expected.
    The ClientServerBootstrap.DefaultListenAddress is actually not necessary (it is inspected and used only when AutoConnectPort != 0) and you are manually calling Listen().

    ClientServerBootstrap.AutoConnectPort = 0 should be called before invoke the StartClient and StartServer. Because that ensure the ClientServerBootstrap.AutoConnectPort is not set for the server (or that will actually cause the server listen to another port).

    That being said, we need to investigate what happening. The fact setting AutoConnect work is by no mean suspicious.
    Can you actually add some extra logging or checking that the server is actually listen to the correct port and running all systems ? Same that client is actually running groups and systems and not waiting on some CompleteDepedency() method or similar?
     
  4. Ezro

    Ezro

    Joined:
    Jun 7, 2013
    Posts:
    32
    I was using 1.0.0-pre.65 but updated to 1.0.10 and am still facing difficulty when manually connecting. I've created a GH repo that should reproduce the issue: https://github.com/Ezro/ClientServerBootstrapManualConnect

    for a quick video showing the ~bug in action

    Steps to reproduce:
    1. Clone repo
    2. Play
    3. Click Host
    4. Check Console
    5. Stop Play
    6. Check Console again (sometimes I have to mouse over the Scene / Game window to have it print the log)
     
  5. Ezro

    Ezro

    Joined:
    Jun 7, 2013
    Posts:
    32
    Aha! I figured it out. My latest attempted to mirror the Frontend sample but still ran into the same issue. After adding some debug prints it turns out what's happening is that the Client / Server Worlds were being created _after_ the baking had ran -- or, in other words, the scene is being loaded (and subsequently baked) before the Worlds are being created.

    This was evidenced by the Live converted "Entity Baking Preview" not recognizing that my Spawner was baked in both the Client and Server Worlds.

    After adding a new NetCapsule scene, which contains the spawner in a SubScene, and manually loading the scene in my Frontend.cs.
     
  6. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    882
    If you create the worlds after the gameobject scene are loaded you need to do that manually as you did.
    To simplify, usually you create worlds before the loading the game scene (so everything is auto-injected). But loading sub-scene manually is also fine.

    Be aware that, if you have a default world created while you are in the frontend, that world can cause two issue if not destroyed:
    - Memory consumption (because entities data are loaded into it)
    - Rendering problems (because of that)

    Because a world is usually necessary, but you don't need to have any logic into it, just create an "empty" one, without presentation or scene loading systems. That would not cause all the aforementioned and can be kept alive (and used for other purpose),
     
  7. ScallyGames

    ScallyGames

    Joined:
    Sep 10, 2012
    Posts:
    44
    @CMarastoni I'm currently running into a similar issue with some of my systems querying game objects from the scene in the OnStartRunning() function of the systems (i.e. to get a reference to UI, Audio, ..).

    If I create the world first, the game objects are not found since the scene hasn't loaded yet, if I load the scene first, the connection doesn't work properly.

    If I only have the one scene everything works fine but now after I've tried adding a main menu and doing manual connection I'm running into these issues.
    Is accessing game objects in OnStartRunning() just an anti-pattern or is there any good solution to this?


    Could you elaborate a bit more on that solution? What is the empty scene needed for?