Search Unity

Help Wanted Lag Compensation [Jobs + Burst Perfomance Problem]

Discussion in 'Multiplayer' started by ep1s0de, Jul 4, 2020.

  1. ep1s0de

    ep1s0de

    Joined:
    Dec 24, 2015
    Posts:
    123
    Hello comrades...

    I am writing a small multiplayer project and there is a problem with lagocompensation, the fact is that I store the history of the position of the bones of the character model in a buffer of 64 (for each tick of the game)

    Watch my video, it shows everything


    When initializing the player on the server side, I create 64 copies of the model's bones (64 ticks = 1 second since the tick delta = 0.015625) with its own separate layer, so that the hitboxes do not affect the surrounding objects in any way.

    When the player shoots, a physical ray is created on the server side that checks the collision with the HitBox. if the ray hits it, we get access to the lagocompensation script and, consequently, to the history of HitBox positions. The server always knows how many ticks the client is lagging behind, and based on the current difference, it takes 1 element and already deals damage to the player.

    And so, closer to the problem.
    The fact is that changing the position of the bones in a single thread (without Job System) greatly reduced performance (up to 0 FPS) and I resorted to using the Job System using the Burst compiler


    Script for an array of hitboxes with the ability to copy and set transform positions

    Code (CSharp):
    1.  
    2. using Unity.Burst;
    3. using Unity.Collections;
    4. using Unity.Jobs;
    5. using Unity.Mathematics;
    6. using UnityEngine;
    7. using UnityEngine.Jobs;
    8.  
    9. [BurstCompile]
    10. public struct Hitbox_Copy_Job : IJobParallelForTransform
    11. {
    12.     public NativeArray<float3> hitbox_pos;
    13.     public NativeArray<quaternion> hitbox_rot;
    14.  
    15.     public Hitbox_Copy_Job(int count)
    16.     {
    17.         hitbox_pos = new NativeArray<float3>(count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
    18.         hitbox_rot = new NativeArray<quaternion>(count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
    19.     }
    20.  
    21.     public void Execute(int index, TransformAccess transform)
    22.     {
    23.         hitbox_pos[index] = new float3(transform.position);
    24.         hitbox_rot[index] = new quaternion(transform.rotation.x,transform.rotation.y,transform.rotation.z,transform.rotation.w);
    25.     }
    26.  
    27.     public void Flush()
    28.     {
    29.         hitbox_pos.Dispose();
    30.         hitbox_rot.Dispose();
    31.     }
    32. }
    33.  
    34. [BurstCompile]
    35. public struct Hitbox_Set_Job : IJobParallelForTransform
    36. {
    37.     public NativeArray<float3> hitbox_pos;
    38.     public NativeArray<quaternion> hitbox_rot;
    39.  
    40.     public void SetArray(NativeArray<float3> _hitbox_pos, NativeArray<quaternion> _hitbox_rot)
    41.     {
    42.         hitbox_pos = _hitbox_pos;
    43.         hitbox_rot = _hitbox_rot;
    44.     }
    45.  
    46.     public void Execute(int index, TransformAccess transform)
    47.     {
    48.         transform.position = hitbox_pos[index];
    49.         transform.rotation = hitbox_rot[index];
    50.     }
    51. }
    52.  
    53. public class HitboxArray : MonoBehaviour
    54. {
    55.     public LagCompensationInfo LagCompensationInfo;
    56.     public Net_Player Player;
    57.     public Transform Transform;
    58.     [Header("Hitboxes")]
    59.     public Hitbox[] Hitboxes = new Hitbox[0];
    60.     public Transform[] Transforms;
    61.     public TransformAccessArray TransformsArray;
    62.  
    63.     private void Awake()
    64.     {
    65.         TransformsArray = new TransformAccessArray(Transforms);
    66.         copy_j = new Hitbox_Copy_Job(TransformsArray.length);
    67.     }
    68.  
    69.     Hitbox_Copy_Job copy_j;
    70.     JobHandle copy_h;
    71.  
    72.     public Hitbox_Copy_Job Copy()
    73.     {
    74.         copy_h = copy_j.Schedule(TransformsArray);
    75.  
    76.         copy_h.Complete();
    77.  
    78.         return copy_j;
    79.     }
    80.  
    81.     Hitbox_Set_Job set_j;
    82.     JobHandle set_h;
    83.  
    84.     public void Set(Hitbox_Copy_Job job)
    85.     {
    86.         set_j.SetArray(job.hitbox_pos,job.hitbox_rot);
    87.         set_h = set_j.Schedule(TransformsArray);
    88.  
    89.         set_h.Complete();
    90.     }
    91.  
    92.     private void OnDestroy()
    93.     {
    94.         TransformsArray.Dispose();
    95.         copy_j.Flush();
    96.     }
    97.     public bool DrawGizmos = false;
    98.  
    99. #if UNITY_EDITOR
    100.     private void OnValidate()
    101.     {
    102.         Hitboxes = GetComponentsInChildren<Hitbox>();
    103.         Transform = GetComponent<Transform>();
    104.         Transforms = new Transform[Hitboxes.Length];
    105.         if (Player)
    106.         {
    107.             for (int i = 0; i < Hitboxes.Length; i++)
    108.             {
    109.                 Hitboxes[i].Initialize(this,(byte)i);
    110.                 Transforms[i] = Hitboxes[i].Transform;
    111.             }
    112.         }
    113.     }
    114.     private void OnDrawGizmos()
    115.     {
    116.         if (!DrawGizmos)
    117.             return;
    118.  
    119.         for (int i = 0; i < Hitboxes.Length; i++)
    120.         {
    121.             Hitboxes[i].Debug();
    122.         }
    123.     }
    124. #endif
    125. }
    126.  
    Lag Compensation
    Code (CSharp):
    1.  
    2. using System;
    3. using UnityEngine;
    4.  
    5. public struct LagCompensationInfo
    6. {
    7.     public uint server_tick;
    8.     public HitboxArray HitboxArray;
    9. }
    10.  
    11. public class LagCompensation : MonoBehaviour
    12. {
    13.     public static bool LAG_COMPENSATION_ENABLED = true;
    14.     /// Maximum lag compensation time (ticks)
    15.     /// By default, the value is 64 (1 second) and if the network delay is 1 second (1000 milliseconds), the player will be compensated
    16.     public const int MAX_HISTORY_SIZE = 64;//(1f/64 = 0,015625 * 64 = 1)
    17.     public PlayerModel Model = null;
    18.     public static int LagCompensationLayer { get; private set; } = int.MinValue;
    19.     public Transform LagCompensationPool;
    20.     public LagCompensationInfo[] compensation_array =null;
    21.     public bool ShowLagCompensation = false;
    22.     public bool Initialized { get; private set; } = false;
    23.     public void Initialize()
    24.     {
    25.         if (!LAG_COMPENSATION_ENABLED)
    26.         {
    27.             Debug.LogWarning("LAG COMPENSATION IS NOT INITALIZED!!!");
    28.             return;
    29.         }
    30.  
    31.         if (Initialized)
    32.             return;
    33.  
    34.         Initialized = true;
    35.         compensation_array = new LagCompensationInfo[MAX_HISTORY_SIZE];
    36.         if (LagCompensationLayer == int.MinValue)
    37.         {
    38.             LagCompensationLayer = LayerMask.NameToLayer("LagCompensation");
    39.         }
    40.         LagCompensationPool = new GameObject("LagCompensationPool_" + Model.Player.player_id).transform;
    41.         for (int i = 0; i < compensation_array.Length; i++)
    42.         {
    43.             compensation_array[i] = new LagCompensationInfo();
    44.             compensation_array[i].server_tick = ServerGame.server_tick;
    45.             GameObject temp = Model.CloneHitboxArray();
    46.             temp.transform.SetParent(LagCompensationPool);
    47.             compensation_array[i].HitboxArray = temp.GetComponent<HitboxArray>();
    48.         }
    49.         LagCompensationPool.gameObject.SetLayerRecursively(LagCompensationLayer);
    50.     }
    51.  
    52.     uint index = 0;
    53.     public void SetupBones(uint _server_tick)
    54.     {
    55.         if (!Initialized)
    56.             return;
    57.  
    58.         compensation_array[index % MAX_HISTORY_SIZE].HitboxArray.LagCompensationInfo = compensation_array[index % MAX_HISTORY_SIZE];
    59.         compensation_array[index % MAX_HISTORY_SIZE].server_tick =  _server_tick;
    60.         compensation_array[index % MAX_HISTORY_SIZE].HitboxArray.DrawGizmos = ShowLagCompensation;
    61.         compensation_array[index % MAX_HISTORY_SIZE].HitboxArray.Set(Model.Hitboxes.Copy());
    62.         index++;
    63.     }
    64.  
    65.     public void Destroy()
    66.     {
    67.         if (Initialized)
    68.         {
    69.             Array.Clear(compensation_array, 0, compensation_array.Length);
    70.             if (LagCompensationPool)
    71.             {
    72.                 Destroy(LagCompensationPool.gameObject);
    73.             }
    74.         }
    75.     }
    76. }
    77.  
    Part of the code where damage occurs

    Code (CSharp):
    1. uint diff = ServerGame.server_tick - client_command.server_tick;
    2.  
    3.         if (Command.buttons.HasFlag(Buttons.PrimaryAttack))
    4.         {
    5.             if (diff < LagCompensation.MAX_HISTORY_SIZE)
    6.             {
    7.  
    8.                 /*
    9.                  * Solution to circumvent the restriction on collision layers
    10.                 We launch a beam that will capture all hitboxes that are caught in the beam and then we compare the player's identifier
    11.                 with our own so as not to cause damage to ourselves since the hitboxes in the lagocompensation system are located in the same physical layer
    12.                 */
    13.                 RaycastHit[] hits = Physics.RaycastAll(CameraOrigin(), CameraDirection(), Mathf.Infinity, 1 << LagCompensation.LagCompensationLayer);
    14.                 if (hits.Length > 0 && hits.Length >= 2)
    15.                 {
    16.  
    17.  
    18.                     Hitbox hitbox = null;
    19.  
    20.                     if(!hitbox && hits[0].collider != null)
    21.                     {
    22.                         if(hits[0].collider.TryGetComponent(typeof(Hitbox), out Component c))
    23.                         {
    24.                             Hitbox h = c as Hitbox;
    25.  
    26.                             if (h.HitboxArray.Player.player_id != player_id)
    27.                             {
    28.                                 hitbox = h;
    29.                             }
    30.                         }
    31.                     }
    32.  
    33.                     if (!hitbox && hits[1].collider != null)
    34.                     {
    35.                         if (hits[1].collider.TryGetComponent(typeof(Hitbox), out Component c))
    36.                         {
    37.                             Hitbox h = c as Hitbox;
    38.  
    39.                             if (h.HitboxArray.Player.player_id != player_id)
    40.                             {
    41.                                 hitbox = h;
    42.                             }
    43.                         }
    44.                     }
    45.  
    46.                
    47.                     if (hitbox)
    48.                     {
    49.                         if (hitbox)
    50.                         {
    51.                             //we take the information from the lag compensation according to the tick difference
    52.                             LagCompensationInfo info = hitbox.HitboxArray.Player.Compensation.compensation_array[diff];
    53.  
    54.                             info.HitboxArray.Player.OnHit(info.server_tick,hitbox.ID, this);
    55.                         }
    56.                     }
    57.                
    58.                 }
    59.             }
    60.         }
    The problem is that performance drops with each player even when using Job System + Burst, and this is the only problem I have encountered... Please help :)

    P.S. If anyone has had experience with lag compensation, please let us know about alternative methods for recording the positions of the model bones
     
    Last edited: Jul 4, 2020
unityunity