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;
}
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.
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?
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:
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;
}