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

Resolved Client Server connection only works if I start the client first?

Discussion in 'NetCode for ECS' started by vectorized-runner, Jul 7, 2021.

  1. vectorized-runner

    vectorized-runner

    Joined:
    Jan 22, 2018
    Posts:
    396
    I'm using one Client only and one headless Server only build to make connection work, if I start the Client exe then the Server, the connection is fine and Player prefab is instantiated on Client. If I start the Server first then GoInGameServerSystem isn't updated and Player prefab isn't visible on Client. What am I doing wrong here?

    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.NetCode;
    3. using Unity.Networking.Transport;
    4. using UnityEngine;
    5.  
    6. namespace Game
    7. {
    8.     // Control system updating in the default world
    9.     [UpdateInWorld(UpdateInWorld.TargetWorld.Default)]
    10.     [AlwaysSynchronizeSystem]
    11.     public class InitGameSystem : SystemBase
    12.     {
    13.         protected override void OnCreate()
    14.         {
    15.             RequireSingletonForUpdate<InitGameComponent>();
    16.  
    17.             // Create singleton, require singleton for update so system runs once
    18.             EntityManager.CreateEntity(typeof(InitGameComponent));
    19.         }
    20.  
    21.         protected override void OnUpdate()
    22.         {
    23.             // Destroy singleton to prevent system from running again
    24.             EntityManager.DestroyEntity(GetSingletonEntity<InitGameComponent>());
    25.  
    26.             foreach(var world in World.All)
    27.             {
    28.                 var network = world.GetExistingSystem<NetworkStreamReceiveSystem>();
    29. #if UNITY_CLIENT || UNITY_EDITOR
    30.                 if(world.GetExistingSystem<ClientSimulationSystemGroup>() != null)
    31.                 {
    32.                     world.EntityManager.CreateEntity(typeof(EnableGame));
    33.  
    34.                     // Client worlds automatically connect to localhost
    35.                     NetworkEndPoint ep = NetworkEndPoint.LoopbackIpv4;
    36.                     ep.Port = 7979;
    37.                     network.Connect(ep);
    38.  
    39.                     Debug.Log("Client is connecting to Server.");
    40.                 }
    41. #endif
    42.  
    43. #if UNITY_SERVER || UNITY_EDITOR
    44.                 if(world.GetExistingSystem<ServerSimulationSystemGroup>() != null)
    45.                 {
    46.                     world.EntityManager.CreateEntity(typeof(EnableGame));
    47.  
    48.                     var tickRate = world.EntityManager.CreateEntity();
    49.  
    50.                     // I'm guessing this does 60hz sending on Client and 20hz on Server?
    51.                     world.EntityManager.AddComponentData(tickRate, new ClientServerTickRate
    52.                     {
    53.                         SimulationTickRate = 60,
    54.                         NetworkTickRate = 20,
    55.                     });
    56.  
    57.                     // Server world automatically listen for connections from any host
    58.                     NetworkEndPoint ep = NetworkEndPoint.AnyIpv4;
    59.                     ep.Port = 7979;
    60.                     network.Listen(ep);
    61.  
    62.                     Debug.Log("Server is listening to clients.");
    63.                 }
    64. #endif
    65.             }
    66.         }
    67.     }
    68. }
    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.NetCode;
    4. using UnityEngine;
    5.  
    6. namespace Game
    7. {
    8.     [UpdateInGroup(typeof(ClientSimulationSystemGroup))]
    9.     [AlwaysSynchronizeSystem]
    10.     public class GoInGameClientSystem : SystemBase
    11.     {
    12.         protected override void OnCreate()
    13.         {
    14.             RequireSingletonForUpdate<EnableGame>();
    15.             RequireForUpdate(GetEntityQuery(
    16.                 ComponentType.ReadOnly<NetworkIdComponent>(),
    17.                 ComponentType.Exclude<NetworkStreamInGame>()));
    18.         }
    19.  
    20.         protected override void OnUpdate()
    21.         {
    22.             var commandBuffer = new EntityCommandBuffer(Allocator.Temp);
    23.            
    24.             Entities.WithNone<NetworkStreamInGame>()
    25.                     .ForEach((Entity entity, in NetworkIdComponent id) =>
    26.                     {
    27.                         commandBuffer.AddComponent<NetworkStreamInGame>(entity);
    28.                     })
    29.                     .Run();
    30.            
    31.             commandBuffer.Playback(EntityManager);
    32.            
    33.             Debug.Log("Client is going into the game.");
    34.         }
    35.     }
    36. }
    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.NetCode;
    3. using Unity.Transforms;
    4. using UnityEngine;
    5.  
    6. namespace Game
    7. {
    8.     [UpdateInGroup(typeof(ServerSimulationSystemGroup))]
    9.     [AlwaysSynchronizeSystem]
    10.     public class GoInGameServerSystem : SystemBase
    11.     {
    12.         BeginSimulationEntityCommandBufferSystem BeginSimulationECBSystem;
    13.  
    14.         protected override void OnCreate()
    15.         {
    16.             RequireSingletonForUpdate<EnableGame>();
    17.             RequireSingletonForUpdate<PlayerSpawner>();
    18.             RequireForUpdate(GetEntityQuery(
    19.                 ComponentType.ReadOnly<NetworkIdComponent>(),
    20.                 ComponentType.Exclude<NetworkStreamInGame>()));
    21.  
    22.             BeginSimulationECBSystem = World.GetExistingSystem<BeginSimulationEntityCommandBufferSystem>();
    23.         }
    24.  
    25.         protected override void OnUpdate()
    26.         {
    27.             var commandBuffer = BeginSimulationECBSystem.CreateCommandBuffer();
    28.             var playerSpawner = GetSingleton<PlayerSpawner>();
    29.  
    30.             Entities.WithNone<NetworkStreamInGame>()
    31.                     .ForEach((Entity entity, in NetworkIdComponent id) =>
    32.                     {
    33.                         commandBuffer.AddComponent<NetworkStreamInGame>(entity);
    34.                         var player = commandBuffer.Instantiate(playerSpawner.Prefab);
    35.                         commandBuffer.SetComponent(player, new Translation { Value = playerSpawner.SpawnPosition });
    36.                         commandBuffer.SetComponent(player, new GhostOwnerComponent { NetworkId = id.Value });
    37.                         commandBuffer.SetComponent(entity, new CommandTargetComponent { targetEntity = player });
    38.                     })
    39.                     .Run();
    40.            
    41.             Debug.Log("Player is spawned. Server is going into the game.");
    42.         }
    43.     }
    44. }
    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.NetCode;
    3. using UnityEngine;
    4.  
    5. namespace Game
    6. {
    7.     [UpdateInGroup(typeof(ClientSimulationSystemGroup))]
    8.     public class InitClientCommandTargetSystem : SystemBase
    9.     {
    10.         struct InitCommandTarget : IComponentData
    11.         {
    12.         }
    13.  
    14.         EntityQuery LocalPlayerQuery;
    15.  
    16.         protected override void OnCreate()
    17.         {
    18.             LocalPlayerQuery = GetEntityQuery(typeof(Player), typeof(PredictedGhostComponent));
    19.             EntityManager.CreateEntity(typeof(InitCommandTarget));
    20.  
    21.             RequireForUpdate(LocalPlayerQuery);
    22.             RequireSingletonForUpdate<InitCommandTarget>();
    23.         }
    24.  
    25.         protected override void OnUpdate()
    26.         {
    27.             var commandTarget = GetSingleton<CommandTargetComponent>();
    28.             var localPlayerEntity = LocalPlayerQuery.GetSingletonEntity();
    29.             commandTarget.targetEntity = localPlayerEntity;
    30.             SetSingleton(commandTarget);
    31.            
    32.             Debug.Assert(GetSingleton<CommandTargetComponent>().targetEntity != Entity.Null);
    33.  
    34.             EntityManager.DestroyEntity(GetSingletonEntity<InitCommandTarget>());
    35.            
    36.             Debug.Log("CommandTarget is initialized on Client.");
    37.         }
    38.     }
    39. }
     
  2. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    882
    The code in itself looks correct. Nothing special about it.

    The first thing I would check is that the UNITY_SERVER and UNITY_CLIENT define are correctly set in the build, so you are now having a client-server build instead, please double check that.
    The fact you are starting the client first everything works suggest that the client is actually auto-connecting to itself.

    If that is not the case, we should probably add some log to test if the client for some reason get disconnected.
    Are you using IL2CPP or Mono ? Or some combination of the two ? There is a fix in regards an issue with connecting client and server due to a problem with hash calculation.
     
  3. vectorized-runner

    vectorized-runner

    Joined:
    Jan 22, 2018
    Posts:
    396
    I checked my defines and I'm pretty sure I'm having client only build, since on my headless server, client connection logs are printed. I also checked my player log on the client build with some debug systems and it looks like client connects to the server, and has singleton NetworkId component for 5 frames, and for 1 frame NetworkStreamDisconnected singleton exists, after that NetworkId doesn't exist anymore. I don't know why client just gets disconnected like that.

    I think I'm building mono since that's what shows on my Player settings, but I didn't add any settings for that on my BuildConfiguration scriptable object
     
  4. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    882
    I was mostly concerned with the presence of both UNITY_CLIENT and UNITY_SERVER at the same time, since in your code that would cause the client auto connect to himself.
    But if the defines are correct and since there is a disconnected components, looks like to me an hash calculation issue.
    In 0.6-preview.7 there was for sure a bug in that regards.
    If you look in the forum there is a recent post were we pass an "unofficial" fix for it that may help solve the problem.
    (IIRC it was a problem with android build )
     
  5. vectorized-runner

    vectorized-runner

    Joined:
    Jan 22, 2018
    Posts:
    396
    I've found the GhostAuthoringComponent replacement you mentioned and replaced it after embedding the NetCode package. I still can't get it to work though.

    Here's my build configurations

    ClientBuildConfig.png ServerBuildConfig.png

    Also here's my custom bootstraps:
    Code (CSharp):
    1. #if UNITY_SERVER
    2. using Unity.Entities;
    3. using Unity.NetCode;
    4.  
    5. namespace Game
    6. {
    7.     public class ServerBootstrap : ClientServerBootstrap
    8.     {
    9.         public override bool Initialize(string defaultWorldName)
    10.         {
    11.             TypeManager.Initialize();
    12.  
    13.             var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default);
    14.  
    15.             GenerateSystemLists(systems);
    16.  
    17.             var world = new World(defaultWorldName);
    18.             World.DefaultGameObjectInjectionWorld = world;
    19.  
    20.             DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, ExplicitDefaultWorldSystems);
    21.  
    22.             // TODO: Learn to use AddWorldToPlayerLoop here.
    23.             ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world);
    24.  
    25.             CreateServerWorld(world, "ServerWorld");
    26.  
    27.             return true;
    28.         }
    29.     }
    30. }
    31. #endif
    Code (CSharp):
    1. #if UNITY_CLIENT
    2. using Unity.Entities;
    3. using Unity.NetCode;
    4.  
    5. namespace Game
    6. {
    7.     public class ClientBootstrap : ClientServerBootstrap
    8.     {
    9.         public override bool Initialize(string defaultWorldName)
    10.         {
    11.             TypeManager.Initialize();
    12.  
    13.             var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default);
    14.  
    15.             GenerateSystemLists(systems);
    16.  
    17.             var world = new World(defaultWorldName);
    18.             World.DefaultGameObjectInjectionWorld = world;
    19.  
    20.             DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, ExplicitDefaultWorldSystems);
    21.  
    22.             // TODO: Learn to use AddWorldToPlayerLoop here.
    23.             ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world);
    24.  
    25.             CreateClientWorld(world, "ClientWorld");
    26.  
    27.             return true;
    28.         }
    29.     }
    30. }
    31. #endif
    Maybe I should wait until update to netcode package drops :/
     
  6. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    882
    The build configuration are ok, the bootstrap are ok as well.
    In 0.6-preview.7 many of the logs are disabled in the build unfortunately so is more difficult to see why the client disconnect.
    As a first step I would suggest enabling/adding some logs when the
    NetworkStreamRequestDisconnect is added to the connection and/or the Disconnect is called.
    I would focus on the RPC System at first, when we check for the version and the protocol hash.
    Another point that is critical can be the GhostCollectionSystem, when we check the ghost prefab hash.

    The fix I suggested you to include was to fix the prefab hash calculation, and since it still not working, looks like it may be the RPC.
    If rpc commands are not the same on both client and server build because for example the server and client use a different set of assemblies, it is possible that calculated hash not match.
    You may want to try to add the RpcSystem.DynamicAssemblyList to both client and server bootstrap like this:

    Code (csharp):
    1.  
    2. public override bool Initialize(string defaultWorldName)
    3. {
    4.      RpcSystem.DynamicAssemblyList = true;
    5.      TypeManager.Initialize();
    6.      ...
    7.      ...
    8.      RpcSystem.DynamicAssemblyList = false;
    9. }
    10.  
    RpcSystem.DynamicAssemblyList flag is telling the RPC system to encode the RPC using unique hash.