DOTS/ECS ISystem - DynamicBuffer referenceable data

Made a ECS reference script using DynamicBuffers (has more then 1 value type). I need to innitialize the data for the “CreateDropPile” to function.

I’ve tried to run the “OnStartRunning” inside the function referencing the “World” SystemState, creating a static instance of the reference data, and some other stuff.

Any ideas?

Code:

DropPileSystem

using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using Random = Unity.Mathematics.Random;
public partial struct DropPileSystem : ISystem
{
    public EntityManager entityManager;
    public EntityCommandBuffer entityCommandBuffer;

    public DynamicBuffer<DropPileDynamicBuffer> dropPileDynamicBuffer;
    public Entity dropPileReference;
    public Entity holdablePileReference;
    public DynamicBuffer<HoldableItemDynamicBuffer> holdableItemDynamicBuffer;
    public void OnStartRunning(ref SystemState state)
    {
        entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
        entityCommandBuffer = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>().CreateCommandBuffer(state.WorldUnmanaged);

        dropPileReference = SystemAPI.GetSingletonEntity<DropPileReferences>();
        holdablePileReference = SystemAPI.GetSingletonEntity<HoldableItemReferences>();
        holdableItemDynamicBuffer = SystemAPI.GetBuffer<HoldableItemDynamicBuffer>(holdablePileReference);
        dropPileDynamicBuffer = SystemAPI.GetBuffer<DropPileDynamicBuffer>(dropPileReference);

        UnityEngine.Debug.Log(dropPileDynamicBuffer.Length);

    }
    public void CreateDropPile(Entity holdableItemType)
    {
        UnityEngine.Debug.Log(dropPileDynamicBuffer.Length);


        DropPileDynamicBuffer dropPileTemplate = GetDropPileTemplate(holdableItemType);
        UnityEngine.Debug.Log(dropPileTemplate);

        Entity newDropPile = entityManager.Instantiate(dropPileTemplate.dropPile);

        entityCommandBuffer.SetComponent(newDropPile, new DropPile
        {
            dropPileItemEntity = holdableItemType,
            dropPileItemQuantity = 1,
        });
    }

HoldableItemReference

using UnityEngine;
using Unity.Entities;
using Unity.Mathematics;
using System.Collections.Generic;
using static HoldableItemCategorySO;
using Random = Unity.Mathematics.Random;

public class HoldableItemReferencesAuthoring : MonoBehaviour
{
    [SerializeField] private List<HoldableItemSO> holdableItemSOList;

    public class Baker : Baker<HoldableItemReferencesAuthoring>
    {

        public override void Bake(HoldableItemReferencesAuthoring authoring)
        {
            Entity entity = GetEntity(TransformUsageFlags.Dynamic);
            DynamicBuffer<HoldableItemDynamicBuffer> holdableItemDataBuffer = AddBuffer<HoldableItemDynamicBuffer>(entity);

            for (int i = 0; i < authoring.holdableItemSOList.Count; i++)
            {
                holdableItemDataBuffer.Add(new HoldableItemDynamicBuffer
                {
                    itemEntity = GetEntity(authoring.holdableItemSOList[i].gameWorldPrefab, TransformUsageFlags.Dynamic),
                    itemID = i,
                    inventoryQuantityMax = authoring.holdableItemSOList[i].holdingAmountMax,
                    inventorySpacePointsCost = authoring.holdableItemSOList[i].inventorySpacePointsCost,
                    stackCategory = authoring.holdableItemSOList[i].stackCategory,
                    itemCategory = authoring.holdableItemSOList[i].itemCategory,
                    handheldOffset = authoring.holdableItemSOList[i].handheldOffset,
                });
            }
            AddComponent(entity, new HoldableItemReferences());
        }
    }
}
public struct HoldableItemReferences : IComponentData
{
}
public struct HoldableItemDynamicBuffer: IBufferElementData
{
    public Entity itemEntity;
    public int itemID;
    public int inventoryQuantityMax;
    public float inventorySpacePointsCost;
    public StackCategory stackCategory;
    public ItemCategory itemCategory;
    public float3 handheldOffset;
    public Random randomSeed;
}

Input Command System:

