Initialize DynamicBuffer when Creating Entity

I have a byte[ ] that represents some serialized binary data. I’m trying to associate entities based on a SqlHierarchyID approach. SqlHierarchyId Struct (Microsoft.SqlServer.Types) | Microsoft Learn

From what I can find about using byte[ ]'s, it looks like DynamicBuffer is intended to handle things like this. I’m setting this value when I create my entities, outside of jobs. I’m having trouble figuring out how to initialize the buffer when I create my entities. The docs show something like this to define the buffer.

    [InternalBufferCapacity(8)]
    public struct HierarchyElement : IBufferElementData
    {
        public byte Value;
        public static implicit operator byte(HierarchyElement e)
        {
            return e.Value;
        }

        public static implicit operator HierarchyElement(byte e)
        {
            return new HierarchyElement {Value = e};
        }
    }

I can add it to my archetype and it shows on my entity, but there is no “SetBufferData” on EntityManager. The examples I’ve found show using it inside a job, and that works. But I want to populate it when/before I add it. Also, it seems that it is designed to support working on individual elements, and doesn’t support just loading it with an array.

I don’t know if I’m going down the wrong path here. What I need to do is the equivalent of

byte[] hid = SqlHierarchyId.Parse("/1/1/2/").Serialize();
var u = EntityManager.CreateEntity( typeof(HierarchyBuffer));
EntityManager.SetBufferData(u, new HierarchyBuffer(hid));
1 Like

You use em.GetBuffer then you will get a native container linked to those buffer memory. Meaning that modification to it will really do it to the real database. There is no command to set back.

The ECB one’s “set” meaning is to queue a set command. This command return an empty buffer that you can set the content, so it completely replace the real one when it play back. (So ECB set buffer cannot be used to add more to existing buffer, it would always start over.

Maybe they thought if they use the same “get” wording then it wouldn’t make sense since the buffer does not exist yet at that time, but I also think set is equally confusing.

The implicit operator just help you throw in a byte into a buffer and it get converted into a struct type wrapped variable. Because it maybe common you just want a lot of bytes per entity but because ECS ties to typing system you have to make a type to wrap just one byte.

To add managed array you can use NativeArray constructor that copies from managed array then use .AddRange on the buffer.

1 Like

Thanks @5argon . You got me to a working approach. I’m adding the values in a loop instead of using AddRange.
// Buffer

        public struct HierarchyFacet : IBufferElementData {
            public byte HierarchyByte;

            public static implicit operator byte(HierarchyFacet v) {
                return v.HierarchyByte;
            }

            public static implicit operator HierarchyFacet(byte v) {
                return new HierarchyFacet { HierarchyByte = v };
            }
        }

// Code

            var em = World.DefaultGameObjectInjectionWorld.EntityManager;
            var foo = em.CreateEntity(typeof(HierarchyFacet));
            // typeof(SqlBytes) - Array of SqlByte
            var hidSqlBytes = SqlHierarchyId.Parse("/1/2/1/").Serialize();
            // byte[]
            var bytes = hidSqlBytes.Buffer;
            var bufferContainer = em.GetBuffer<HierarchyFacet>(foo);
            foreach (var b in bytes) {
                bufferContainer.Add(new HierarchyFacet() { HierarchyByte = b });
            }

Fills up the EntityDebugger with a lot of lines lol. But I’m able to build full hierarchies of entities.

Also you can use it without loop. But it will make sense on big arrays only, because on small arrays difference not really matter.

                        var em = World.DefaultGameObjectInjectionWorld.EntityManager;
                        var foo = em.CreateEntity(typeof(HierarchyFacet));
                        // typeof(SqlBytes) - Array of SqlByte
                        var hidSqlBytes = SqlHierarchyId.Parse("/1/2/1/").Serialize();
                        // byte[]
                        var nativeBytes = new NativeArray<byte>(hidSqlBytes.Buffer.Length, Allocator.Temp);
                        nativeBytes.CopyFrom(hidSqlBytes.Buffer);
                        var bufferContainer = em.GetBuffer<HierarchyFacet>(foo);
                        UnsafeUtility.MemCpy(bufferContainer.GetUnsafePtr(),
                            nativeBytes.GetUnsafeReadOnlyPtr(),
                            hidSqlBytes.Buffer.Length * UnsafeUtility.SizeOf<byte>());

It pin managed array and doing memcpy under hood inside CopyFrom and UnsafeUtility.MemCpy which should be faster than loop (but scale - matter, on small amount of data it wouldn’t be faster).