Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Join us on Dec 8, 2022, between 7 am & 7 pm EST, in the DOTS Dev Blitz Day 2022 - Q&A forum, Discord, and Unity3D Subreddit to learn more about DOTS directly from the Unity Developers.
    Dismiss Notice
  3. Have a look at our Games Focus blog post series which will show what Unity is doing for all game developers – now, next year, and in the future.
    Dismiss Notice

DOTS Multiplayer discussion

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

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

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    447
    Not a full answer but check out this tip for speeding up hitting play: https://forum.unity.com/threads/protip-super-fast-enter-playmode-times-when-working-in-dots.984543/
     
  2. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    447
    @timjohansson this is breaking my brain I was hoping you could explain what is happening with GhostRelevancySphere in the PlayerRelevancySphereSystem in Asteroids sample

    The key point I am confused about is at the end:
    Code (CSharp):
    1.         //Here we check all ghosted entities and see which ones are relevant to the NCEs based on distance and the relevancy radius
    2.         Dependency = Entities
    3.             .WithReadOnly(connections)
    4.             .ForEach((in GhostComponent ghost, in Translation pos) => {
    5.             for (int i = 0; i < connections.Length; ++i)
    6.             {
    7.                 if (math.distance(pos.Value, connections[i].Position) > settings.relevancyRadius)
    8.                     parallelRelevantSet.TryAdd(new RelevantGhostForConnection(connections[i].ConnectionId, ghost.ghostId), 1);
    9.             }
    10.         }).ScheduleParallel(JobHandle.CombineDependencies(connectionHandle, clearHandle));
    This works but I can't figure out how
    math.distance(pos.Value, connections[i].Position) > settings.relevancyRadius
    gets us what we want.

    If it is OUTSIDE the radius, should it NOT be added to the RelevantGhostForConnection?




    Full code here:
    Code (CSharp):
    1. using Unity.NetCode;
    2. using Unity.Entities;
    3. using Unity.Mathematics;
    4. using Unity.Collections;
    5. using Unity.Transforms;
    6. using Unity.Jobs;
    7.  
    8. [UpdateInGroup(typeof(ServerSimulationSystemGroup))]
    9. [UpdateBefore(typeof(GhostSendSystem))]
    10. public class PlayerRelevancySphereSystem : SystemBase
    11. {
    12.     //This will be a struct we use only in this system
    13.     //The ConnectionId is the entities NetworkId
    14.     struct ConnectionRelevancy
    15.     {
    16.         public int ConnectionId;
    17.         public float3 Position;
    18.     }
    19.     //We grab the ghost send system to use its GhostRelevancyMode
    20.     GhostSendSystem m_GhostSendSystem;
    21.     //Here we keep a list of our NCEs with NetworkId and position of player
    22.     NativeList<ConnectionRelevancy> m_Connections;
    23.     EntityQuery m_GhostQuery;
    24.     EntityQuery m_ConnectionQuery;
    25.     protected override void OnCreate()
    26.     {
    27.         m_GhostQuery = GetEntityQuery(ComponentType.ReadOnly<GhostComponent>());
    28.         m_ConnectionQuery = GetEntityQuery(ComponentType.ReadOnly<NetworkIdComponent>());
    29.         RequireForUpdate(m_ConnectionQuery);
    30.         m_Connections = new NativeList<ConnectionRelevancy>(16, Allocator.Persistent);
    31.         m_GhostSendSystem = World.GetExistingSystem<GhostSendSystem>();
    32.         //We need the GameSettingsComponent so we need to make sure it streamed in from the SubScene
    33.         RequireSingletonForUpdate<GameSettingsComponent>();
    34.     }
    35.     protected override void OnDestroy()
    36.     {
    37.         m_Connections.Dispose();
    38.     }
    39.     protected override void OnUpdate()
    40.     {
    41.         //We only run this if the relevancyRadius is not 0
    42.         var settings = GetSingleton<GameSettingsComponent>();
    43.         if ((int) settings.relevancyRadius == 0)
    44.         {
    45.             m_GhostSendSystem.GhostRelevancyMode = GhostRelevancyMode.Disabled;
    46.             return;
    47.         }
    48.         //This is a special NetCode system configuration
    49.         m_GhostSendSystem.GhostRelevancyMode = GhostRelevancyMode.SetIsIrrelevant;
    50.  
    51.         //We create a new list of connections ever OnUpdate
    52.         m_Connections.Clear();
    53.         var relevantSet = m_GhostSendSystem.GhostRelevancySet;
    54.         var parallelRelevantSet = relevantSet.AsParallelWriter();
    55.  
    56.         var maxRelevantSize = m_GhostQuery.CalculateEntityCount() * m_ConnectionQuery.CalculateEntityCount();
    57.  
    58.         var clearHandle = Job.WithCode(() => {
    59.             relevantSet.Clear();
    60.             if (relevantSet.Capacity < maxRelevantSize)
    61.                 relevantSet.Capacity = maxRelevantSize;
    62.         }).Schedule(m_GhostSendSystem.GhostRelevancySetWriteHandle);
    63.  
    64.         //Here we grab the positions and networkids of the NCEs ComandTargetCommponent's targetEntity
    65.         var connections = m_Connections;
    66.         var transFromEntity = GetComponentDataFromEntity<Translation>(true);
    67.         var connectionHandle = Entities
    68.             .WithReadOnly(transFromEntity)
    69.             .WithNone<NetworkStreamDisconnected>()
    70.             .ForEach((in NetworkIdComponent netId, in CommandTargetComponent target) => {
    71.             var pos = new float3();
    72.             //If we havent spawned a player yet we will set the position to the location of the main camera
    73.             if (target.targetEntity == Entity.Null)
    74.                 pos = new float3(0,1,-10);
    75.             else
    76.                 pos = transFromEntity[target.targetEntity].Value;
    77.             connections.Add(new ConnectionRelevancy{ConnectionId = netId.Value, Position = pos});
    78.         }).Schedule(Dependency);
    79.  
    80.         //Here we check all ghosted entities and see which ones are relevant to the NCEs based on distance and the relevancy radius
    81.         Dependency = Entities
    82.             .WithReadOnly(connections)
    83.             .ForEach((in GhostComponent ghost, in Translation pos) => {
    84.             for (int i = 0; i < connections.Length; ++i)
    85.             {
    86.                 if (math.distance(pos.Value, connections[i].Position) > settings.relevancyRadius)
    87.                     parallelRelevantSet.TryAdd(new RelevantGhostForConnection(connections[i].ConnectionId, ghost.ghostId), 1);
    88.             }
    89.         }).ScheduleParallel(JobHandle.CombineDependencies(connectionHandle, clearHandle));
    90.  
    91.         m_GhostSendSystem.GhostRelevancySetWriteHandle = Dependency;
    92.     }
    93. }
     
    Last edited: Jan 28, 2021
  3. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    464
    Not sure, I have not had time to dig into the integration with the build pipelines. It might require some extra work there to remove the assets.

    Can you check the profiler to see what is slow? I know it's really slow if you disable burst or enable full stack trace leak detection.
    We do run the client and server worlds sequentially on the same main thread, so if you are using a lot of non-jobified or otherwise main-thread heavy systems you can get perf problems from that too.
     
    optimise likes this.
  4. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    464
    The naming of that local variable is a bit confusing, it's the relvancy set, not the relevant set.
    If you look at line 49 you have
    Code (CSharp):
    1. m_GhostSendSystem.GhostRelevancyMode = GhostRelevancyMode.SetIsIrrelevant;
    - which means the relevancy set contains the set of all non relevant ghosts.
    You should be able to set it to SetIsRelevant and invert the distance compare if you prefer.
     
  5. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    447
    Thank you @timjohansson !!! I was doing mental gymnastics trying to explain to myself how greater actually means less than.
     
  6. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    447
    @timjohansson I have the strangest bug and I was wondering if it was a "known" NetCode issue.

    • I follow the same flow as the Asteroids sample
      • Socket connection is made between server/client (while simultaneously spawning asteroids)
      • Socket connection creates an NCE which triggers the server to send an rpc with level data to the client
      • Client sends back an rpc that updates the server's NCE (network connection entity)
    • I am using a GhostRelevancyMode and the server is spawning 2000 ghosted asteroids.

    For some inexplicable reason, the flow of the server sending an RPC and the client responding with an RPC does not work. As if the systems that send these RPCs aren't firing.

    But if I add a Debug.Log() to the RPC sent from the client to the server (SendServerGameLoadedRpc)... the entire flow works again (?!).

    I am wondering if there is some hidden NetCode nuance that I am missing that is necessary to get systems firing? Like if there are too many ghosted entities that NetCode is not able to send RPC's or something? Is there any reason why RPCs would stop firing, unless there is a Debug.Log() within the system?

    System where Server sends the RPC to client:
    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.Jobs;
    3. using Unity.Collections;
    4. using Unity.NetCode;
    5. using UnityEngine;
    6.  
    7. //This component is only used by this system so we define it in this file
    8. public struct SentClientGameRpcTag : IComponentData
    9. {
    10. }
    11.  
    12. //This system should only be run by the server (because the server sends the game settings)
    13. //By sepcifying to update in group ServerSimulationSystemGroup it also specifies that it must
    14. //be run by the server
    15. [UpdateInGroup(typeof(ServerSimulationSystemGroup))]
    16. [UpdateBefore(typeof(RpcSystem))]
    17. public class ServerSendGameSystem : SystemBase
    18. {
    19.     private BeginSimulationEntityCommandBufferSystem m_Barrier;
    20.  
    21.     protected override void OnCreate()
    22.     {
    23.         m_Barrier = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
    24.         RequireSingletonForUpdate<GameSettingsComponent>();
    25.         RequireSingletonForUpdate<ServerDataComponent>();
    26.     }
    27.  
    28.     protected override void OnUpdate()
    29.     {
    30.         var commandBuffer = m_Barrier.CreateCommandBuffer();
    31.  
    32.         var serverData = GetSingleton<GameSettingsComponent>();
    33.         var gameNameData = GetSingleton<ServerDataComponent>();
    34.  
    35.         Entities
    36.         .WithNone<SentClientGameRpcTag>()
    37.         .ForEach((Entity entity, in NetworkIdComponent netId) =>
    38.         {
    39.             commandBuffer.AddComponent(entity, new SentClientGameRpcTag());
    40.             var req = commandBuffer.CreateEntity();
    41.             commandBuffer.AddComponent(req, new SendClientGameRpc
    42.             {
    43.                 levelWidth = serverData.levelWidth,
    44.                 levelHeight = serverData.levelHeight,
    45.                 levelDepth = serverData.levelDepth,
    46.                 playerForce = serverData.playerForce,
    47.                 bulletVelocity = serverData.bulletVelocity,
    48.                 gameName = gameNameData.GameName
    49.             });
    50.  
    51.             commandBuffer.AddComponent(req, new SendRpcCommandRequestComponent {TargetConnection = entity});
    52.         }).Schedule();
    53.  
    54.         m_Barrier.AddJobHandleForProducer(Dependency);
    55.     }
    56. }
    System where client responds back by sending RPC to server
    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.NetCode;
    3. using UnityEngine;
    4.  
    5. //This will only run on the client because it updates in ClientSimulationSystemGroup (which the server does not have)
    6. [UpdateInGroup(typeof(ClientSimulationSystemGroup))]
    7. [UpdateBefore(typeof(RpcSystem))]
    8. public class ClientLoadGameSystem : SystemBase
    9. {
    10.     private BeginSimulationEntityCommandBufferSystem m_BeginSimEcb;
    11.  
    12.     protected override void OnCreate()
    13.     {
    14.         //We will be using the BeginSimECB
    15.         m_BeginSimEcb = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
    16.  
    17.         //Requiring the ReceiveRpcCommandRequestComponent ensures that update is only run when an NCE exists and a SendClientGameRpc has come in
    18.         RequireForUpdate(GetEntityQuery(ComponentType.ReadOnly<SendClientGameRpc>(), ComponentType.ReadOnly<ReceiveRpcCommandRequestComponent>()));
    19.         //This is just here to make sure the Sub Scene is streamed in before the client sets up the level data
    20.         RequireSingletonForUpdate<GameSettingsComponent>();
    21.         //We will make sure we have our ClientDataComponent so we can send the server our player name
    22.         RequireSingletonForUpdate<ClientDataComponent>();
    23.     }
    24.  
    25.     protected override void OnUpdate()
    26.     {
    27.  
    28.         //We must declare our local variables before using them within a job (.ForEach)
    29.         var commandBuffer = m_BeginSimEcb.CreateCommandBuffer();
    30.         var rpcFromEntity = GetBufferFromEntity<OutgoingRpcDataStreamBufferComponent>();
    31.         var gameSettingsEntity = GetSingletonEntity<GameSettingsComponent>();
    32.         var getGameSettingsComponentData = GetComponentDataFromEntity<GameSettingsComponent>();
    33.         var clientData = GetSingleton<ClientDataComponent>(); //We will use this to send the player name to server
    34.  
    35.         Entities
    36.         .ForEach((Entity entity, in SendClientGameRpc request, in ReceiveRpcCommandRequestComponent requestSource) =>
    37.         {
    38.             //This destroys the incoming RPC so the code is only run once
    39.             commandBuffer.DestroyEntity(entity);
    40.  
    41.             //Check for disconnects before moving forward
    42.             if (!rpcFromEntity.HasComponent(requestSource.SourceConnection))
    43.                 return;
    44.  
    45.             //Set the game size (unnecessary right now but we are including it to show how it is done)
    46.             getGameSettingsComponentData[gameSettingsEntity] = new GameSettingsComponent
    47.             {
    48.                 levelWidth = request.levelWidth,
    49.                 levelHeight = request.levelHeight,
    50.                 levelDepth = request.levelDepth,
    51.                 playerForce = request.playerForce,
    52.                 bulletVelocity = request.bulletVelocity
    53.             };
    54.  
    55.  
    56.             //Here we create a new singleton entity for GameNameComponent
    57.             //We could add this component to the singleton entity that has the GameSettingsComponent
    58.             //but we will keep them separate in case we want to change workflows in the future and don't
    59.             //want these components to be dependent on the same entity
    60.             var gameNameEntity= commandBuffer.CreateEntity();
    61.             commandBuffer.AddComponent(gameNameEntity, new GameNameComponent {
    62.                 GameName = request.gameName
    63.             });
    64.  
    65.             //These update the NCE with NetworkStreamInGame (required to start receiving snapshots) and
    66.             //PlayerSpawningStateComponent, which we will use when we spawn players
    67.             commandBuffer.AddComponent(requestSource.SourceConnection, new PlayerSpawningStateComponent());
    68.             commandBuffer.AddComponent(requestSource.SourceConnection, default(NetworkStreamInGame));
    69.      
    70.             //This tells the server "I loaded the level"
    71.             //First we create an entity called levelReq that will have 2 necessary components
    72.             //Next we add the RPC we want to send (SendServerGameLoadedRpc) and then we add
    73.             //SendRpcCommandRequestComponent with our TargetConnection being the NCE with the server (which will send it to the server)
    74.             var levelReq = commandBuffer.CreateEntity();
    75.             commandBuffer.AddComponent(levelReq, new SendServerGameLoadedRpc());
    76.             commandBuffer.AddComponent(levelReq, new SendRpcCommandRequestComponent {TargetConnection = requestSource.SourceConnection});
    77.  
    78.             // this tells the server "This is my name and Id" which will be used for player score tracking
    79.             var playerReq = commandBuffer.CreateEntity();
    80.             commandBuffer.AddComponent(playerReq, new SendServerPlayerNameRpc {playerName = clientData.PlayerName});
    81.             commandBuffer.AddComponent(playerReq, new SendRpcCommandRequestComponent {TargetConnection = requestSource.SourceConnection});
    82.  
    83.         }).Schedule();
    84.  
    85.         m_BeginSimEcb.AddJobHandleForProducer(Dependency);
    86.     }
    87. }
    This is the Rpc where if I add a Debug.Log() the system starts working again:
    Code (CSharp):
    1. using AOT;
    2. using Unity.Burst;
    3. using Unity.Networking.Transport;
    4. using Unity.NetCode;
    5. using Unity.Entities;
    6. using UnityEngine;
    7. using Unity.Collections;
    8.  
    9. [BurstCompile]
    10. public struct SendServerGameLoadedRpc : IComponentData, IRpcCommandSerializer<SendServerGameLoadedRpc>
    11. {
    12.     //Necessary boilerplate
    13.     public void Serialize(ref DataStreamWriter writer, in RpcSerializerState state, in SendServerGameLoadedRpc data)
    14.     {
    15.     }
    16.     //Necessary boilerplate
    17.     public void Deserialize(ref DataStreamReader reader, in RpcDeserializerState state, ref SendServerGameLoadedRpc data)
    18.     {
    19.     }
    20.  
    21.     [BurstCompile]
    22.     [MonoPInvokeCallback(typeof(RpcExecutor.ExecuteDelegate))]
    23.     private static void InvokeExecute(ref RpcExecutor.Parameters parameters)
    24.     {
    25.         //Within here is where
    26.         var rpcData = default(SendServerGameLoadedRpc);
    27.  
    28.         //Here we deserialize the received data
    29.         rpcData.Deserialize(ref parameters.Reader, parameters.DeserializerState, ref rpcData);
    30.  
    31.         //Here we add 3 components to the NCE
    32.         //The first, PlayerSpawningStateComonent will be used during our player spawn flow
    33.         parameters.CommandBuffer.AddComponent(parameters.JobIndex, parameters.Connection, new PlayerSpawningStateComponent());
    34.         //NetworkStreamInGame must be added to an NCE to start receiving Snapshots
    35.         parameters.CommandBuffer.AddComponent(parameters.JobIndex, parameters.Connection, default(NetworkStreamInGame));
    36.         //GhostConnectionPosition is added to be used in conjunction with GhostDistanceImportance (from the socket section)
    37.         parameters.CommandBuffer.AddComponent(parameters.JobIndex, parameters.Connection, default(GhostConnectionPosition));
    38.     }
    39.  
    40.     //Necessary boilerplate
    41.     static PortableFunctionPointer<RpcExecutor.ExecuteDelegate> InvokeExecuteFunctionPointer =
    42.         new PortableFunctionPointer<RpcExecutor.ExecuteDelegate>(InvokeExecute);
    43.     public PortableFunctionPointer<RpcExecutor.ExecuteDelegate> CompileExecute()
    44.     {
    45.         return InvokeExecuteFunctionPointer;
    46.     }
    47. }
    48.  
    49. //Necessary boilerplate
    50. class SendServerGameLoadedRpcCommandRequestSystem : RpcCommandRequestSystem<SendServerGameLoadedRpc, SendServerGameLoadedRpc>
    51. {
    52.     [BurstCompile]
    53.     protected struct SendRpc : IJobEntityBatch
    54.     {
    55.         public SendRpcData data;
    56.         public void Execute(ArchetypeChunk chunk, int orderIndex)
    57.         {
    58.             data.Execute(chunk, orderIndex);
    59.         }
    60.     }
    61.     protected override void OnUpdate()
    62.     {
    63.         var sendJob = new SendRpc{data = InitJobData()};
    64.         ScheduleJobData(sendJob);
    65.     }
    66. }
    Even weirder, if I then REMOVE the Debug.Log() after, the flow continues to work...

    If you want to see it for yourself attached is the project. Open up NavigationScene, hit play, hit "Host" and "Host" and notice no asteroids stream to ClientWorld. (I am running in 2020.1.17f)

    Then add a Debug.Log() to the RPC, and presto, the flow is working.

    Remove it again, and the flow still works.
     

    Attached Files:

    Last edited: Jan 28, 2021
  7. SebLazyWizard

    SebLazyWizard

    Joined:
    Jun 15, 2018
    Posts:
    208
    I used to exclude translation and rotation components from syncing via "GhostAuthoringComponentEditor.GhostDefaultOverrides.Remove()", but since NetCode 0.6 came out this is no longer possible as this method got removed for some reason.
    What is the current way to do so?
     
  8. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    447
    I think if you choose a "static" optimization the Translation and Rotation are not updated.
    upload_2021-1-31_12-0-31.png
     
  9. SebLazyWizard

    SebLazyWizard

    Joined:
    Jun 15, 2018
    Posts:
    208
    No, that will still sync it, but only when it's changed. I need a way to not send it at all.
     
  10. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    447
    Ah gotcha, sorry for the noise!
     
  11. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    244
    SebLazyWizard likes this.
  12. SebLazyWizard

    SebLazyWizard

    Joined:
    Jun 15, 2018
    Posts:
    208
    Yea that worked, thanks for the info.
    This is the code if anyone is interested;
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.NetCode.Editor;
    3.  
    4. public class GhostOverrides : IGhostDefaultOverridesModifier
    5. {
    6.     public void Modify(Dictionary<string, Unity.NetCode.GhostComponentModifier> overrides)
    7.     {
    8.         overrides.Remove("Unity.Transforms.Rotation");
    9.         overrides.Remove("Unity.Transforms.Translation");
    10.     }
    11.  
    12.     public void ModifyAlwaysIncludedAssembly(HashSet<string> alwaysIncludedAssemblies)
    13.     {
    14.     }
    15.  
    16.     public void ModifyTypeRegistry(TypeRegistry typeRegistry, string netCodeGenAssemblyPath)
    17.     {
    18.     }
    19. }
    20.  
     
  13. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    447
    Does this apply to all ghosts or do you have a [GenerateAuthoringComponent] on this that we can't see? (sorry confused how this is implemented and what it affects)
     
  14. SebLazyWizard

    SebLazyWizard

    Joined:
    Jun 15, 2018
    Posts:
    208
    It applies to all ghosts.
    I have only a few ghost's that actually need translation and rotation to be synchronized and for that I use separate components.
     
  15. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    447
    Ah gotcha, so you just re-add to ghosts where you need them. Very cool appreciate the clarification!
     
  16. optimise

    optimise

    Joined:
    Jan 22, 2014
    Posts:
    1,532
    1) @timjohansson. I think you already done the groundwork. I believe the rest of the work should continue by build pipeline team that also working on DOTS Addressable.

    2) Even I know where is slow I can't do anything. What I want to say is there is heavy overhead at Editor even I enable burst, off burst safety check, disable job debugger, off leak detection with Editor Release Mode. I only get super low FPS at Editor meanwhile I get solid 60 fps after I roll out the real build. I think Editor Release Mode need to strip out more debug thingy to make it faster.
     
    Last edited: Feb 2, 2021
  17. optimise

    optimise

    Joined:
    Jan 22, 2014
    Posts:
    1,532
    Hi @timjohansson. Can u go through case 1311658? Currently I found that IL2CPP build does not work properly with Netcode. I get this log when running IL2CPP server build. Reproducible at both Unity 2020.2.x version and 2021.1 beta.
     

    Attached Files:

    Last edited: Feb 3, 2021
  18. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    464
    Try setting stripping to low and it should work. It might also be possible to work around it by adding an empty class extending ClientServerBootstrap and adding a [Preserve] attribute to the class if you need stripping - but I did not check if it is enough in 0.6. If you already have a class extending ClientServerBootstrap you should add preserve to that instead.
     
  19. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    447
  20. twaananen

    twaananen

    Joined:
    Jul 24, 2017
    Posts:
    23
    I have an issue with spawning bullets that I've been messing around with for some time now.
    The problem is that the spawnscript running in GhostPredictionSystemGroup spawns a bunch of extra bullets after some time int the clientworld (server world seems ok). The extras dissapear after a while(I assume when the server spawned snapshot arrives). The problem gets worse/more noticable depending on the delay, the picture below is with 200ms set. In the picture, only the first bullet should be spawned; by the time the other bullets spawn, the button is no longer pressed.
    upload_2021-2-4_19-49-22.png

    I assume it could have something to do with the countdown system, but I have tried to build it both ways
    - as counting down toward 0 each frame
    - setting a future value more like the multiplayer sampleproject steeringsystem.

    The countdown itself works perfectly on the server. I've also tried setting the countdown and all related components as
    [GhostComponent] without a noticable difference.

    Code (CSharp):
    1. public class SpawnPredictedOnInputSystem : SystemBase
    2. {
    3.     EndSimulationEntityCommandBufferSystem m_Barrier;
    4.  
    5.     private GhostPredictionSystemGroup m_PredictionGroup;
    6.  
    7.     protected override void OnCreate()
    8.     {
    9.         base.OnCreate();
    10.  
    11.         m_Barrier = World
    12.             .GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    13.         m_PredictionGroup = World.GetOrCreateSystem<GhostPredictionSystemGroup>();
    14.     }
    15.  
    16.     protected override void OnUpdate()
    17.     {
    18.         var currentTime = Time.ElapsedTime;
    19.         var commandBuffer = m_Barrier.CreateCommandBuffer().AsParallelWriter();
    20.         var currentTick = m_PredictionGroup.PredictingTick;
    21.         var getOwnerData = GetComponentDataFromEntity<OwnerData>(true);
    22.         var getParentPrediction = GetComponentDataFromEntity<PredictedGhostComponent>(true);
    23.         var getParentCountDown = GetComponentDataFromEntity<CountDownData>(true);
    24.         var getInput = GetBufferFromEntity<NetworkedPlayerInputData>(true);
    25.         var getActivation = GetComponentDataFromEntity<ActivateOnWeaponInputData>(true);
    26.         var getVelocity = GetComponentDataFromEntity<CompoundMovementData>(true);
    27.         var getInitialSpeed = GetComponentDataFromEntity<InitialSpeedData>(true);
    28.         var getSpreadAngle = GetComponentDataFromEntity<SpreadAngleData>(true);
    29.         var randomArray = World.GetExistingSystem<RandomHelper>().RandomArray;
    30.  
    31.         Entities
    32.         .WithReadOnly(getOwnerData)
    33.         .WithReadOnly(getParentPrediction)
    34.         .WithReadOnly(getInput)
    35.         .WithReadOnly(getParentCountDown)
    36.         .WithReadOnly(getActivation)
    37.         .WithReadOnly(getVelocity)
    38.         .WithReadOnly(getInitialSpeed)
    39.         .WithReadOnly(getSpreadAngle)
    40.         .WithNativeDisableParallelForRestriction(randomArray)
    41.         .WithAll<IsConvertedToPredictedTag,ActivateOnWeaponInputData>()
    42.         .ForEach(( Entity itemSlot, int entityInQueryIndex, int nativeThreadIndex, in SpawnPrefabData prefabs,
    43.         in LocalToWorld translation, in Parent parentData
    44.         ) =>
    45.         {
    46.             // This runs on slots that are childentities of predicted ghost entities.
    47.             // Which is why I'm making checks against the parent.
    48.  
    49.             // Check if all the necessary components are in place on the parent
    50.             var parent = parentData.Value;
    51.             // Cooldowns are handled in other systems
    52.             if (HasComponent<CountDownActiveTag>(parent))
    53.                 return;
    54.             if (HasComponent<StartCountDownData>(parent))
    55.             return;
    56.             if (!getParentPrediction.HasComponent(parent))
    57.                 return;
    58.             var prediction = getParentPrediction[parent];
    59.             if (!GhostPredictionSystemGroup.ShouldPredict(currentTick, prediction))
    60.                 return;
    61.             if (prefabs.clientPredictedPrefab == Entity.Null)
    62.                 return;
    63.  
    64.             // What input this should activate on
    65.             var activateOn = getActivation[parent];
    66.  
    67.             // Get countdown from parent. Overflow for fast weapons + slow framerate
    68.             var countDown = getParentCountDown[parent];
    69.             float overflowSeconds = HasComponent<CountDownCompletedTag>(parent)
    70.                 ? (float)(currentTime - countDown.completesAtTime)
    71.                 : 0f;
    72.  
    73.             // Get input
    74.             var owner = getOwnerData[parent];
    75.             var inputBuffer = getInput[owner.character];
    76.             NetworkedPlayerInputData input;
    77.             if (inputBuffer.GetDataAtTick(currentTick, out input)){
    78.                 while (overflowSeconds >= 0f && (input.fireWeaponOne > 0f && activateOn.weaponGroupOne
    79.                 || input.fireWeaponTwo > 0f && activateOn.weaponGroupTwo
    80.                 || input.fireWeaponThree > 0f && activateOn.weaponGroupThree)) {
    81.  
    82.  
    83.                     var ghostOwner = GetComponent<GhostOwnerComponent>(parent);
    84.                     Entity itemOwner = getOwnerData.HasComponent(parent) ? getOwnerData[parent].character : parent;
    85.                     Entity parentItemRoot = getOwnerData[parent].controlledRootItem;
    86.                     quaternion newItemRotation = translation.Rotation;
    87.                     float3 localForward;
    88.                     // Calculate a random spread
    89.                     if (getSpreadAngle.HasComponent(parent)){
    90.                         var spreadAngle = math.radians(getSpreadAngle[parent].value);
    91.                      
    92.                         var random = randomArray[nativeThreadIndex];
    93.                         float3 randomSpread = new float3(
    94.                             random.NextFloat(-spreadAngle.y,spreadAngle.y)
    95.                         ,random.NextFloat(-spreadAngle.x,spreadAngle.x),
    96.                         random.NextFloat(-spreadAngle.z,spreadAngle.z)
    97.                         );
    98.                         randomArray[nativeThreadIndex]= random;
    99.  
    100.                         quaternion randomRotation = quaternion.EulerXYZ(randomSpread);
    101.                         localForward = math.forward(math.mul(newItemRotation,randomRotation));
    102.                     } else {
    103.                         localForward = math.forward(newItemRotation);
    104.                     }
    105.                     float3 parentVelocity = getVelocity[parentItemRoot].moveSpeed;
    106.                     float initialSpeed = HasComponent<InitialSpeedData>(parent) ? getInitialSpeed[parent].speed : 0f;
    107.                     float3 newItemVelocity = parentVelocity + localForward*initialSpeed;
    108.                     float3 newItemPosition = translation.Position + overflowSeconds*newItemVelocity;
    109.                  
    110.                     // The prefabs.clientPredictedPrefab spawned here has been through the
    111.                     // GhostCollectionSystem.CreatePredictedSpawnPrefab() in another system
    112.                     var item = commandBuffer.Instantiate(entityInQueryIndex, prefabs.clientPredictedPrefab);
    113.                  
    114.                     commandBuffer.SetComponent(entityInQueryIndex, item, new Translation{Value = newItemPosition});
    115.                     commandBuffer.SetComponent(entityInQueryIndex, item, new Rotation{Value = newItemRotation});
    116.                     commandBuffer.SetComponent(entityInQueryIndex, item, new CompoundMovementData{moveSpeed = newItemVelocity});
    117.                     commandBuffer.SetComponent(entityInQueryIndex, item, new OwnerData{character = itemOwner, controlledRootItem = item});
    118.                     commandBuffer.SetComponent(entityInQueryIndex, item, new GhostOwnerComponent {NetworkId = ghostOwner.NetworkId});
    119.  
    120.                     if(overflowSeconds <= countDown.countDownSeconds) {
    121.                         var newStartTime = currentTime - overflowSeconds;
    122.                         // This starts the cooldown on the weapon
    123.                         commandBuffer.AddComponent(entityInQueryIndex, parent, new StartCountDownData{startTime = newStartTime});
    124.                     }
    125.                     overflowSeconds -= countDown.countDownSeconds;
    126.                 }
    127.             }
    128.         })
    129.         .ScheduleParallel();
    130.  
    131.         m_Barrier.AddJobHandleForProducer(Dependency);
    132.     }
    133. }
    Any help would be much appreciated!
     
  21. twaananen

    twaananen

    Joined:
    Jul 24, 2017
    Posts:
    23
    I actually found a dirty solution by moving the spawning action out of prediction and only have a flag set from a prediction system. It kind of makes sense if the spawning action doesn't depend on any other values eg. position other than input. Further I could use GhostPredictionSystemGroup.IsFinalPredictionTick to skip the system on most prediction loops, but going this route will I run into issues when the packets start being full?
     
  22. twaananen

    twaananen

    Joined:
    Jul 24, 2017
    Posts:
    23
    Another issue I'm facing and can't find any reference to is getting these two errors to the console after spawning bullets for some time. This seems to only happen when I have two or more weapons firing (using the same entity prefab). The total amount of bullets or a high spawnrate doesn't affect this when using just one weapon.

    Code (CSharp):
    1. InvalidOperationException: A ghost changed type, ghost must keep the same serializer type throughout their lifetime
    2. Unity.NetCode.GhostSendSystem+SerializeJob.SerializeChunk (Unity.Networking.Transport.DataStreamWriter& dataStream, Unity.NetCode.SerializeData& data, Unity.Entities.DynamicComponentTypeHandle* ghostChunkComponentTypesPtr, System.Int32 ghostChunkComponentTypesLength, System.Int32& skippedEntityCount, System.UInt32& anyChangeMask) (at Library/PackageCache/com.unity.netcode@0.6.0-preview.7/Runtime/Snapshot/GhostSendSystem.cs:2179)
    3. Unity.NetCode.GhostSendSystem+SerializeJob.sendEntities (Unity.Networking.Transport.DataStreamWriter& dataStream, Unity.Entities.DynamicComponentTypeHandle* ghostChunkComponentTypesPtr, System.Int32 ghostChunkComponentTypesLength) (at Library/PackageCache/com.unity.netcode@0.6.0-preview.7/Runtime/Snapshot/GhostSendSystem.cs:1211)
    4. Unity.NetCode.GhostSendSystem+SerializeJob.Execute (System.Int32 idx, Unity.Entities.DynamicComponentTypeHandle* ghostChunkComponentTypesPtr, System.Int32 ghostChunkComponentTypesLength) (at Library/PackageCache/com.unity.netcode@0.6.0-preview.7/Runtime/Snapshot/GhostSendSystem.cs:802)
    5. Unity.NetCode.GhostSendSystem+SerializeJob32.Execute (System.Int32 idx) (at Library/PackageCache/com.unity.netcode@0.6.0-preview.7/Runtime/Snapshot/GhostSendSystem.cs:673)
    6. Unity.NetCode.IJobParallelForDeferRefExtensions+JobParallelForDeferProducer`1[T].Execute (T& jobData, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at Library/PackageCache/com.unity.netcode@0.6.0-preview.7/Runtime/Snapshot/GhostSendSystem.cs:58)
    Code (CSharp):
    1. Missing EndSend, calling BeginSend without calling EndSend will result in a memory leak
    2. UnityEngine.Debug:LogError (object)
    3. Unity.Networking.Transport.NetworkDriver/ClearEventQueue:Execute () (at Library/PackageCache/com.unity.transport@0.6.0-preview.7/Runtime/NetworkDriver.cs:635)
    4. Unity.Jobs.IJobExtensions/JobStruct`1<Unity.Networking.Transport.NetworkDriver/ClearEventQueue>:Execute (Unity.Networking.Transport.NetworkDriver/ClearEventQueue&,intptr,intptr,Unity.Jobs.LowLevel.Unsafe.JobRanges&,int) (at /home/bokken/buildslave/unity/build/Runtime/Jobs/Managed/IJob.cs:30)
    5.  
     
  23. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    464
    Hi everybody,

    We just opened a new sub-forum to discuss DOTS multiplayer topics and moved previous related threads over, including this one. We will be closing this thread for further comments to encourage the use of the new forum. Thank you all for contributing to this thread during the last two years.
     
Thread Status:
Not open for further replies.