docs.unity3d.com
Search Results for

    Show / Hide Table of Contents

    Use entity command buffers for structural changes

    When iterating through entities in an ECS system, your use case might require performing certain operations with entity components or entities themselves, such as removing or adding a component, or destroying an entity from the current loop iteration. Such changes are called structural changes.

    Structural changes modify an entity's archetype, which changes the chunk layout the iteration is processing. This section shows why you can't perform such changes immediately during an iteration, and how to use an entity command buffer (ECB) to defer them to a point after the iteration completes.

    The section covers the following topics:

    • Example use case involving structural changes during iteration.
    • Why EntityManager.RemoveComponent doesn't work in this case.
    • The solution: Use an entity command buffer.
    • Alternative approach: creating ECB instance manually.
    Note

    This workflow builds on concepts introduced in the Authoring and baking workflow. It assumes you have a working project with the Cube prefab, the RotationSpeed component, and the RotationSystem that rotates entities. Completing the steps in Entity prefab instantiation workflow is optional, but makes it easier to observe the effects of these examples.

    Prerequisites

    1. A Unity 6.X project with the following packages installed:

      • Entities
      • Entities Graphics
    2. To follow along in the Editor, complete the steps described in Authoring and baking workflow.

    3. Optional: Complete the steps described in Entity prefab instantiation workflow.

    Example of structural changes during iteration

    Consider the rotation system from Authoring and baking workflow:

    public partial struct RotationSystem : ISystem
    {
        // The BurstCompile attribute indicates that the method should be compiled
        // with the Burst compiler into highly-optimized native CPU code.
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            // In ECS, use the DeltaTime property from Entities.SystemAPI.Time.
            float deltaTime = SystemAPI.Time.DeltaTime;
    
            // Create a query that selects all entities that have a LocalTransform
            // component and a RotationSpeed component.
            // In each loop iteration, the transform variable is assigned 
            // a read-write reference to LocalTransform, and the speed variable is
            // assigned a read-only reference to the RotationSpeed component.
            foreach (var (transform, speed) in
                        SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>>())
            {
                // ValueRW and ValueRO both return a reference to the actual component
                // value. The difference is that ValueRW does a safety check for 
                // read-write access while ValueRO does a safety check for read-only
                // access.
                transform.ValueRW = transform.ValueRO.RotateY(
                    speed.ValueRO.RadiansPerSecond * deltaTime);
            }
        }
    }
    

    Assume you want entities to stop rotating after they've rotated through a certain angle. To implement this, you need to remove the RotationSpeed component from entities that have completed their rotation.

    Create the RotationLifetime component

    First, create a new component that tracks how much rotation remains before an entity should stop rotating.

    1. Create a new C# script called RotationLifetimeAuthoring.cs and replace the contents with the following code:

      using Unity.Entities;
      using Unity.Mathematics;
      using UnityEngine;
      
      // The authoring component provides a way to define the rotation speed of
      // a GameObject in the Editor. ECS does not use the authoring component at 
      // runtime, but converts it into an entity component using the Baker class.    
      public class RotationLifetimeAuthoring : MonoBehaviour
      {
          public float DegreesRemaining = 360.0f;
      }
      
      // In the baking process, this Baker runs once for every RotationSpeedAuthoring
      // instance in a subscene.
      class RotationLifetimeBaker : Baker<RotationLifetimeAuthoring>
      {
          public override void Bake(RotationLifetimeAuthoring authoring)
          {
              // GetEntity returns an entity that ECS creates from the GameObject using
              // pre-built ECS baker methods. TransformUsageFlags.Dynamic instructs the
              // Bake method to add the Transforms.LocalTransform component to the entity.
              var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
      
              var rotationLifetime = new RotationLifetime
              {
                  RadiansRemaining = math.radians(authoring.DegreesRemaining)
              };            
      
              AddComponent(entity, rotationLifetime);
          }
      }
      
      public struct RotationLifetime: IComponentData
      {
          public float RadiansRemaining;
      }
      
    2. Add the Rotation Lifetime Authoring component to the Cube prefab. This gives each cube a configurable amount of rotation before it stops.

    Why EntityManager.RemoveComponent doesn't work in this case

    The EntityManager struct provides a RemoveComponent method. This method is suitable for immediate, synchronous changes when the code isn't iterating over entities that would be affected by the structural change.

    The following code example shows a common mistake of attempting to remove the RotationSpeed component inside a foreach loop using the EntityManager.RemoveComponent method:

    using Unity.Burst;
    using Unity.Entities;
    using Unity.Transforms;
    
    // NOTICE: This code demonstrates an INVALID approach that causes an error.
    [BurstCompile]
    public partial struct RotationSystemInvalidExample : ISystem
    {
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            float deltaTime = SystemAPI.Time.DeltaTime;
    
            foreach (var (transform, speed, lifetime, entity) in
                        SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>,
                        RefRW<RotationLifetime>>().WithEntityAccess())
            {
                float rotationThisFrame = speed.ValueRO.RadiansPerSecond * deltaTime;
                transform.ValueRW = transform.ValueRO.RotateY(rotationThisFrame);
                lifetime.ValueRW.RadiansRemaining -= rotationThisFrame;
    
                if (lifetime.ValueRO.RadiansRemaining <= 0)
                {
                    // INVALID: This operation causes an InvalidOperationException.
                    // You cannot make structural changes while iterating over entities.
                    state.EntityManager.RemoveComponent<RotationSpeed>(entity);
                }
            }
        }
    }
    

    The previous code causes an exception at runtime:

    InvalidOperationException: Structural changes are not allowed during iteration.
    You must use an EntityCommandBuffer to defer structural changes until after the iteration is complete.
    

    When you iterate over entities with SystemAPI.Query, ECS reads from memory chunks that are organized by archetype (the specific combination of components an entity has). Removing a component changes the entity's archetype, which means:

    • The entity must move to a different chunk.
    • The chunk layout the code is currently iterating over becomes invalid.
    • ECS can't safely continue the iteration.

    The following operations are considered structural changes:

    • Creating or destroying an entity.
    • Adding or removing components.
    • Setting a shared component value.

    The solution: Use an entity command buffer

    Instead of executing an EntityManager.RemoveComponent command immediately, you can queue the command into an entity command buffer. These queued commands are executed later in the frame, after the iteration is complete.

    For better performance and easier management, use one of Unity's built-in ECB systems, such as EndSimulationEntityCommandBufferSystem. This allows multiple systems to queue commands that Unity executes together at a specific point in the frame.

    To implement this approach:

    1. In a system, get the singleton for the EndSimulationEntityCommandBufferSystem using SystemAPI.GetSingleton.

      var ecbSingleton = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();
      
    2. Create the command buffer from the singleton using the CreateCommandBuffer method.

      var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
      
    3. Inside a loop, use ecb.RemoveComponent<RotationSpeed>(entity) instead of EntityManager.RemoveComponent<RotationSpeed>(entity) to remove a component. This queues the commands that make structural changes in the command buffer.

      foreach (var (transform, speed, lifetime, entity) in
                  SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>,
                  RefRW<RotationLifetime>>().WithEntityAccess())
      {
          float rotationThisFrame = speed.ValueRO.RadiansPerSecond * deltaTime;
          transform.ValueRW = transform.ValueRO.RotateY(rotationThisFrame);
          lifetime.ValueRW.RadiansRemaining -= rotationThisFrame;
      
          if (lifetime.ValueRO.RadiansRemaining <= 0)
          {
              // Record the command now, execute it later.
              // The component is removed after this system, and other systems
              // in the simulation group, finish their iteration.
              ecb.RemoveComponent<RotationSpeed>(entity);
          }
      }
      
      Note

      SystemAPI.Query is called with the WithEntityAccess method in this loop. This is to get a reference to the entity itself, from which the code removes a component later.

    4. The EndSimulationEntityCommandBufferSystem automatically executes the commands at the end of the simulation group. You don't need to trigger the execution manually. For more information about built-in ECB systems, refer to the section Default EntityCommandBufferSystem systems.

    Create a new C# script called RotationSystemECB.cs and replace the contents with the following code:

    using Unity.Burst;
    using Unity.Entities;
    using Unity.Transforms;
    
    // This system rotates entities until they've rotated a specified amount,
    // then removes the RotationSpeed component to stop rotation.
    [DisableAutoCreation]
    public partial struct RotationSystemECB : ISystem
    {
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            float deltaTime = SystemAPI.Time.DeltaTime;
    
            // Get an EntityCommandBuffer from the EndSimulationEntityCommandBufferSystem.
            // Commands recorded to this command buffer play back at the end of the
            // simulation group, after all systems have finished iterating.
            var ecbSingleton = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem
                .Singleton>();
            var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
    
            foreach (var (transform, speed, lifetime, entity) in
                        SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>,
                        RefRW<RotationLifetime>>().WithEntityAccess())
            {
                float rotationThisFrame = speed.ValueRO.RadiansPerSecond * deltaTime;
                transform.ValueRW = transform.ValueRO.RotateY(rotationThisFrame);
                lifetime.ValueRW.RadiansRemaining -= rotationThisFrame;
    
                if (lifetime.ValueRO.RadiansRemaining <= 0)
                {
                    // Record the command now, execute it later.
                    // The component is removed after this system, and other systems
                    // in the simulation group, finish their iteration.
                    ecb.RemoveComponent<RotationSpeed>(entity);
                }
            }
        }
    }
    

    In the previous system, ecb.RemoveComponent() queues the command, the iteration continues safely, and the EndSimulationEntityCommandBufferSystem system executes the commands after the iteration ends. This lets you avoid exceptions from modifying entities during iteration, and reduces sync points by batching structural changes.

    If you have the original RotationSystem from the Authoring and baking workflow in your project, either remove it or disable it using the [DisableAutoCreation] attribute. Otherwise, both systems will process the same entities.

    Note

    Entity command buffers don't batch commands for faster execution. When the ECB executes, it executes the same code path as EntityManager does. The main benefit of ECBs is that they let you defer structural changes to a safe point in the frame, and enable multiple systems or parallel jobs to queue commands independently.

    Alternative approach: create an ECB instance manually

    The code in the previous section uses the built-in EndSimulationEntityCommandBufferSystem system to create an entity command buffer.

    In certain scenarios, you might want to create and manage an entity command buffer manually, without relying on a built-in system. For example, if you want to execute commands immediately after a loop completes.

    To implement manual ECB creation and execution:

    1. Create a new EntityCommandBuffer using Allocator.Temp before your loop begins.

      var ecb = new EntityCommandBuffer(Allocator.Temp);
      

      For more information on different allocators, refer to Memory allocators overview.

    2. Inside a loop, use ecb.RemoveComponent<RotationSpeed>(entity) to remove a component.

    3. After the loop finishes, call ecb.Playback(state.EntityManager) to execute all queued commands.

    4. Dispose of the buffer using ecb.Dispose(). When using Allocator.Temp, the buffer is deallocated automatically at the end of the frame, but it's good practice to dispose of it explicitly.

    The complete code implementing the alternative solution:

    using Unity.Burst;
    using Unity.Collections;
    using Unity.Entities;
    using Unity.Transforms;
    
    public partial struct RotationSystemManualECB : ISystem
    {
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            float deltaTime = SystemAPI.Time.DeltaTime;
    
            // Create a temporary command buffer.
            var ecb = new EntityCommandBuffer(Allocator.Temp);
    
            foreach (var (transform, speed, lifetime, entity) in
                        SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>,
                        RefRW<RotationLifetime>>().WithEntityAccess())
            {
                float rotationThisFrame = speed.ValueRO.RadiansPerSecond * deltaTime;
                transform.ValueRW = transform.ValueRO.RotateY(rotationThisFrame);
                lifetime.ValueRW.RadiansRemaining -= rotationThisFrame;
    
                if (lifetime.ValueRO.RadiansRemaining <= 0)
                {
                    // Queue the command.
                    ecb.RemoveComponent<RotationSpeed>(entity);
                }
            }
    
            // Execute all queued commands immediately after the loop.
            ecb.Playback(state.EntityManager);
    
            // Dispose of the buffer.
            ecb.Dispose();
        }
    }
    

    Additional resources

    • Entity command buffer overview
    • Use an entity command buffer
    • Entity command buffer playback
    • Automatic playback and disposal of entity command buffers
    • Manage structural changes introduction
    • Authoring and baking workflow
    • Prefab instantiation workflow
    In This Article
    Back to top
    Copyright © 2026 Unity Technologies — Trademarks and terms of use
    • Legal
    • Privacy Policy
    • Cookie Policy
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)