AddComponentDuringStructuralChange crash

We’re having some random crashes in a build (at least on PC and PS4) that points to AddComponentDuringStructuralChange or RemoveComponentDuringStructuralChange in a fairly simple system that add and remove components. Usualy, the order in which the add/remove/set component are called can cause a crash in a build and an error in the editor depending of their order if done in a foreach that loops more than once due to the structural change but never give any issues when looping once or without any foreach(). We’ve had the same crash also occur in another SystemBase without a foreach that was adding and removing components. Does it have anything to do with the order of the add/remove component? The crash happens very rarely.

Running Unity 2021.3.14f1, ECS package 0.51.1-preview.21 and burst 1.8.1

I have included a screenshot of the stack trace from the crash.

[UpdateInGroup(typeof(VehicleSystemGroup))]
    public partial class ActivateVehicleSystem : SystemBase
    {
        protected override void OnUpdate()
        {
            Entities.WithStructuralChanges().WithAll<Vehicle, VehicleInactive>().
                WithAny<PlayerCharacterActiveControl, PlayerCharacterNextControl>().
                ForEach((Entity vehicleEntity) =>
            {
                EntityManager.RemoveComponent<VehicleInactive>(vehicleEntity);
                EntityManager.AddComponent<VehicleActive>(vehicleEntity);
                EntityManager.AddComponent<OnVehicleActivated>(vehicleEntity);
                EntityManager.AddComponent<EquipBlock>(vehicleEntity);
            }).Run();
        }
    }

Here is another exemple. It seems to have crashed on the line
EntityManager.SetComponentData(playerEntity, breathContainer);

[UpdateInGroup(typeof(SwimSystemGroup))]
    [UpdateAfter(typeof(ActivateSwimSystem))]
    public partial class DeactivateSwimSystem : SystemBase
    {
        private EntityQuery swimControllerQuery;
        private EntityQuery playerQuery;

        private EntityQuery swimMoveQuery;
       
        protected override void OnCreate()
        {
            base.OnCreate();

            playerQuery = GetEntityQuery(ComponentType.ReadOnly<BreathContainer>());
            swimMoveQuery = GetEntityQuery(ComponentType.ReadOnly<OnSwimMove>());
           
            RequireForUpdate(swimControllerQuery);
            RequireForUpdate(playerQuery);
        }

        protected override void OnUpdate()
        {
            Entity playerEntity = playerQuery.GetSingletonEntity();
            bool isAlive = !EntityManager.HasComponent<Dead>(playerEntity);
           
            Entities.WithStructuralChanges().WithStoreEntityQueryInField(ref swimControllerQuery).
                WithAll<SwimController, SwimActive>().
                WithNone<OnSwimActivated>().
                WithNone<PlayerCharacterActiveControl, PlayerCharacterNextControl>().
                ForEach((Entity swimControllerEntity, ref SwimControllerWaterCurrent swimControllerWaterCurrent, in SwimAudio swimAudio,
                    in SwimControllerComponentObjects swimControllerComponentObjects) =>
                {
                    Rigidbody rigidbody = swimControllerComponentObjects.Rigidbody;
                    BreathContainer breathContainer = EntityManager.GetComponentData<BreathContainer>(playerEntity);
                   
                    if (isAlive)
                    {
                        breathContainer.Breath = breathContainer.MaxBreath;
                        EntityManager.SetComponentData(playerEntity, breathContainer);
                    }

                    EntityManager.RemoveComponent<SwimActive>(swimControllerEntity);
                    EntityManager.RemoveComponent<SwimTouchFloor>(swimControllerEntity);
                    EntityManager.AddComponent<SwimInactive>(swimControllerEntity);
                    EntityManager.AddComponent<OnSwimDeactivated>(swimControllerEntity);
                   
                    EntityManager.RemoveComponent<EquipBlock>(swimControllerEntity);

                    EntityManager.RemoveComponent<OnSwimMove>(swimMoveQuery);
                   
                    swimControllerWaterCurrent.CurrentForce = float3.zero;
                   
                    rigidbody.velocity = Vector3.zero;
                    rigidbody.gameObject.SetActive(false);
                    EntityManager.SetEnabled(swimAudio.AboveWaterLoopAudioElement, false);
                    EntityManager.SetEnabled(swimAudio.UnderWaterLoopAudioElement, false);
                   
                    if (swimAudio.GetOutPrefab != Entity.Null)
                    {
                        EntityManager.CreateAudioEvent(swimAudio.GetOutPrefab);
                    }
                    if (isAlive && breathContainer.Ratio < 0.3f && swimAudio.AirGaspPrefab != Entity.Null)
                    {
                        EntityManager.CreateAudioEvent(swimAudio.AirGaspPrefab);
                    }
            }).Run();
        }
    }

To be clear, the call stack in the first post was in 0.17.0, in which the same bug happened. In this version, this is the line in EntityComponentStore where the crash happens:

We’ve got several more points where this happens (On PS4 in this example). It seems to happen when it allocates a memory block for a new archetype? Could there a maximum amount of archetypes that can be created in the ECS?

There is 16MB limit for archetypes. But you can extend that limit for your requirements (of course keep in mind that on your own risk)
In EntityComponentStore.cs on line 493

entities->m_ArchetypeChunkAllocator = new BlockAllocator(AllocatorManager.Persistent, 16 * 1024 * 1024); // 16MB should be enough

You can change that 16MB limit.
And at the same time you should change that value in EntityQueryManager.cs on line 25

queryManager->m_GroupDataChunkAllocator = new BlockAllocator(AllocatorManager.Persistent, 16 * 1024 * 1024); // 16MB should be enough
2 Likes