Dynamic buffers getting deallocated when adding multiple?

Im trying to allocate multiple dynamic buffers and associate them with an entity. I would then like to use those buffers in a job, but only the last buffer created is not deallocated. Scheduling the job yields the following error:
InvalidOperationException: The NativeContainer Job.Buffer0 has been deallocated. All containers must be valid when scheduling a job.

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

public class System : ComponentSystem
{
    protected override void OnUpdate()
    {
        var e = EntityManager.CreateEntity();
        var b0 = EntityManager.AddBuffer<MyInt0>(e);
        var b1 = EntityManager.AddBuffer<MyInt1>(e);

        var job = new Job
        {
            Buffer0 = b0,
            Buffer1 = b1
        };
      
        job.Schedule();
    }
}

[BurstCompile]
struct Job : IJob
{
    public DynamicBuffer<MyInt0> Buffer0;
    public DynamicBuffer<MyInt1> Buffer1;
  
    public void Execute()
    {
    }
}

[InternalBufferCapacity(32)]
struct MyInt0 : IBufferElementData
{
    public int Value;
}

[InternalBufferCapacity(32)]
struct MyInt1 : IBufferElementData
{
    public int Value;
}

The second AddBuffer causes a structural change and thus the references to the buffer are invalid.
Call AddBuffer first, then GetBuffer on both.

Also resolving the DynamicBuffer from an Entity refernece on the job as opposed to on main thread is in most cases more flexible and what you want.

1 Like

Thanks. Im a bit puzzled by the InternalBufferCapacity attribute, can I not allocate buffers at an arbitrary size without resizing? Is there a best practice if I know I need to resize, using [InternalBufferCapacity(0)] or something?

InternalBufferCapacity is the capacity that will be drawn from the chunk. It gurantees linear memory layout access from IJobForEach. Anything beyond that, results in Malloc / Free. Which is random memory access & allocation / deallocation cost.

For large allocations it is recommended to set [InternalBufferCapacity(0)].
And you can set the buffer capacity at runtime to whatever you like.

The use case is small common allocations. Eg. a weapon targeting system might often have up to 3-4 targets. In that case memory can be laid out well using the right capacity.

1 Like

Last version of DynamicBuffer I see has no shrink ability.
So once you go beyond InternalBufferCapacity you have Malloc and never Free afterwards until delete entity completely.

So we can not shrink capacity down into InternalBufferCapacity to get back to Linear Memory Access.

Why we have such behavior? Is it intended or it is a bug? May be it was very old version of DynamicBuffer?

Not being able to shrink is a bug.

TrimExcess() definately gets rid of all excesss space.

It’s strange that .Capacity has no setter. I’ll file a bug for that.

1 Like

It does.
But we still have dynamically allocated memory and dont use InternalBufferCapacity inside component.

So of we have InternalBufferCapacity(4) then after adding 5th element we malloc dynamic memory and no longer use internal component memory and never can go back. If we remove 1 or 2 elements we steel use same dynamic allocated memory and even after TrimExcess() we just get new dynamic allocated block for 3 elements ignoring internal buffer capacity of 4 elements.

Thanks @JesOb for bringing this up. I was looking for a fix too. TrimExcess() does shrink but still keep things on the heap (which can be the intended purpose of the method). Here’s my patch to DynamicBuffer that allows switching back to the internal capacity buffer:

// DynamicBuffer.cs
public void SwitchToInternalCapacityBuffer(int capacity) {
    byte* oldPtr = m_Buffer->Pointer;
    int length = m_Buffer->Length;
    if (oldPtr == null || length > capacity)
        return;

    m_Buffer->Capacity = capacity;
    m_Buffer->Pointer = null;
    byte* newPtr = BufferHeader.GetElementPointer(m_Buffer);
    int elemSize = UnsafeUtility.SizeOf<T>();
    UnsafeUtility.MemCpy(newPtr, oldPtr, (long)elemSize * length);
 
    UnsafeUtility.Free(oldPtr, Allocator.Persistent);
}

Test:

namespace Tests {

    public class DynamicBufferTests : ECSTestsFixture {

        [InternalBufferCapacity(8)]
        public struct TestElement : IBufferElementData {
            public static implicit operator int(TestElement e) { return e.value; }
            public static implicit operator TestElement(int e) { return new TestElement { value = e }; }

            public int value;
        }

        [Test]
        public unsafe void SwitchToInternalCapacityBuffer() {
            var entity = m_Manager.CreateEntity();
            var buffer = m_Manager.AddBuffer<TestElement>(entity);
            var originalPtr = buffer.GetUnsafePtr();
            for (int i = 0; i < 10; i++) {
                buffer.Add(1);
            }

            Assert.AreEqual(16, buffer.Capacity);
            Assert.AreNotEqual((long)originalPtr, (long)buffer.GetUnsafePtr());

            buffer.RemoveRange(0, 3);

            Assert.AreEqual(7, buffer.Length);
            Assert.AreEqual(16, buffer.Capacity);
            Assert.AreNotEqual((long)originalPtr, (long)buffer.GetUnsafePtr());

            buffer.SwitchToInternalCapacityBuffer(8);

            Assert.AreEqual(7, buffer.Length);
            Assert.AreEqual(8, buffer.Capacity);
            Assert.AreEqual(1, buffer[0].value);
            Assert.AreEqual((long)originalPtr, (long)buffer.GetUnsafePtr());
        }
    }

}

Im probably missing something, but dont I need the entitymanager to resolve the buffer, which I dont have access to in a job? I get this error from the code below: InvalidOperationException: Job.EntityManager is not a value type. Job structs may not contain any reference types.

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

public class System : ComponentSystem
{
    protected override void OnUpdate()
    {
        var e = EntityManager.CreateEntity();
        EntityManager.AddBuffer<MyInt>(e);
      
        var job = new Job
        {
            e0 = e,
            EntityManager = EntityManager
        };
      
        job.Schedule();
    }
}

//[BurstCompile]
struct Job : IJob
{
    public Entity e0;
    public EntityManager EntityManager;
  
    public void Execute()
    {
        var b0 = EntityManager.GetBuffer<MyInt>(e0);
    }
}

[InternalBufferCapacity(32)]
struct MyInt : IBufferElementData
{
    public int Value;
}

@Bas-Smit You can resolve the DynamicBuffer on the Job by using BufferFromEntity.

Something like this (Just edited your own code, so not tested, but you get the gist. Make sure to use JobComponentSystem):

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

public class System : JobComponentSystem
{
    protected override void OnUpdate()
    {
        var entity = EntityManager.CreateEntity(typeof(MyInt));
 
        var job = new Job
        {
            Entity = entity,
            MyIntBFE = GetBufferFromEntity<MyInt>(true)
        };
 
        job.Schedule();
    }
}

struct Job : IJob
{
    public Entity Entity;
    public BufferFromEntity<MyInt> MyIntBFE;
    public void Execute()
    {
        var myInts = MyIntBFE[e0];
    }
}

[InternalBufferCapacity(32)]
struct MyInt : IBufferElementData
{
    public int Value;
}
1 Like