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. Dismiss Notice

DOTS NetCode 0.4.0 released

Discussion in 'NetCode for ECS' started by timjohansson, Sep 17, 2020.

  1. Justin_Larrabee

    Justin_Larrabee

    Joined:
    Apr 24, 2018
    Posts:
    106
    I equally stress how important this is. The core ECS has supported hybrid because of this exact reason, and by doing so has received an immense amount of usage and feedback from partners and the community. The API has improved because of this.
     
  2. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    Can't remember where I read this, but was there supposed to be a sample of Netcode + Unity.Physics at some point?

    I see the one about lag compensation (physicsWorldHistory), but will there be a sample demonstrating, for example, a player-controlled rigidbody with prediction?

    I'd mostly be interested in this because I'm facing difficulties with prediction in my own project and it'd be useful to have a working example to study. Right now, predicted entities always jitter a little bit in my project
     
    Last edited: Oct 1, 2020
    Samsle likes this.
  3. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    The initialization order between the callbacks and scene loading seems to have changed in 20.2. If you remove the checks for GetActiveScene().name == "Asteroids" in NetCodeBootStrap.cs and GameMain.cs it should load in 20.2.
     
    PhilSA likes this.
  4. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Right now that is not possible, we are looking at ways to bring that functionality back
     
    tagergo and PhilSA like this.
  5. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Right now you would need to have multiple subscenes with sets of ghosts, selectively load the ones you want - not auto load all of them - and finally have a system which combines them to a single GhostCollection at runtime before you go in-game.
    We are planning to look at this specific problem to make it easier and more flexible as one of the next things we work on
     
  6. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Replied in that thread, it's a bug when building with il2cpp - we'll get that fixed
     
  7. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Are you expecting to use GameObjects on the server or is it client only? If we are talking about client-only things, would it be enough to point World.DefaultGameObjectInjectionWorld to the client world instead of the default world and use the regular ConvertToEntity? If not, what's missing?

    You can make a sub-scene not load automatically and trigger loading of it only on the server - but this is something we need to iterate a bit on and make easier to work with.

    If you're spawning at runtime I would assume you invoke ConvertGameObjectHeirarchy directly, in which case you don't need a ConvertToEntity. Not sure I understand the use-case you are talking about correctly.

    We do want to have multiple clients + server easily visible at the same time, but there is a lot of work to do before that is possible, it is much more than just a few fixed to the old implementation.
     
    bb8_1 and optimise like this.
  8. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    There will be at some point, but I can't really say when. There is still some work required to make prediction of rigidbodies possible without lots of hacks and workarounds
     
    Extrys, QuadomSoft, Samsle and 3 others like this.
  9. Lukas_Kastern

    Lukas_Kastern

    Joined:
    Aug 31, 2018
    Posts:
    97
    After upgrading to 0.4 I experience miss predictions on the movement of my client predicted character, every time I spawn an owner predicted bullet my character jitters a bit.

    I have pointed it down to the actual spawning if I remove the call to instantiate I have no problems at all. It also doesn't have anything to do with any of my scripts that would be on the bullet. I already tested an entity with only the GhostAuthoring and OwnerComponent on it and the behaviour is the same.

    I haven't filled a bug report yet because I wasn't able to reproduce the same behaviour in the NetCube example or whatever it's called. Did someone else experience something similar?

    Am I just missing something obvious?
     
  10. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Not sure if this is the problem in your case, but this often happens if you forget to add a
    if (!GhostPredictionSystemGroup.ShouldPredict(tick, predictedGhostComponent)) return;
    at the beginning of your prediction systems.
    There is no guarantee that all entities are rolled back to the same tick, and the prediction loop will run from the oldest tick any predicted ghost was rolled back to. The early-out check makes sure you only run the prediction code for entities which should be predicted for a specific tick. (This is something we want to make easier to work with in the future)
    When you use predicted spawning you will get entities rolled back to different ticks even if all your world state fit in a single packet.
     
    bb8_1 and Lukas_Kastern like this.
  11. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    909
    As outlined in my previous post, I'm trying to make channels for 2-4 players and I'm using server worlds. It works but I'm not sure how it's supposed to scale. It runs fast enough in release but I'll hit the 16.6ms mark sooner than I want. 20 empty channels take 3-4ms in release.

    So question, is it the intended way to even create that many (20-100) server worlds? For every world, systems get created. Does ECS batch these worlds or do the systems all run individually? If so, I don't feel like this is good design for scale. 1 system should run over all server worlds, right?

    I made tests with 20-100 server worlds. Took a long time to startup, but that's on me. In the editor, so times are of course relative, every server world took around 1.5ms.

    Suggestion, introduce ENABLE_NETSTATS directive so we don't always have NetCode in debug mode, gathering stats takes a lot of performance

    Looking at the profiler, NetworkReceiveSystem takes most of performance waiting for a job.complete.
    upload_2020-10-1_16-22-36.png

    Can someone with a better understanding explain to me why there's so much idle time? Is it the time spent in the main thread till jobs kick off?
    There are 127 calls to Job.Complete and I'm not using any so, I guess the GetBufferFromEntity has to do with it which makes performance right now impossible to to optimize further. The work around right now seems to be to acquire all buffers at the start of the frame and get them as reference in the systems.
    If this bug doesn't get fixed soon, I propose to integrate the workaround till it's fixed.
    In case this has nothing to do with it, I'm sorry. :)

    Another question and that's more DOTS in general is how to instantiate a prefab from world A in world B?
    (Sidenote, Move, Copy, ExclusiveEntityTransaction is documented very poorly!)
    It seems like a big hassle that only can be done on main thread. I've not found a way for Command buffer to move anything from world A to B.
    Overall this part seems neglected and I wonder if anyone has solved or ever needed this.
    My use-case is to have a world with "skill" entities that servers as database world and instantiate and move them to the server worlds, that way a lot of memory can be freed because I don't have to create every skill in every server world.

    My "best" solution was to create a comp of the data that is needed for the skill to be created and have that processed in a main thread that is instantiating the skills in world A. After the system finished I MoveEntitiesFrom with an EntityQuery. During moving Entity fields get remapped (if you want it or not) so my source and target entities got lost.
    I tried different methods no matter, the remapping screwed things up.
    DOTS devs, please improve this path, there is already an internal move that skips remapping, we need access to it. Sometimes we just don't want to have anything rewritten!

    As this wasn't working out that well, my next try will be with Blobs. I think it fits better to what I actually want to do anyway.
     
    Last edited: Oct 1, 2020
    Lukas_Kastern, bb8_1 and florianhanke like this.
  12. Lukas_Kastern

    Lukas_Kastern

    Joined:
    Aug 31, 2018
    Posts:
    97
    That indeed was the problem after all. Have I just been very lucky or did some of the prediction behaviour change since 0.2?

    I also want to state that I really appreciate the changes you guys did towards usability of the entire package.
     
    bb8_1 likes this.
  13. optimise

    optimise

    Joined:
    Jan 22, 2014
    Posts:
    2,032
    Yes. Then I can use ConvertToEntity monoBehaviour to do convert and inject game object, it will directly convert the entity to client world. I still have some of the things need to use that workflow.
     
  14. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    We have not done any optimization for this yet, we are planning to look at the use-case of running many server worlds in the same process in the future, but it is not our top priority right now.

    Which part of it is taking performance? Do you have timings or a profiler capture?

    I don't see any Complete call in that screenshot, Complete would end up as an explicit scope on the main thread. That looks more like job scheduling overhead, does it run faster if you disable the JobsDebugger in the Jobs menu?

    We do a lot of Complete calls to wait for the results of previous frame, and those have very low cost - so just number of calls does not mean that much. Which ones do you see taking much time on the main thread?
     
    bb8_1 likes this.
  15. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    There were some changes to the timing and how far we roll back. We have a new backup and restore system which means we do not roll back to the last received snapshot if we did not get any new data, instead we roll back to the last full simulated frame. That change makes it more important to get the early-outs right.
     
    Lukas_Kastern likes this.
  16. coffeecatcoding

    coffeecatcoding

    Joined:
    Sep 28, 2020
    Posts:
    10
    Hello,

    I've been trying out the NetCode package for a few days and I keep struggling with input and prediction.
    For example if I want to calculate a rotation for the camera using Input.GetAxis(), the result is different on client and server.

    I am sampling the input as follows:
    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.NetCode;
    3. using UnityEngine;
    4.  
    5.     [UpdateInGroup(typeof(ClientSimulationSystemGroup))]
    6.     [UpdateBefore(typeof(GhostSimulationSystemGroup))]
    7.     [UpdateBefore(typeof(CommandSendSystemGroup))]
    8.     public class SamplePlayerInput : SystemBase
    9.     {
    10.         private ClientSimulationSystemGroup _clientSimulationSystemGroup;
    11.  
    12.         protected override void OnCreate()
    13.         {
    14.             base.OnCreate();
    15.             RequireSingletonForUpdate<NetworkStreamInGame>();
    16.             _clientSimulationSystemGroup = World.GetExistingSystem<ClientSimulationSystemGroup>();
    17.         }
    18.  
    19.         protected override void OnUpdate()
    20.         {
    21.             Entity localPlayer = GetSingleton<CommandTargetComponent>().targetEntity;
    22.             if(localPlayer == Entity.Null)
    23.                 return;
    24.            
    25.             Entity playerCamera = GetComponent<PlayerCameraReferenceData>(localPlayer).target;
    26.             if (playerCamera == Entity.Null)
    27.                 return;
    28.  
    29.             if (_clientSimulationSystemGroup.ServerTick == 0 || !EntityManager.HasComponent<PlayerMovementInput>(localPlayer))
    30.                 return;
    31.  
    32.             PlayerInputConfigData keyData = EntityManager.GetComponentData<PlayerInputConfigData>(localPlayer);
    33.             DynamicBuffer<PlayerMovementInput> inputBuffer = EntityManager.GetBuffer<PlayerMovementInput>(localPlayer);
    34.  
    35.             PlayerMovementInput input = default;
    36.             input.Tick = _clientSimulationSystemGroup.ServerTick;
    37.            
    38.             if (Input.GetKey(keyData.upKey))
    39.                 input.vertical = 1;
    40.             if (Input.GetKey(keyData.downKey))
    41.                 input.vertical = -1;
    42.             if (Input.GetKey(keyData.leftKey))
    43.                 input.horizontal = -1;
    44.             if (Input.GetKey(keyData.rightKey))
    45.                 input.horizontal = 1;
    46.  
    47.             input.jumpKeyPressed = Input.GetKey(keyData.jumpKey);
    48.  
    49.             input.mouseX = (int) (Input.GetAxis("Mouse X") * 100);
    50.             input.mouseY = (int) (Input.GetAxis("Mouse Y") * 100);
    51.             input.mouseScrollDelta = (int) (Input.mouseScrollDelta.y * 100);
    52.  
    53.             inputBuffer.AddCommandData(input);
    54.         }
    55.     }

    And then it is processed as follows:
    Code (CSharp):
    1.    
    2. [UpdateInGroup(typeof(GhostPredictionSystemGroup))]
    3.     public class PlayerInputProcessingSystem : SystemBase
    4.     {
    5.         private GhostPredictionSystemGroup _ghostPredictionSystemGroup;
    6.  
    7.         protected override void OnCreate()
    8.         {
    9.             base.OnCreate();
    10.             RequireSingletonForUpdate<EnableGame>();
    11.             RequireSingletonForUpdate<NetworkIdComponent>();
    12.             _ghostPredictionSystemGroup = World.GetExistingSystem<GhostPredictionSystemGroup>();
    13.         }
    14.  
    15.         protected override void OnUpdate()
    16.         {
    17.             uint predictingTick = _ghostPredictionSystemGroup.PredictingTick;
    18.             float unscaledDeltaTime = UnityEngine.Time.unscaledDeltaTime;
    19.  
    20.             Entities.WithBurst().ForEach
    21.             ((
    22.                 DynamicBuffer<PlayerMovementInput> inputBuffer,
    23.                 ref PlayerMovementData movement,
    24.                 in PredictedGhostComponent prediction
    25.             ) =>
    26.             {
    27.                 if (!inputBuffer.GetDataAtTick(predictingTick, out PlayerMovementInput cmd))
    28.                     return;
    29.                 if (!GhostPredictionSystemGroup.ShouldPredict(predictingTick, prediction))
    30.                     return;
    31.  
    32.                 inputBuffer.GetDataAtTick(predictingTick, out PlayerMovementInput input);
    33.  
    34.                  float2 cursorMovement = new float2(input.mouseX * 0.01f, input.mouseY * 0.01f);
    35.                  movement.rotationAngles += cursorMovement * rotationSpeed * unscaledDeltaTime;
    36.               }).Run();
    37.         }
    38. }
    39.                
    40.  

    And the value for movement.rotationAngles is different for client and server.
    The server is always behind but never actually syncs up.

    Which leads me to a few questions:

    Why is that? How can I handle input properly?

    If I understand correctly this checks if there is any data for the tick currently predicted:
    Code (CSharp):
    1. if (!inputBuffer.GetDataAtTick(predictingTick, out PlayerMovementInput cmd))
    2.     return;
    And this checks if there is already newer data available:
    Code (CSharp):
    1. if (!GhostPredictionSystemGroup.ShouldPredict(predictingTick, prediction))
    2.     return;
    I am not certain if I understand this line (from LagCompensation sample):
    Code (CSharp):
    1. if (!_ghostPredictionSystemGroup.IsFinalPredictionTick)
    2.     return;
    3.  
    Is the final prediction tick the last tick the client is predicting just before the server will verfiy all movement has been correct?
    Which means in the LagCompensation sample the raycasts will only be performed just before the server verifies the hit?

    I've been looking through the samples and searched for answers trying to understand how they work but I can not solve this problem.
    I am thankful for every response.
     
  17. jposey

    jposey

    Joined:
    Aug 14, 2014
    Posts:
    16
    I believe I've found bug(s) in the Unity Transport package (0.4.1-preview.1) after having disconnects and new connections.

    I am able to reproduce it with the NetCube sample. I made a Win-Client build, ran the server in Unity Editor, and launched clients and closed them and opened new ones.

    With NetCube, if I limited it to only 1 client ever connected at a time, the problem was not reproducible. It seems to require having had a disconnect and then connecting a 2nd simultaneous client. In my own project, it is reproducible from 1 client connecting, disconnecting, and connecting again. The error and callstack are different from what happens in NetCube.

    Reproduction Steps using NetCube:
    1. Launch standalone NetCube server
    2. Launch first NetCube client
    3. Launch second NetCube client
    4. Close second NetCube client
    5. Launch third NetCube client

    Here is the error and callstack that gets spammed after this:

    ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
    Unity.Networking.Transport.DataStreamReader.ReadBytes (System.Byte* data, System.Int32 length) (at Library/PackageCache/com.unity.transport@0.4.1-preview.1/Runtime/DataStream.cs:624)
    Unity.Networking.Transport.DataStreamReader.ReadULong () (at Library/PackageCache/com.unity.transport@0.4.1-preview.1/Runtime/DataStream.cs:682)
    Unity.NetCode.RpcSystem+RpcExecJob.Execute (Unity.Entities.ArchetypeChunk chunk, System.Int32 chunkIndex, System.Int32 firstEntityIndex) (at Library/PackageCache/com.unity.netcode@0.4.0-preview.1/Runtime/Rpc/RpcSystem.cs:215)
    Unity.Entities.JobChunkExtensions+JobChunkProducer`1[T].ExecuteInternal (Unity.Entities.JobChunkExtensions+JobChunkWrapper`1[T]& jobWrapper, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at Library/PackageCache/com.unity.entities@0.14.0-preview.18/Unity.Entities/IJobChunk.cs:363)
    Unity.Entities.JobChunkExtensions+JobChunkProducer`1[T].Execute (Unity.Entities.JobChunkExtensions+JobChunkWrapper`1[T]& jobWrapper, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at Library/PackageCache/com.unity.entities@0.14.0-preview.18/Unity.Entities/IJobChunk.cs:337)

    This is from my own project, and the error comes from the ReliableSequenced pipeline code.

    ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
    Unity.Networking.Transport.DataStreamReader.ReadBytes (System.Byte* data, System.Int32 length) (at Library/PackageCache/com.unity.transport@0.4.1-preview.1/Runtime/DataStream.cs:624)
    Unity.Networking.Transport.ReliableSequencedPipelineStage.Receive (Unity.Networking.Transport.NetworkPipelineContext& ctx, Unity.Networking.Transport.InboundRecvBuffer& inboundBuffer, Unity.Networking.Transport.NetworkPipelineStage+Requests& requests) (at Library/PackageCache/com.unity.transport@0.4.1-preview.1/Runtime/Pipelines/ReliableSequencedPipelineStage.cs:67)
    Unity.Networking.Transport.NetworkPipelineProcessor.ProcessReceiveStage (System.Int32 stage, Unity.Networking.Transport.NetworkPipeline pipeline, System.Int32 internalBufferOffset, System.Int32 internalSharedBufferOffset, Unity.Networking.Transport.NetworkPipelineContext& ctx, Unity.Networking.Transport.InboundRecvBuffer& inboundBuffer, Unity.Collections.NativeList`1[System.Int32]& resumeQ, System.Boolean& needsUpdate, System.Boolean& needsSendUpdate) (at Library/PackageCache/com.unity.transport@0.4.1-preview.1/Runtime/NetworkPipeline.cs:912)
    Unity.Networking.Transport.NetworkPipelineProcessor.ProcessReceiveStagesFrom (Unity.Networking.Transport.NetworkDriver driver, System.Int32 startStage, Unity.Networking.Transport.NetworkPipeline pipeline, Unity.Networking.Transport.NetworkConnection connection, Unity.Networking.Transport.InboundRecvBuffer buffer) (at Library/PackageCache/com.unity.transport@0.4.1-preview.1/Runtime/NetworkPipeline.cs:851)
    Unity.Networking.Transport.NetworkPipelineProcessor.Receive (Unity.Networking.Transport.NetworkDriver driver, Unity.Networking.Transport.NetworkConnection connection, Unity.Collections.NativeArray`1[T] buffer) (at Library/PackageCache/com.unity.transport@0.4.1-preview.1/Runtime/NetworkPipeline.cs:819)
    Unity.Networking.Transport.NetworkDriver.AppendPacket (Unity.Networking.Transport.NetworkInterfaceEndPoint address, Unity.Networking.Transport.Protocols.UdpCHeader header, System.Int32 dataLen) (at Library/PackageCache/com.unity.transport@0.4.1-preview.1/Runtime/NetworkDriver.cs:1328)
    Unity.Networking.Transport.NetworkPacketReceiver.AppendPacket (Unity.Networking.Transport.NetworkInterfaceEndPoint address, Unity.Networking.Transport.Protocols.UdpCHeader header, System.Int32 dataLen) (at Library/PackageCache/com.unity.transport@0.4.1-preview.1/Runtime/INetworkInterface.cs:25)
    Unity.Networking.Transport.BaselibNetworkInterface+ReceiveJob.Execute () (at Library/PackageCache/com.unity.transport@0.4.1-preview.1/Runtime/BaselibNetworkInterface.cs:326)
    Unity.Jobs.IJobExtensions+JobStruct`1[T].Execute (T& data, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at <a9810827dce3444a8e5c4e9f3f5e0828>:0)
     
  18. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    909
    I've found a pretty fundamental problem in NetCode 0.4 with lost inputs and I made a specific test extending the NetCube sample which also failed.
    My test uses GetKeyDown(KeyCode.Space). The expected behaviour is that every keyDown is received on the server but in reality some are lost. They are reported on the client but not on the server.

    The test server runs in AWS and I'm not aware of any issues having to with that.

    Any help or insight is appreciated.
     
  19. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    909
    I can reproduce this error on my local machine. It's not just in 0.4 but in 0.3 too.

    Here's a log from client:
    Code (CSharp):
    1. 23735 local Input jump
    2. 23735 MoveCubeSystem jump - Final Tick: True
    3. 23735 MoveCubeSystem jump - Final Tick: False
    4. 23735 MoveCubeSystem jump - Final Tick: False
    5. 24600 local Input jump
    6. 24600 MoveCubeSystem jump - Final Tick: True
    7. 24600 MoveCubeSystem jump - Final Tick: False
    8. 24600 MoveCubeSystem jump - Final Tick: False
    9. 24600 MoveCubeSystem jump - Final Tick: False
    10. 24700 local Input jump
    11. 24700 MoveCubeSystem jump - Final Tick: True
    12. 24700 MoveCubeSystem jump - Final Tick: False
    13. 24700 MoveCubeSystem jump - Final Tick: False
    14. 24700 MoveCubeSystem jump - Final Tick: False
    15. 24830 local Input jump
    16. 24830 MoveCubeSystem jump - Final Tick: True
    17.  
    18.  
    and server:
    Code (CSharp):
    1. Server setting connection 2 to in game
    2. 23735 MoveCubeSystem jump
    3. 24600 MoveCubeSystem jump
    4. 24700 MoveCubeSystem jump
    3 space presses go through. the last one doesn't. The curious thing is that you can see the prediction runs several times when it works but only run once when it doesn't.

    So, when the prediction tick is the final tick it seems the input is lost!
     
  20. Lukas_Kastern

    Lukas_Kastern

    Joined:
    Aug 31, 2018
    Posts:
    97
    @Enzi Your problem might be related to storing input for the same tick multiple times.

    In case you haven't yet try adding the following to your input storing code, where inputBuffer is the buffer of your ICommandData
    .
    Code (CSharp):
    1.            
    2. if ( !inputBuffer.GetDataAtTick( command.Tick, out var dupCmd ) || dupCmd.Tick != command.Tick )
    3. {
    4.      inputBuffer.AddCommandData( command );
    5. }
     
  21. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    909
    I tried your code but the problem persists.This was a good catch from you though, the error seems very similar.

    I'm knee-deep in this now and I have debugged this to the point where I can say that the correct data isn't sent in the CommandSendSystem. I've extended ICommandData with a field uint jumpDebug { get; set; } so I can debug other values than tick and when the space press goes through it's working with an expected value of 1.
    When the problem occurs, the value is 0. So at some point the CommandData gets overwritten which is really weird.
     
  22. Lukas_Kastern

    Lukas_Kastern

    Joined:
    Aug 31, 2018
    Posts:
    97
    Maybe I'm missing something but I am not able to reproduce your issue.
    Inside the NetCube sample input struct, I've added a jump bool.

    Added this system to print the state
    Code (CSharp):
    1. [UpdateInGroup(typeof( SimulationSystemGroup ))]
    2. [UpdateAfter(typeof(SampleCubeInput))]
    3. public class DebugPrintInput : SystemBase
    4. {
    5.     protected override void OnUpdate( )
    6.     {
    7.         var tick = World.GetExistingSystem<GhostPredictionSystemGroup>( ).PredictingTick;
    8.  
    9.         Entities.WithoutBurst().WithAll<PredictedGhostComponent>().ForEach( ( in GhostOwnerComponent ownerComponent, in DynamicBuffer<CubeInput> inputs ) =>
    10.         {
    11.             inputs.GetDataAtTick( tick, out var input );
    12.             Debug.Log( $"{this.World.Name}: Jump state from owner {ownerComponent.NetworkId} at tick {tick} is {input.jump}" );
    13.         } ).Run( );
    14.     }
    15. }
    And replaced the line inputBuffer.AddCommandData( input); inside CubeInput.cs with
    Code (CSharp):
    1.         input.jump = Input.GetKeyDown( KeyCode.Space );
    2.  
    3.         if ( !inputBuffer.GetDataAtTick( input.Tick, out var dupCmd ) || dupCmd.Tick != input.Tick )
    4.         {
    5.             inputBuffer.AddCommandData( input );
    6.         }
    After looking at the console log the input of the client and server seems to be in sync.
     
  23. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    909
    Here's the project: Link
    Can someone reproduce this error? My Unity version is 2020.1.4f1

    I've written the sample project new with the code you posted. I haven't put in the condition before the AddCommandData. as it can be reproduced with or without it.

    On some space presses the server world receives it, sometimes it doesn't and there's also a state where not even the prediction gets it.

    I don't need very long to get this error but yeah, sometimes it seems to work just fine and then there are 3 lost ones.
    Here's some output:
    upload_2020-10-4_0-31-35.png
     
  24. coffeecatcoding

    coffeecatcoding

    Joined:
    Sep 28, 2020
    Posts:
    10
    Since I've been struggling with input for days, I've been following along and reproduced the error.
    Downloaded the project and downgraded to 2020.1.3f1
    Feels like it misses a lot more if you spam jump fast.

    upload_2020-10-4_1-3-5.png
     
    Enzi likes this.
  25. coffeecatcoding

    coffeecatcoding

    Joined:
    Sep 28, 2020
    Posts:
    10
    @Enzi I applied the problems I have been struggling with (see above) in your demo;

    Create a component and apply it to the cube prefab:
    Code (CSharp):
    1. using Unity.Entities;
    2.  
    3. [GenerateAuthoringComponent]
    4. public struct CountData : IComponentData
    5. {
    6.     public int count;
    7. }

    To MoveCubeSystem.cs add another if statement below the others:
    Code (CSharp):
    1. if (input.jump)
    2.     counter.count += 1;


    Now launch the demo in the editor and press space a few times and check the entity debugger for the client and server side version of the cube countdata and notice how they might be equal the first few times, but if you keep pressing space they will diverge; Also the number of time you pressed space doesn't even match with the client side version which would be expected;
     
  26. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    909
    I got it into a working state. At least I can't reproduce the error anymore.
    Here's a link

    Code (CSharp):
    1. uint serverTick = m_ClientSimulationSystemGroup.ServerTick;
    2.  
    3.         var inputBuffer = EntityManager.GetBuffer<CubeInput>(localInput);
    4.         var isInputForThisTickAlreadyStored = inputBuffer.GetDataAtTick(serverTick, out var dupCmd) && dupCmd.Tick == serverTick;
    5.  
    6.         var spaceDown = Input.GetKeyDown(KeyCode.Space);
    7.  
    8.         if (spaceDown)
    9.         {
    10.             Debug.Log("CLIENT JUMP at tick " + serverTick);
    11.         }
    12.  
    13.         if (!isInputForThisTickAlreadyStored)
    14.         {
    15.             var input = default(CubeInput);
    16.             input.Tick = serverTick;
    17.  
    18.             input.jump = spaceDown;
    19.  
    20.             if (spaceDown)
    21.                 Debug.Log(serverTick + "/" + callCount + " creating new jump state - spaceDown : " + spaceDown);
    22.             inputBuffer.AddCommandData(input);
    23.         }
    24.         else
    25.         {
    26.             if (spaceDown)
    27.                 Debug.Log(serverTick + "/" + callCount + " already in buffer jump state: " + dupCmd.jump + " spaceDown : " + spaceDown);
    28.  
    29.             if (spaceDown)
    30.                 dupCmd.jump = spaceDown;
    31.  
    32.             inputBuffer.AddCommandData(dupCmd);
    33.         }
    Problem is that I don't understand why this is working. I thought I had a grasp how the input sampling and command data reacts but I was wrong.

    Why can a tick be sampled twice and why does a tick in commandData even exist?
    I haven't seen this usage anywhere in the samples so before anyone else has this issue, please update the samples.
    Using KeyDown is otherwise not reliable. Samples use the key state and with that it's easy to miss that some inputs are actually lost.
     
  27. coffeecatcoding

    coffeecatcoding

    Joined:
    Sep 28, 2020
    Posts:
    10
    Unfortunately for me, the problem still persists, I downloaded your updated project and added a CountData component to the cube and an extra if statement as described above and the count diverges on client and server;

    [Edited]
    I am pretty certain now that the server applies the inputs for the ticks correctly and once, and the client applies the inputs multiple times
    Basically this: https://forum.unity.com/threads/netcode-duplicated-input-in-buffer.821415/
    The input is being sampled just before the GhostSimulationSystemGroup.

    [Edit2:]
    I added a count variable to count how many times input has been applied and an uint variable to store the last applied input tick.
    This is to prevent the client to apply the same input twice in a row (only checks if the current input being evaluated has the same tick as the last one)
    It seems to work pretty well and has reduced the difference a lot but sometimes the server is applying random ticks that don't even exist?
    The server is applying an input from tick 401 which is never created.
    403 does not exist either and is not applied.
    mismatch2.jpg
     
    Last edited: Oct 4, 2020
  28. ThomasEgan

    ThomasEgan

    Joined:
    Dec 17, 2013
    Posts:
    20
    The input issue occurs because the server is running at a fixed rate and the client is updating every frame, this allows multiple client updates to occur for the same server tick, if you create a new input struct each frame the jump input can get overwritten for that tick. This is much easier to replicate when you slow down the fixed timestep using ClientServerTickRate.SimulationTickRate.

    What you want to do is something like this:
    Code (CSharp):
    1. if (inputBuffer.GetDataAtTick(tick, out var input) && input.Tick != tick)
    2. {
    3.    // new tick, new input
    4.    input = default(TestInput);
    5. }
    6.      
    7. input.Tick = tick;
    8. input.KeyDown |= Input.GetKeyDown(KeyCode.Space);
    Note that GetDataAtTick returns true even if it doesn't have the input for that tick which is why we have to compare input.Tick to tick (when this is the case it returns the previous tick, I guess this is to aid extrapolation on the server if the input hasn't arrived)
     
    Last edited: Oct 5, 2020
  29. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    909
    Thank you very much to both of you! @Lukas_Kastern and @ThomasEgan
    That was a very clear explanation, totally missed the fixed rate ...
     
  30. coffeecatcoding

    coffeecatcoding

    Joined:
    Sep 28, 2020
    Posts:
    10
    @ThomasEgan
    Hello and thanks for your reply, I am confused now, did your comment mean: "new tick, new input" instead of "new frame, new input" ?
    From my understanding this if statement is checking for new ticks;
    Also, do I understand correctly, you use this operator
    |=
    because the input is being sampled each frame and multiple frames are being processed within one tick, therefore the
    input.KeyDown
    will remain true even if the button was pressed for only one frame?

    I am still experiencing problems with the client applying input twice sometimes :c
     
  31. ThomasEgan

    ThomasEgan

    Joined:
    Dec 17, 2013
    Posts:
    20
    @coffeecatcoding
    Yeah you're right, "new tick, new input" makes more sense (I was thinking frame = server frame but yeah that's confusing in this context), I've edited it for clarity.
    You're also correct about the |= operator, it's equivalent to
    if(Input.GetKeyDown(KeyCode.Space)){input.KeyDown = true;}
    which makes sure we keep the keydown event recorded over every client frame for the server tick.

    I'm not exactly sure what you mean about about the client 'applying' input twice but I suspect it's due to how the prediction works on the client.
    On each frame the client checks if there is a new server snapshot, if there is then it rolls back to that snapshot then predicts forward in fixed ticks as far as it can, if there is no new snapshot then it rolls back to the last predicted fixed tick, from this point it then predicts the next subtick (some fraction of a full tick).
    This means at slow simulation rates or fast framerates you end up getting several prediction subticks for the same server tick.
    To deal with this you should apply input in the GhostPredictionSystemGroup and make sure the things affected by input (eg. position) are included in your snapshot so they get rolled back (ie. they are marked with the GhostField attribute).
     
    coffeecatcoding likes this.
  32. coffeecatcoding

    coffeecatcoding

    Joined:
    Sep 28, 2020
    Posts:
    10
    @ThomasEgan
    Thank you very much.
    Your explanation has improved my understanding and I was able to get everything to work as expected.

    After your reply I checked the manual again and I must have missed the following part:
    "For each component, you need to add an attribute to the values you want to sent. Add a [GhostField] attribute to the fields you want to send in an IComponentData."

    I realized I was trying to add the [GhostField(Quantization=1000)] attribute to a float value in my ICommandData input struct before, because I wanted to sample and send mouse movement and quantize it, which did not work as expected. It worked when I stored it as int and multiplied/divided manually with respective casting.
    After your reply I added the attribute to the float value in a seperate IComponentData to which I was trying to add the input value to instead. Now I understand that by adding the attribute to the field in the IComponentData I am including it in the snapshot and hence it needs to be quantized.

    But why is it not possible (or necessary ?) to quantize a float value using this attribute in an ICommandData struct? Should I be storing and converting it as integer manually like I already did or can I just use a float without worrying ?
     
  33. ThomasEgan

    ThomasEgan

    Joined:
    Dec 17, 2013
    Posts:
    20
    @coffeecatcoding
    GhostFields/Snapshots are simulation state and are sent from the server to the client (the client also stores a buffer of these to rewind during prediction and to interpolate with).
    ICommandData are inputs and are sent from the client to the server.
    ICommandData are automatically serialized and sent as long as CommandTargetComponent is setup correctly, I'd recommend having a read of https://docs.unity3d.com/Packages/com.unity.netcode@0.4/manual/command-stream.html, note the Manual serialization section if you want to perform your own quantization but I wouldn't worry about that level of optimization until you need to.
     
    coffeecatcoding likes this.
  34. coffeecatcoding

    coffeecatcoding

    Joined:
    Sep 28, 2020
    Posts:
    10
    @ThomasEgan
    Okay thank you again, you were a big help, the picture is coming together now.
     
  35. flyQuixote

    flyQuixote

    Joined:
    Jun 23, 2013
    Posts:
    25
    Hello, I've been using DOTS and NetCode a bit for a project and have run into an error with subscenes.

    When using netcode 0.4.0, I'm running into an error where whenever I unload and reload the subscene for the client (I'm doing this for connecting and disconnecting), it duplicates the ghost entities shared between the client and server. I'm not sure if this is an error in my code or how I'm currently using the library.

    By default the subscene is no loaded (no subscene is loaded). The subscene is loaded on the server when the game starts.

    Code (CSharp):
    1.  
    2. // On Connect
    3. World.GetOrCreateSystem<SceneSystem>().LoadSceneAsync(SubSceneReferences.Instance.GetSceneByName("TestRoom").SceneGUID);
    4. // On disconnect
    5. World.GetOrCreateSystem<SceneSystem>().UnloadScene(SubSceneReferences.Instance.GetSceneByName("TestRoom").SceneGUID);
    6.  
    I'm getting an error after unloading the subscene on the client (even if I'm not connected to the server) so I'm not sure if this error of duplicating objects is due to netcode or due to my use of subscenes. Seeing as the error seems to be associated with the physics world I'm not really sure where to start debugging or if this is the right form/thread to ask on. I can send more information about the project if it would help resolving this issue. I'm not sure how to use the new subscene workflow required in netcode 0.4.0

    Code (CSharp):
    1. InvalidOperationException: The BlobAssetReference is not valid. Likely it has already been unloaded or released.
    2. Unity.Entities.BlobAssetReferenceData.ValidateNonBurst () (at Library/PackageCache/com.unity.entities@0.14.0-preview.19/Unity.Entities/Blobs.cs:253)
    3. Unity.Entities.BlobAssetReferenceData.ValidateAllowNull () (at Library/PackageCache/com.unity.entities@0.14.0-preview.19/Unity.Entities/Blobs.cs:280)
    4. Unity.Entities.BlobAssetReference`1[T].GetUnsafePtr () (at Library/PackageCache/com.unity.entities@0.14.0-preview.19/Unity.Entities/Blobs.cs:332)
    5. Unity.Physics.ManifoldQueries.BodyBody (Unity.Physics.RigidBody& rigidBodyA, Unity.Physics.RigidBody& rigidBodyB, Unity.Physics.MotionVelocity& motionVelocityA, Unity.Physics.MotionVelocity& motionVelocityB, System.Single collisionTolerance, System.Single timeStep, Unity.Physics.BodyIndexPair pair, Unity.Collections.NativeStream+Writer& contactWriter) (at Library/PackageCache/com.unity.physics@0.5.0-preview.1/Unity.Physics/Collision/Queries/Manifold.cs:48)
    6. Unity.Physics.NarrowPhase+ParallelCreateContactsJob.ExecuteImpl (Unity.Physics.PhysicsWorld& world, System.Single timeStep, Unity.Collections.NativeArray`1[T] dispatchPairs, System.Int32 dispatchPairReadOffset, System.Int32 numPairsToRead, Unity.Collections.NativeStream+Writer& contactWriter) (at Library/PackageCache/com.unity.physics@0.5.0-preview.1/Unity.Physics/Dynamics/Simulation/Narrowphase.cs:111)
    7. Unity.Physics.NarrowPhase+ParallelCreateContactsJob.Execute (System.Int32 workItemIndex) (at Library/PackageCache/com.unity.physics@0.5.0-preview.1/Unity.Physics/Dynamics/Simulation/Narrowphase.cs:78)
    8. Unity.Jobs.IJobParallelForDeferExtensions+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.jobs@0.5.0-preview.14/Unity.Jobs/IJobParallelForDefer.cs:62)
     
    Lukas_Kastern and florianhanke like this.
  36. Lukas_Kastern

    Lukas_Kastern

    Joined:
    Aug 31, 2018
    Posts:
    97
    I think I have the same issue or a similar one but it's not consistent for me. I noticed one more or less interesting thing when inspecting the duplicate entities inside the entity debugger. Their scene tag is different. This means one of the entities references the correct scene while the other one references a scene it doesn't or shouldn't even belong to.

    I only recently switched to using subscenes for ghosts so I'm not sure if that error is on my side or on one of the packages.
     
    flyQuixote and florianhanke like this.
  37. Lukas_Kastern

    Lukas_Kastern

    Joined:
    Aug 31, 2018
    Posts:
    97
    @flyguy23ndm Did you make any progress with that issue?
     
  38. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    909
    This is from the unity blog: https://blogs.unity3d.com/2020/10/0...020-2-for-smoother-gameplay-what-did-it-take/

    I'm using Application.targetFrameRate = 60 currently to prevent errors in NetCode. This means I should switch to vsync.
    Does vsync run normally in batchmode? I couldn't find any information about it, so if anyone knows, please tell me. :)
     
  39. FakeByte

    FakeByte

    Joined:
    Dec 8, 2015
    Posts:
    147
    Batchmode might run Vsync on windows, but when you run it on a server without monitor then vsync will be ignored and Application.targetFrameRate has to be used.
     
  40. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    909
    Okay, that makes sense. Thank you.
     
  41. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    Can you please file a bug for this? Thanks
     
  42. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    This sounds like the subscene is not being unloaded for some reason, or that the entities from the subscene are not destroyed when unloading it. I can't really say what is wrong without debugging it unfortunately.
     
  43. Sobirux

    Sobirux

    Joined:
    Apr 2, 2019
    Posts:
    6
    I am facing an issue with the Unity Netcode Version 0.4.0-preview.1, and Unity version 2020.9f.1

    When I add a
    GhostAuthoringComponent
    to a prefab and hit "Update component list", I cannot change any settings for the items in the list (e.g., Server/Interpolated Client/Predicted Client) since they are all grayed out. I have also tested it out with a fresh, empty prefab and still they are all unchangeable. Has anybody else experienced this? I am not quite sure what the issue is here.


     
    Last edited: Oct 22, 2020
  44. kanesteven

    kanesteven

    Joined:
    Aug 30, 2018
    Posts:
    45
    Sobirux and Lukas_Kastern like this.
  45. AdamBebko

    AdamBebko

    Joined:
    Apr 8, 2016
    Posts:
    161
    I've read all the posts in this thread and am still not understanding how the sub scene system works. I can create a sub scene, It's recognized by the entity debugger. But there doesn't seem to any way to target the client world or server world or both.

    The official unity reply to this was in my opinion not valid English :) I didn't understand a word of it. The hello cube example still uses the deprecated ConvertToClientServerEntity, so there's no help there.

    They will end up in all worlds, but they will be converted differently depending on the NetCodeConversionSettings you add to your build settings assets.

    What does this mean?

    You also need a system which tells the sub-scene system which version of the subscene to load, see https://github.com/Unity-Technologi...pleproject/Assets/Samples/ConfigureSystems.cs . The hashes in that file is the guids of the Client and Server build settings assets.
    You can write custom conversion systems which reads the NetCodeConversionSettings if you need to do something else.

    So I need to fiddle with GUIDs and keep multiple versions of each sub scene? No thanks, I'll keep using the deprecated script.

    If you do not use NetCodeConversionSettings or a setup like ConfigureSystems.cs it will load the same subscene on both client and server - then adapt the prefabs and ghosts at runtime.
    What does adapt mean in this context?


    I'm sorry if my tone sounds a bit negative. I'm a huge fan of this new dots system. And the recent improvements have made it much easier to use! I'm just struggling to understand why something was deprecated when there's no new documentation or information out on how to replace it. I'm sure I'm not the only one struggling to understand this.

    Before, it was very simple, if I wanted a game object on the server, I would go to the ConvertToClientEntity inspector, pick server, if I wanted it only on the client I would pick client. The new way seems WAY more difficult to understand. Perhaps we could have a little script that attaches to a sub scene that targets the server or client worlds?
     
    bb8_1 and florianhanke like this.
  46. flyQuixote

    flyQuixote

    Joined:
    Jun 23, 2013
    Posts:
    25
    Hey, sorry for not being as active this last two weeks or so. I've found that making everything a ghost entity and just never loading/unloading them on the client seems to work just about fine. I haven't bene able to get a good environment working for this yet though.

    I can post and share more of my code if you're interested. If you wanted to make a larger simulated environment you would probably have to load static objects (like terrain and buildings) with something that is not netcode and only synchronize dynamic objects that are close to the player. There is probably a smart way to overwrite and use the ghost map to only sync the objects you need but for right now I'm going to just make everything a ghost object and only load/unload on the server. Bad solution but it just might work :)
     
  47. Ali_Bakkal

    Ali_Bakkal

    Joined:
    Jan 26, 2013
    Posts:
    84
    Hello everyone,

    I am experiencing an issue with the last version of Netcode 0.4 (Unity Editor 2020.1) that i had not in Netcode 0.2.
    If you create a server build and a client build for Mac Os.
    And If you launch your server from a terminal which works and launch the client app, they do not see each other (client not able to connect to server).
    If i launch the server and the client inside Unity Editor, it works the client is able to connect to the server.

    Plus I tried to reproduce the problem with the last version of Netcube project in Unity's multiplayer repository. And i had the same problem. The client cannot connect to the server outside Unity Editor...

    So my guess is outside Unity Editor, the client is not able to find the server,
    Do you have any idea how to fix that ?

    (i did not have this issue at all with Netcode 0.2 and Unity Editor 2019.3)
    @timjohansson

    Thanks in advance
     
  48. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    When conversion runs it will generate a client version of the sub-scene and a server version of the sub-scene. The content of those two files will be slightly different (ghosts are converted differently and you can write other converters which also check the setting and do different things).
    When subscenes load the server world will load the server version of the sub-scene while the client world will load the client version of the same sub-scene. So they will load different files which are both generated from the same source.

    Adapt in this case means remove the components which should not be there. The sub-scene will contain all components which are required on the client OR the server, when the sub-scene is loaded the server world will remove all components which should not be there on the server and the client will remove all components which should not be there on the client.
     
    florianhanke likes this.
  49. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    That's not something I've seen, and I've built client + server on macos a few times.
    The only big change to making standalone builds recently is that you have to use the build configs in Assets/BuildSettings instead of the build menu if you have any sub-scenes in the project. If you do not the standalone will fail to load any data and nothing will work.
     
  50. AdamBebko

    AdamBebko

    Joined:
    Apr 8, 2016
    Posts:
    161
    Thanks so much for the quick reply. Perhaps what is not clear to me is how to accomplish "when the sub-scene is loaded the server world will remove all components which should not be there on the server and the client will remove all components which should not be there on the client"

    How do I tell it which objects/components etc should/shouldn't be there without the ConvertToClientServerEntity Component? I'm definitely missing something very key here.

    Also and relatedly, In a hybrid Mono/ECS project, is there a way for a server to not load all the monobehaviours? Right now it still loads them all. I suspect subscenes might also help with this if I understood them correctly.