            if (inputData.ValueRO.isInteracting)
            {
                DropPileSystem dropPileSystem = state.WorldUnmanaged.GetUnsafeSystemRef<DropPileSystem>(state.SystemHandle);
                dropPileSystem.CreateDropPile(holdableItemDynamicBuffer[1].itemEntity);
            }```

Your problems probably come from the fact that youre chaching a dynamicbuffer. A dynamic buffer is a pointer to Inchunk saved data so any structual change (adding/removing components, modifying sharedcomponent values) invalidates the dynamic buffer and it has to be reaquired.

Simply call SystemApi.GetBuffer again in your OnUpdate method (or where ever)

Also i would advice against storing general data in system since it goes against the ecs paradigm. Specificly the entitymanager is available through the SystemState variable.

1 Like

Noticed that in the “state.” options, didn’t know why. Thanks for clarifying, will do for now on.

I tried and also created an IJobEntity so that everything can be executed from the OnUpdate(). It seems that the data stored in the DynamicBuffer during baking is not stored stably.

Maybe there is something other than a BlobBuilderArray that I can use to replace the DynamicBuffer.

It seems that the data stored in the DynamicBuffer during baking is not stored stably.

Unlikely, or at least fixable. Have you checked the state of the HoldableItemReferences-Entity and its BufferComponent via the “Entities Hirarchy” (both during authoring and runtime).

Also could you share the code of your Systems OnUpdate and IJobEntity.

IJobEntities:

    [BurstCompile]
    public partial struct GetPileDynamicBufferEventJob : IJobEntity
    {
        [ReadOnly] public EntityManager entityManager;
        [ReadOnly] public EntityCommandBuffer entityCommandBuffer;

        [ReadOnly] public Entity dropPileReferences;
        [ReadOnly] public DynamicBuffer<DropPileDynamicBuffer> dropPileDynamicBufferLookup;
        //[ReadOnly] public BufferLookup<DropPileDynamicBuffer> dropPileDynamicBufferLookup;

        [ReadOnly] public Entity holdableItemReferences;
        [ReadOnly] public DynamicBuffer<HoldableItemDynamicBuffer> holdableItemDynamicBufferLookup;
        //[ReadOnly] public BufferLookup<HoldableItemDynamicBuffer> holdableItemDynamicBufferLookup;

        [ReadOnly] public Entity dropPileObjectType;

        public DropPileDynamicBuffer dropPile;
        public void Execute()
        {
            for (int i = 0; i < holdableItemDynamicBufferLookup.Length; i++)
            {
                if (holdableItemDynamicBufferLookup[i].itemEntity != dropPileObjectType) continue;

                switch (holdableItemDynamicBufferLookup[i].stackCategory)
                {
                    case StackCategory.Small:

                        foreach (DropPileDynamicBuffer dropPileTemplate in dropPileDynamicBufferLookup) if (dropPileTemplate.dropPileType != DropPileType.Small) continue;
                        dropPile = dropPileDynamicBufferLookup[i];
                        return;

                    case StackCategory.Medium:
                        foreach (DropPileDynamicBuffer dropPileTemplate in dropPileDynamicBufferLookup) if (dropPileTemplate.dropPileType != DropPileType.Medium) continue;
                        dropPile = dropPileDynamicBufferLookup[i];
                        return;

                    case StackCategory.Large:
                        foreach (DropPileDynamicBuffer dropPileTemplate in dropPileDynamicBufferLookup) if (dropPileTemplate.dropPileType != DropPileType.Large) continue;
                        dropPile = dropPileDynamicBufferLookup[i];
                        return;
                }
            }
        }
    [BurstCompile]
    public partial struct SpawnDropPileEventJob : IJobEntity
    {
        [ReadOnly] public EntityCommandBuffer entityCommandBuffer;
        [ReadOnly] public DropPileDynamicBuffer dropPileDynamicBuffer;
        [ReadOnly] public Entity dropPileObjectType;

        public Entity newDropPile;
        public void Execute()
        {
            Entity newDropPile = entityCommandBuffer.Instantiate(dropPileDynamicBuffer.dropPile);

            entityCommandBuffer.AddComponent(newDropPile, new DropPile
            {
                dropPileItemEntity = dropPileObjectType,
                dropPileItemQuantity = 1,
            });

            this.newDropPile = newDropPile;
        }
    }

Job Entity Scheduller:

            if (inputData.ValueRO.isInteracting)
            {
                dropPileDynamicBuffer = SystemAPI.GetBuffer<DropPileDynamicBuffer>(dropPileReferences);
                GetPileDynamicBufferEventJob getPileDynamicBufferEventJob = new GetPileDynamicBufferEventJob
                {
                    entityManager = state.EntityManager,
                    entityCommandBuffer = entityCommandBuffer,

                    dropPileReferences = dropPileReferences,
                    dropPileDynamicBufferLookup = dropPileDynamicBuffer,

                    holdableItemReferences = holdableItemReferences,
                    holdableItemDynamicBufferLookup = holdableItemDynamicBuffer,

                    dropPileObjectType = holdableItemDynamicBuffer[1].itemEntity,
                };

                getPileDynamicBufferEventJob.ScheduleParallel();

                SpawnDropPileEventJob spawnDropPileEventJob = new SpawnDropPileEventJob
                {
                    entityCommandBuffer = entityCommandBuffer,
                    dropPileDynamicBuffer = getPileDynamicBufferEventJob.dropPile,
                    dropPileObjectType = getPileDynamicBufferEventJob.dropPileObjectType,
                };

                spawnDropPileEventJob.ScheduleParallel();
            }

Entity Snapshots after baking:

“HoldableItems” can be dropped on the ground. Depending on what “Stack Category” they have, their drop limit will change → requiring different “Drop Piles” for the specific requirements.

Each “Drop Pile” holds a DynamicBuffer of the position and rotation their children (the dropped items) need to locate to.

I’m also introducing a random seed to randomize the Drop Pile so they don’t spawn the same exact one each time but that’s extra for now. Can remove it if necessary.

Lots to unpack here (at this point i dont exactly know what your perceived problem is).

I gather all you want is to do is instantiate a dropPile to represent a dropped item for characters to interact with in 3d.

If so you probably dont need to jobify that process (unless you spawn hundrets of objects every frame). Jobs are generally reserved for computationally intensive (lots of math or iterations) but simple tasks (few dependencies). Schedualling jobs actually has a pretty significant overhead that makes it slower than just running it on the main thread for stuff like this. Id recommend not trying to overoptimize your code before its necessary and learning the Ecs foundation first.

Heres a more straight forward approach


public partial struct DropItemSystem : ISystem
{
    void ISystem.OnUpdate(ref SystemState state)
    {
        //Get an entityCommandBuffer
        var ecbSystem = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();
        var ecb = ecbSystem.CreateCommandBuffer(state.WorldUnmanaged);


        //This needs to change to however you aquire your itemToDrop
        HoldableItemDynamicBuffer holdableItemToDrop = default;

        //Fetch the appropriate dropPileTemplate
        var dropPileBuffer = SystemAPI.GetSingletonBuffer<DropPileDynamicBuffer>(true);

        var dropPileTemplate = holdableItemToDrop.stackCategory switch
        {
            StackCategory.Small => dropPileBuffer[0],
            StackCategory.Medium => dropPileBuffer[1],
            StackCategory.Large => dropPileBuffer[2],
            _ => throw new System.NotImplementedException(),
        };


        //Create a dropPile instance
        var dropPileEntity = ecb.Instantiate(dropPileTemplate.dropPile);
        ecb.AddComponent(dropPileEntity, new DropPile()
        {
            dropPileItemEntity = dropPileTemplate.dropPileType,
            dropPileItemQuantity = 1,
        });
    }
}

Note this is more or less pseudo code and needs to be adapted to your data structure.

Now im gonna get into more specific code problems in your implementation (again i dont recommend spending to much time trying to jobify everything).

Youre using IJobEntity wrong. IJobEntity uses CodeGeneration to simplify the workflow of writing simple ECS jobs.


public partial struct FooJob : IJobEntity
{
    public void Execute(ref FooComponent component, Entity entity)
    {
        //Do work
    }
}

This will execute on all entities with the Components specified in the Execute method (FooComponent)
What youre doing is essentially just writing what a IJobEntity produces under the hood (minus the other issues in your code). Simply use an IJob instead or properly use the IJobEntity interface.

Dont ever use the EntityManager inside a job. Either copy the data into the job or ComponentLookups/BufferLookups.

Dont use an EntityCommandBuffer in multithreaded jobs instead use a EntityCommandBuffer.ParallelWriter

I dont think this heres correct (GetPileDynamicBufferEventJob line 28, 33, 38)

foreach (DropPileDynamicBuffer dropPileTemplate in dropPileDynamicBufferLookup) if (dropPileTemplate.dropPileType != DropPileType.Small) continue;
                        dropPile = dropPileDynamicBufferLookup[i];

and shoud read

foreach (DropPileDynamicBuffer dropPileTemplate in dropPileDynamicBufferLookup) 
{
     if (dropPileTemplate.dropPileType != DropPileType.Small) 
          continue;
     dropPile = dropPileTemplate ;
}

In SpawnDropPileEventJob line 19 youre passing back an entity created by and EntityCommandBuffer.
Entities that are created by an ECB are only valid to use with said ECB. Dont use them with an EntityManger, other ECBs or cache them for later use. If you need to get the entities created by an ECB you need to give them a temporary Component that you can query for after playing back the ECB.

In your JobEntityScheduler line 24 you try the receive the dropPile from the previous job for use in hte spawn job. However, the previous job hasnt run yet since you schedualled it. What you essentially did is pass an uninitialized dropPile to your spawnJob. If you need to run jobs synchronously (which defeats the point) use job.Run().

Dont use directly use a DynamicBuffer in a job. Like i statet before, they get invalidated by structual change (which can happen whilst a schedualled job is running). Instead paste the data into a nativeArray, use a BufferLookup, or an IJobEntity with

public Void Execute(ref DynamicBuffer<Foo> buffer)

Schedualling jobs required the dependancy of other jobs who access the same data. I dont know how much the CodeGeneration takes care of that, just now it can lead to a lot of errors.

This is how you pass a systems dependencies to a job for schedualling.

new FooJob().ScheduleParallel(state.Dependency);

Thanks! I’ll look into all of it.

But you’re right, the code is a little bit of a mess at this point because I’ve been trying out different approaches.

at this point i dont exactly know what your perceived problem is

Basically this: (I think)

  1. Dont use directly use a DynamicBuffer in a job. Like i statet before, they get invalidated by structual change (which can happen whilst a schedualled job is running). Instead paste the data into a nativeArray, use a BufferLookup, or an IJobEntity with

The data becomes non existent where I try to access it. Thanks again.

Okay so with the info from LeFlop2001, I was able to make the following caching script: (it works)

using Unity.Entities;
using Unity.Jobs;
using Unity.Burst;
using Unity.Collections;

public partial struct DynamicBufferCaching : ISystem, ISystemStartStop
{
    public static DynamicBufferCaching Instance;

    public NativeArray<HoldableItemDynamicBuffer> holdableItemNativeArray;
    public NativeArray<DropPileDynamicBuffer> dropPileNativeArray;
    public void OnStartRunning(ref SystemState state)
    {
        foreach (DynamicBuffer<HoldableItemDynamicBuffer> holdableItemDynamicBuffer in SystemAPI.Query<DynamicBuffer<HoldableItemDynamicBuffer>>())
        {
            holdableItemNativeArray = new NativeArray<HoldableItemDynamicBuffer>(holdableItemDynamicBuffer.Length, Allocator.Persistent);
            
            for (int i = 0; i < holdableItemDynamicBuffer.Length; i++)
            {
                holdableItemNativeArray[i] = new HoldableItemDynamicBuffer
                {
                    handheldOffset = holdableItemDynamicBuffer[i].handheldOffset,
                    inventoryQuantityMax = holdableItemDynamicBuffer[i].inventoryQuantityMax,
                    inventorySpacePointsCost = holdableItemDynamicBuffer[i].inventorySpacePointsCost,
                    itemCategory = holdableItemDynamicBuffer[i].itemCategory,
                    itemEntity = holdableItemDynamicBuffer[i].itemEntity,
                    itemID = holdableItemDynamicBuffer[i].itemID,
                    randomSeed = holdableItemDynamicBuffer[i].randomSeed,
                    stackCategory = holdableItemDynamicBuffer[i].stackCategory,
                };
            }
        }
        foreach (DynamicBuffer<DropPileDynamicBuffer> dropPileDynamicBuffer in SystemAPI.Query<DynamicBuffer<DropPileDynamicBuffer>>())
        {
            dropPileNativeArray = new NativeArray<DropPileDynamicBuffer>(dropPileDynamicBuffer.Length, Allocator.Persistent);

            for (int i = 0; i < dropPileDynamicBuffer.Length; i++)
            {
                dropPileNativeArray[i] = new DropPileDynamicBuffer
                {
                    dropPile = dropPileDynamicBuffer[i].dropPile,
                    dropPileType = dropPileDynamicBuffer[i].dropPileType,
                };
            }
        }

        Instance = this;
    }
    public void OnStopRunning(ref SystemState state)
    {
    }
    public void OnDestroy(ref SystemState state)
    {
        holdableItemNativeArray.Dispose();
        dropPileNativeArray.Dispose();
    }
    public NativeArray<HoldableItemDynamicBuffer> GetHoldableItemNativeArray()
    {
        return holdableItemNativeArray;
    }
    public NativeArray<DropPileDynamicBuffer> GetDropPileNativeArray()
    {
        return dropPileNativeArray;
    }
}

Can’t make into a job because types like NativeArrays aren’t compatible with Jobs or IComponentData. If you spot any memory leaks let me know.

One of the authoring reference scripts for convenience:

using UnityEngine;
using Unity.Entities;
using Unity.Mathematics;
using System.Collections.Generic;
using static HoldableItemCategorySO;
using Random = Unity.Mathematics.Random;
using Unity.Collections;

public class HoldableItemReferencesAuthoring : MonoBehaviour
{
    [SerializeField] private List<HoldableItemSO> holdableItemSOList;

    public class Baker : Baker<HoldableItemReferencesAuthoring>
    {

        public override void Bake(HoldableItemReferencesAuthoring authoring)
        {
            Entity entity = GetEntity(TransformUsageFlags.Dynamic);
            DynamicBuffer<HoldableItemDynamicBuffer> holdableItemDataBuffer = AddBuffer<HoldableItemDynamicBuffer>(entity);

            for (int i = 0; i < authoring.holdableItemSOList.Count; i++)
            {
                holdableItemDataBuffer.Add(new HoldableItemDynamicBuffer
                {
                    itemEntity = GetEntity(authoring.holdableItemSOList[i].gameWorldPrefab, TransformUsageFlags.Dynamic),
                    itemID = i,
                    inventoryQuantityMax = authoring.holdableItemSOList[i].holdingAmountMax,
                    inventorySpacePointsCost = authoring.holdableItemSOList[i].inventorySpacePointsCost,
                    stackCategory = authoring.holdableItemSOList[i].stackCategory,
                    itemCategory = authoring.holdableItemSOList[i].itemCategory,
                    handheldOffset = authoring.holdableItemSOList[i].handheldOffset,
                });
            }
            AddComponent(entity, new HoldableItemReferences());
        }
    }
}
public struct HoldableItemReferences : IComponentData
{
}
public struct HoldableItemDynamicBuffer: IBufferElementData
{
    public Entity itemEntity;
    public int itemID;
    public int inventoryQuantityMax;
    public float inventorySpacePointsCost;
    public StackCategory stackCategory;
    public ItemCategory itemCategory;
    public float3 handheldOffset;
    public Random randomSeed;
}