Search Unity

Bug Incongruity between Editor and DOTS Runtime

Discussion in 'Project Tiny' started by SINePrime, Feb 24, 2021.

  1. SINePrime

    SINePrime

    Joined:
    Jan 24, 2019
    Posts:
    54
    I am having a problem where an exception only occurs in my build:
    Code (CSharp):
    1. Unhandled Exception: System.IndexOutOfRangeException: Index 0 is out of range in DynamicBuffer of '0' Length.
    In the LiveLink entity conversion preview, I can see my entities will be converted properly:
    Screenshot 2021-02-24 122319.png

    When I run the scene in the editor, I can see that both the conversion and the game logic is working by examining the component data.

    I think my code is being mangled by the build process, but I'm unsure how to even proceed on that front.

    I'll include some relevant snippets below. For context, I'm making a finite state automata (state machine) in ECS. The automata model is a directed graph, while the executor is a handful of "task" Entity. Each task is unit of execution for a state in the automata.

    Conversion code:
    Code (CSharp):
    1.   /// <summary>
    2.   /// Authoring a <see cref="ClashZero.Actors.Character"/>.
    3.   /// </summary>
    4.   public class CharacterAuthoring : MonoBehaviour, IConvertGameObjectToEntity {
    5.  
    6.     [SerializeField] CharacterAttribtues attribtues;
    7.  
    8.     public void Convert(Entity character, EntityManager dstManager, GameObjectConversionSystem conversionSystem) {
    9.  
    10.       UnityEngine.Debug.Log(character);
    11.  
    12.       // add character components
    13.       dstManager.AddComponentData(
    14.         character,
    15.         new ClashZero.Actors.Character() {
    16.           Attribtues = attribtues
    17.         }
    18.       );
    19.  
    20.       // create machine
    21.       Entity machine = conversionSystem.CreateAdditionalEntity(this.gameObject);
    22.       dstManager.AddComponents(machine, MachineModel.Archetype);
    23.       dstManager.SetName(machine, $"{this.gameObject.name}-{nameof(MachineModel)}-0");
    24.  
    25.       // create neutral state
    26.       Entity state0_Neutral = conversionSystem.CreateAdditionalEntity(this.gameObject);
    27.       dstManager.AddComponents(state0_Neutral, StateModel.Archetype.Concat(typeof(ClashZero.Actors.CharacterState), typeof(ClashZero.Actors.StateBehaviours.Walk)));
    28.       dstManager.InsertStateIntoGraphAndMachine(machine, state0_Neutral, out _, out _);
    29.       dstManager.SetName(state0_Neutral, $"{this.gameObject.name}-{nameof(StateModel)}-0");
    30.  
    31.       UnityEngine.Debug.Assert(!dstManager.GetBuffer<VertexInGraph>(machine).IsEmpty);
    32.       UnityEngine.Debug.Assert(!dstManager.GetBuffer<StateInGraph>(machine).IsEmpty);
    33.  
    34.       // make character a MachineTask from the MachineModel
    35.       dstManager.AddComponents(character, MachineTask.Archetype.Concat(typeof(StatusWaitingToRun)));
    36.       dstManager.CopySetComponentsFromMachineModelToMachineTask(machine, character);
    37.  
    38.       UnityEngine.Debug.Assert(!dstManager.GetBuffer<VertexInGraph>(character).IsEmpty);
    39.       UnityEngine.Debug.Assert(!dstManager.GetBuffer<StateInGraph>(character).IsEmpty);
    40.  
    41.       return;
    42.     }
    43.   }
    44.  
    45.     /// <summary>
    46.     /// Copy a <see cref="DynamicBuffer{T}"/>.
    47.     /// </summary>
    48.     static public void CopyBuffer<T>(this EntityManager thisEntityManager, Entity srcEntity, Entity dstEntity) where T : struct, IBufferElementData {
    49.       // get the buffer
    50.       DynamicBuffer<T> dstBuffer = thisEntityManager.AddBuffer<T>(dstEntity);
    51.  
    52.       // copy from src to buffer
    53.       DynamicBuffer<T> srcBuffer = thisEntityManager.GetBuffer<T>(srcEntity);
    54.       for (int i = 0; i < srcBuffer.Length; i++) {
    55.         dstBuffer.Add(srcBuffer[i]);
    56.       }
    57.     }
    58.  
    59.     #region Add
    60.  
    61.     /// <summary>
    62.     /// Add components for a <see cref="MachineTask"/>.
    63.     /// </summary>
    64.     static public void CopySetComponentsFromMachineModelToMachineTask(this EntityManager thisEntityManager, Entity srcMachineModel, Entity dstMachineTask) {
    65.       // copy graph
    66.       thisEntityManager.CopyBuffer<Graphs.VertexInGraph>(srcMachineModel, dstMachineTask);
    67.       thisEntityManager.CopyBuffer<Graphs.EdgeInGraph>(srcMachineModel, dstMachineTask);
    68.  
    69.       // copy model
    70.       thisEntityManager.CopyBuffer<Model.StateInGraph>(srcMachineModel, dstMachineTask);
    71.       thisEntityManager.CopyBuffer<Model.InstructionInGraph>(srcMachineModel, dstMachineTask);
    72.       thisEntityManager.CopyBuffer<Model.TransitionInGraph>(srcMachineModel, dstMachineTask);
    73.  
    74.       // add machine
    75.       thisEntityManager.AddComponent<MachineTask>(dstMachineTask);
    76.  
    77.       // make MachineTask parent
    78.       DynamicBuffer<StateInGraph> childStates = thisEntityManager.GetBuffer<StateInGraph>(srcMachineModel);
    79.       if (!childStates.IsEmpty) {
    80.         thisEntityManager.AddComponentData(
    81.           dstMachineTask,
    82.           new ParentTask() {
    83.             ChildIndex = 0,
    84.             ChildTask = Entity.Null
    85.           }
    86.         );
    87.       }
    88.     }
    89.  
    90.     #endregion
    91.  
    The code that is throwing the exception uses the DynamicBuffer<VertexInGraph> and DynamicBuffer<StateInGraph> from the character-StateModel-0 at runtime. I'm hesitant to post that code, because the hierarchy is a bit confusing.

    Thoughts?
     
  2. gwenaelle_unity

    gwenaelle_unity

    Unity Technologies

    Joined:
    Jul 24, 2018
    Posts:
    14
    So far the exception just tells you that we are trying to access an element on a empty buffer. That's probably one of your buffer elements.
    Can you copy/paste the callstack of the exception to know exactly when it is happening?
    Also what do your buffer elements contain?
    Make sure to create entities and attach buffer elements to them in the destination world (dstManager) only. That's the only world we will be loading at runtime.
    We haven't maintained conversion in the Editor so far, so livelink and play mode is not really working for tiny yet. But we are going to focus on that in the next coming months to have a better authoring experience.
     
    Last edited: Feb 24, 2021
  3. SINePrime

    SINePrime

    Joined:
    Jan 24, 2019
    Posts:
    54
    Yes, but the problem (the incongruity) is that the Buffer is empty only in the DOTS Runtime.

    The first BufferElement, VertexInGraph, contains a reference to an Entity. The second BufferElement, StateInGraph, contains the index of the first buffer.

    It's happening when I try to traverse the hierarchy.
    Code (CSharp):
    1. Unhandled Exception: System.IndexOutOfRangeException: Index 0 is out of range in DynamicBuffer of '0' Length.
    2.    at Unity.Entities.DynamicBuffer`1.CheckBounds(Int32 index)
    3.    at Unity.Entities.DynamicBuffer`1.get_Item(Int32 index)
    4.    at VegaByteStudios.Unity.Automata.Tasks.EntityManagerUtils.InstantiateStateTask(EntityManager thisEntityManager, Entity machine, Int32 stateIndex)
    5.    at VegaByteStudios.Unity.Automata.Tasks.MachineCreateState.<OnUpdate>b__0_0(Entity machine, MachineModel& machineModel, MachineTask& machineTask, ParentTask& machineParent, StatusRunning& machineStatusRunning)
    6.    at Unity.Entities.EntityQueryBuilder.ForEach[T0,T1,T2,T3](F_EDDDD`4 action)
    7.    at VegaByteStudios.Unity.Automata.Tasks.MachineCreateState.OnUpdate()
    8.    at Unity.Entities.ComponentSystem.Update()
    9.    at Unity.Entities.ComponentSystemGroup.UpdateAllSystems()
    10.    at Unity.Entities.ComponentSystem.Update()
    11.    at Unity.Entities.ComponentSystemGroup.UpdateAllSystems()
    12.    at Unity.Entities.ComponentSystem.Update()
    13.    at Unity.Entities.World.Update()
    14.    at Unity.Tiny.UnityInstance.Update(Double timestampInSeconds)
    15.    at Unity.Tiny.EntryPoint.Program.<>c__DisplayClass0_0.<Main>b__0(Double timestampInSeconds)
    16.    at Unity.Platforms.RunLoopImpl.EnterMainLoop(RunLoopDelegate runLoopDelegate)
    17.    at Unity.Tiny.EntryPoint.Program.Main()
    Yes, my conversion code does that. It was obfuscated by my extension methods, so I've reduced this to the simplest possible example:
    Code (CSharp):
    1.  
    2.     public void Convert(Entity character, EntityManager dstManager, GameObjectConversionSystem conversionSystem) {
    3.  
    4.       // create state
    5.       Entity state = conversionSystem.CreateAdditionalEntity(this.gameObject);
    6.       dstManager.SetName(state, $"{this.gameObject.name}__state");
    7.  
    8.       // create machine
    9.       Entity machineModel = conversionSystem.CreateAdditionalEntity(this.gameObject);
    10.       dstManager.SetName(machineModel, $"{this.gameObject.name}__MachineModel");
    11.       dstManager.AddComponent<MachineModel>(machineModel);
    12.  
    13.       // insert state into machine
    14.       DynamicBuffer<VertexInGraph> vertices = dstManager.AddBuffer<VertexInGraph>(machineModel);
    15.       vertices.Add(new VertexInGraph() { Value = state });
    16.       DynamicBuffer<StateInGraph> states = dstManager.AddBuffer<StateInGraph>(machineModel);
    17.       states.Add(new StateInGraph() { VertexIndex = 0 });
    18.     }
    And despite manually adding them, this system still prints that the lengths are both 0.

    Code (CSharp):
    1.   [UpdateInGroup(typeof(SimulationSystemGroup)), AlwaysUpdateSystem]
    2.   public class DebugAutomata : ComponentSystem {
    3.     override protected void OnUpdate() {
    4.  
    5.       UnityEngine.Debug.Log("MachineModel");
    6.       Entities.ForEach(
    7.         (Entity e, ref MachineModel machineModel, DynamicBuffer<Unity.Graphs.VertexInGraph> vertices, DynamicBuffer<Unity.Automata.Model.StateInGraph> states) => {
    8.           UnityEngine.Debug.Log($"  machineModel {e}, vertex {vertices.Length}, states {states.Length}");
    9.         }
    10.       );
    11.     }
    12.   }
    The above code runs fine in the editor. It prints out:
    machineModel Entity(6:1), vertex 1, states 1

    But it does not work in the DOTS runtime. It prints out:
    machineModel Entity(30:1), vertex 0, states 0

    I'm going to try and delete my library folder and try again.
     
  4. SINePrime

    SINePrime

    Joined:
    Jan 24, 2019
    Posts:
    54
    Alright, I figured it out. Many of my Components have static variables. For example, here's my MachineModel class:

    Code (CSharp):
    1.   public struct MachineModel : IComponentData {
    2.     /// <summary>
    3.     /// Default archetype.
    4.     /// </summary>
    5.     static public readonly ComponentType[] Archetype = new ComponentType[]{
    6.       typeof(Graphs.Graph),
    7.       typeof(Graphs.VertexInGraph),
    8.       typeof(Graphs.EdgeInGraph),
    9.       typeof(Automata.Model.MachineModel),
    10.       typeof(Automata.Model.StateInGraph),
    11.       typeof(Automata.Model.InstructionInGraph),
    12.       typeof(Automata.Model.TransitionInGraph)
    13.     };
    14.   }
    15.  
    These static member causes any DynamicBuffer that's attached to the same entity as the Component to have a length of 0, but only in the DOTS Runtime.

    This seems to go against what the docs here would suggest:
    https://docs.unity3d.com/Packages/com.unity.entities@0.17/manual/gp_common_patterns.html
     
unityunity