Search Unity

Bug Predicted Ghost Classification System Doesn't Work with Different System Name

Discussion in 'NetCode for ECS' started by JinxMD, Jan 22, 2024.

  1. JinxMD

    JinxMD

    Joined:
    Jul 5, 2021
    Posts:
    19
    I took the code from the NetcodeSamples for classifying the predicted ghost spawns on the client and couldn't get it to work in my project despite it being almost identical apart from the
    GrenadeData
    component being replaced with my
    MissileData
    component and the system name to
    TestMissileClassificationSystem
    . The error I was facing was:

    Code (CSharp):
    1. InvalidOperationException: The UNKNOWN_OBJECT_TYPE ClassificationJob.JobData.snapshotDataLookupHelper.m_SnapshotDataLookupCache has not been assigned or constructed. All containers must be valid when scheduling a job.
    which I understand to mean that you need to assign values to all containers when creating a job so it doesn't schedule the job. If I add
    [NativeDisableContainerSafetyRestriction]
    to the
    SnapshotDataLookupHelper
    field, it runs just fine so I'd guess that the helper is initializing properly. The strangest part is that if I use the name from the example,
    ClassificationSystem
    , the job works even without the disable safety restriction attribute. I've reproduced this bug in the NetcodeSamples repository. Here are some system names I've tested:
    • ClassificationSystem ✅
    • HelloWorldClassificationSystem ✅
    • TestClassificationSystem ✅
    • TestMissile ✅
    • UFTB55MZYby7SXw ✅
    • TestMissileClassificationSystem ❌
    • TestMClassificationSystem ❌
    • TestMissileee ❌
    • RandomNameSystem ❌
    • K9u19MHShhAwRwa ❌
    I can't really see any pattern to these names. For each of these examples, I:
    • Restarted the editor
    • Forced the code generation
    • Tried several game restarts
    • Used Editor 2022.3.0
    • Used Netcode for Entities v1.0.17
    I've also used 2022.3.13f1 in my own project which has the same issue.

    Code (CSharp):
    1. using Unity.Burst;
    2. using Unity.Entities;
    3. using Unity.NetCode;
    4. using Unity.NetCode.LowLevel;
    5.  
    6. namespace Samples.HelloNetcode
    7. {
    8.     [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
    9.     [UpdateInGroup(typeof(GhostSimulationSystemGroup))]
    10.     [UpdateAfter(typeof(GhostSpawnClassificationSystem))]
    11.     [CreateAfter(typeof(GhostCollectionSystem))]
    12.     [CreateAfter(typeof(GhostReceiveSystem))]
    13.     [BurstCompile]
    14.     public partial struct TestMissileClassificationSystem : ISystem
    15.     {
    16.         SnapshotDataLookupHelper m_SnapshotDataLookupHelper;
    17.         BufferLookup<PredictedGhostSpawn> m_PredictedGhostSpawnLookup;
    18.         ComponentLookup<GrenadeData> m_GrenadeDataLookup;
    19.         // The ghost type (grenade) this classification system will process
    20.         int m_GhostType;
    21.  
    22.         [BurstCompile]
    23.         public void OnCreate(ref SystemState state)
    24.         {
    25.             m_SnapshotDataLookupHelper = new SnapshotDataLookupHelper(ref state,
    26.                 SystemAPI.GetSingletonEntity<GhostCollection>(),
    27.                 SystemAPI.GetSingletonEntity<SpawnedGhostEntityMap>());
    28.             m_PredictedGhostSpawnLookup = state.GetBufferLookup<PredictedGhostSpawn>();
    29.             m_GrenadeDataLookup = state.GetComponentLookup<GrenadeData>();
    30.             state.RequireForUpdate<GhostSpawnQueue>();
    31.             state.RequireForUpdate<PredictedGhostSpawnList>();
    32.             state.RequireForUpdate<NetworkId>();
    33.             state.RequireForUpdate<GrenadeSpawner>();
    34.         }
    35.  
    36.         [BurstCompile]
    37.         public void OnUpdate(ref SystemState state)
    38.         {
    39.             if (m_GhostType == 0)
    40.             {
    41.                 // Lookup the grenade prefab entity in the ghost prefab list, from there we can find the ghost type for this prefab
    42.                 var prefabEntity = SystemAPI.GetSingleton<GrenadeSpawner>().Grenade;
    43.                 var collectionEntity = SystemAPI.GetSingletonEntity<GhostCollection>();
    44.                 var ghostPrefabTypes = state.EntityManager.GetBuffer<GhostCollectionPrefab>(collectionEntity);
    45.                 for (int i = 0; i < ghostPrefabTypes.Length; ++i)
    46.                 {
    47.                     if (ghostPrefabTypes[i].GhostPrefab == prefabEntity)
    48.                         m_GhostType = ghostPrefabTypes[i].GhostType.GetHashCode();
    49.                 }
    50.             }
    51.             m_SnapshotDataLookupHelper.Update(ref state);
    52.             m_PredictedGhostSpawnLookup.Update(ref state);
    53.             m_GrenadeDataLookup.Update(ref state);
    54.             var classificationJob = new ClassificationJob
    55.             {
    56.                 snapshotDataLookupHelper = m_SnapshotDataLookupHelper,
    57.                 spawnListEntity = SystemAPI.GetSingletonEntity<PredictedGhostSpawnList>(),
    58.                 PredictedSpawnListLookup = m_PredictedGhostSpawnLookup,
    59.                 grenadeDataLookup = m_GrenadeDataLookup,
    60.                 ghostType = m_GhostType
    61.             };
    62.             state.Dependency = classificationJob.Schedule(state.Dependency);
    63.         }
    64.  
    65.         [WithAll(typeof(GhostSpawnQueue))]
    66.         [BurstCompile]
    67.         partial struct ClassificationJob : IJobEntity
    68.         {
    69.             public SnapshotDataLookupHelper snapshotDataLookupHelper;
    70.             public Entity spawnListEntity;
    71.             public BufferLookup<PredictedGhostSpawn> PredictedSpawnListLookup;
    72.             public ComponentLookup<GrenadeData> grenadeDataLookup;
    73.             public int ghostType;
    74.  
    75.             public void Execute(DynamicBuffer<GhostSpawnBuffer> ghosts, DynamicBuffer<SnapshotDataBuffer> data)
    76.             {
    77.                 var predictedSpawnList = PredictedSpawnListLookup[spawnListEntity];
    78.                 var snapshotDataLookup = snapshotDataLookupHelper.CreateSnapshotBufferLookup();
    79.                 for (int i = 0; i < ghosts.Length; ++i)
    80.                 {
    81.                     var newGhostSpawn = ghosts[i];
    82.                     if (newGhostSpawn.SpawnType != GhostSpawnBuffer.Type.Predicted || newGhostSpawn.HasClassifiedPredictedSpawn || newGhostSpawn.PredictedSpawnEntity != Entity.Null)
    83.                         continue;
    84.  
    85.                     // Mark all the grenade spawns as classified even if not our own predicted spawns
    86.                     // otherwise spawns from other players might be picked up by the default classification system when
    87.                     // it runs when we happen to have a predicted spawn in the predictedSpawnList not yet classified here
    88.                     if (newGhostSpawn.GhostType == ghostType)
    89.                         newGhostSpawn.HasClassifiedPredictedSpawn = true;
    90.  
    91.                     // Find new ghost spawns (from ghost snapshot) which match the predict spawned ghost type handled by
    92.                     // this classification system. Match the spawn ID data from the new spawn (by lookup it up in
    93.                     // snapshot data) with the spawn IDs of ghosts in the predicted spawn list. When matched we replace
    94.                     // the ghost entity of that new spawn with our predict spawned entity (so the spawn will not result
    95.                     // in a new instantiation).
    96.                     for (int j = 0; j < predictedSpawnList.Length; ++j)
    97.                     {
    98.                         if (newGhostSpawn.GhostType == predictedSpawnList[j].ghostType)
    99.                         {
    100.                             if (snapshotDataLookup.TryGetComponentDataFromSnapshotHistory(newGhostSpawn.GhostType, data, out GrenadeData grenadeData, i))
    101.                             {
    102.                                 var spawnIdFromList = grenadeDataLookup[predictedSpawnList[j].entity].SpawnId;
    103.                                 if (grenadeData.SpawnId == spawnIdFromList)
    104.                                 {
    105.                                     newGhostSpawn.PredictedSpawnEntity = predictedSpawnList[j].entity;
    106.                                     predictedSpawnList[j] = predictedSpawnList[predictedSpawnList.Length - 1];
    107.                                     predictedSpawnList.RemoveAt(predictedSpawnList.Length - 1);
    108.                                     break;
    109.                                 }
    110.                             }
    111.                         }
    112.                     }
    113.                     ghosts[i] = newGhostSpawn;
    114.                 }
    115.             }
    116.         }
    117.     }
    118. }
    119.  

    This is probably the most unusual bug I've faced. What is going on?
     
    Last edited: Jan 22, 2024
  2. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    894
    The released code had many bug. One of this is at line 100

    Code (csharp):
    1.  
    2. if (snapshotDataLookup.TryGetComponentDataFromSnapshotHistory(newGhostSpawn.GhostType, data, out GrenadeData grenadeData, i))  <---- should not be `i` , just 0
    3.  
    Each spawned entity has an history buffer, that contains up to 32 slot of ghost data. The TryGetComponentDataFromSnapshotHistory helper try to get a component data from that history buffer at history slot X (that is the last parameter). For newly spawned ghost there is only 1 snapshot, at index 0.[/code]
     
  3. JinxMD

    JinxMD

    Joined:
    Jul 5, 2021
    Posts:
    19
    Thanks for the reply. I see, that makes sense but still the issue I raised would happen so it's more a bug in Netcode for Entities than a bug in the sample code as the same issue is present in my project.