ECS & DynamicBuffer

Hello,

I’m currently trying to make a virtual greenhouse using hybrid ECS.

I have a PlantColumn IComponentData which contains an int, NativeString512 and a DynamicBuffer.

using System;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;

[System.Serializable]
public struct PlantColumn : IComponentData
{
    public int id;
    public NativeString512 plantName;

    public DynamicBuffer<Property> properties;
}

Property is another IComponentData which contains a NativeString512, int, float and a bool.

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

public struct Property : IComponentData
{
    public NativeString512 header;

    public int intValue;
    public float floatValue;
    public bool boolValue;
    public NativeString512 stringValue;

    public void AssignValue(string _header, System.Object value)
    {
        if (value.GetType() == typeof(int))
        {
            intValue = int.Parse(value.ToString());
            header = new NativeString512(value.ToString());
        }
        else if (value.GetType() == typeof(float))
        {
            floatValue = float.Parse(value.ToString());
            header = new NativeString512(value.ToString());
        }
        else if (value.GetType() == typeof(string))
        {
            stringValue = new NativeString512(value.ToString());
            header = new NativeString512(value.ToString());
        }
        else if (value.GetType() == typeof(bool))
        {
            bool.Parse(value.ToString());
            header = new NativeString512(value.ToString());
        }
        else
            Debug.LogError("Value type not valid or couldn't be parsed to a valid type. Valid value types are: int, float, string, bool");
    }
}

This is the temporary test script I use to create the entities and create a placeholder dataset with only a ‘height’ property.

using UnityEngine;
using Unity.Entities;
using Unity.Collections;
using UnityEngine.UIElements;
using Unity.Transforms;
using Unity.Rendering;
using Unity.Mathematics;
using System.Collections.Generic;
public class GreenhouseBootstrap : MonoBehaviour
{

    [SerializeField] private Mesh mesh;
    [SerializeField] private Material mat;

    [SerializeField] private GameObject prefab;

    List<Plant> testPlantDataset;

    void Start()
    {
        testPlantDataset = new List<Plant>();

        for (int i = 0; i < 20; i++)
        {
            Plant plant = new Plant();
            plant.properties = new Dictionary<string, object>();

            plant.plantName = "Plant00" + i;
            plant.properties.Add("height", i * UnityEngine.Random.Range(20f, 40f));

            testPlantDataset.Add(plant);
        }

        InstantiateFilteredDataset(20);
    }
  
    public void InstantiateFilteredDataset(int amount)
    {
        //Get the EntityManager
        EntityManager manager = World.Active.EntityManager;

        //Instantiate the PlantColumns prefab and convert them to entities
        for (int i = 0; i < amount; i++)
        {
            GameObject obj = Instantiate(prefab, new Vector3(i + (i * 0.5f), 0, 0), Quaternion.identity);
            obj.AddComponent<ConvertToEntity>().ConversionMode = ConvertToEntity.Mode.ConvertAndDestroy;
        }

        //Construct an EntityQueryDescription that includes all entities that DON'T have a LocalToParent component on them
        EntityQueryDesc query = new EntityQueryDesc
        {
            None = new ComponentType[] {typeof(LocalToParent)}
        };

        //Create the EntityQuery using the EntityManager
        EntityQuery entityQuery = manager.CreateEntityQuery(query);

        //Convert the EntityQuery to a NativeArray<Entity> so we can loop through all the found entities
        NativeArray<Entity> entityArray = entityQuery.ToEntityArray(Allocator.TempJob);

        //Loop through all the found entities and add a PlantColum component on them
        for (int i = 0; i < entityArray.Length; i++)
        {
            manager.AddComponentData(entityArray[i], new PlantColumn { id = entityArray[i].Index });
            DynamicBuffer<Property> tempPropertyBuffer = manager.GetComponentData<PlantColumn>(entityArray[i]).properties;

            foreach (KeyValuePair<string, System.Object> kvp in testPlantDataset[i].properties)
            {
                Property prop = new Property();

                string propertyName = kvp.Key;
                System.Object propertyValue = kvp.Value;

                prop.AssignValue(propertyName, propertyValue);

                tempPropertyBuffer.Add(prop);
            }
        }

        //Dispose the NativeArray<Entity> to prevent memory leaks
        entityArray.Dispose();
    }

The error occurs at line 76

The reason I’m using an object is because a property can be more than just one data type.

The problem is that whenever I create my plant entities and add a PlantColumn ComponentData on them and then try to add a Property using entity.properties.Add(new Property()) I get a NullReferenceException.

The error points to the DynamicBuffer class. Something with AtomicSafetyHandle.CheckWriteAndThrow().

I was wondering if a structure like this is even possible.

Thanks in advance!

I don’t think you’re supposed to use DynamicBuffer inside of a component like that in proper ECS. At least that is not the way it was intended to be used. Honestly I’m surprised that what you have even compiles. I suppose it does because you’re not using jobs or burst.

Everything inside a component must be “blittable” - ie it must have a fixed size that never changes. I’m not familiar with NativeString512, but going by the fact that it has 512 in the name, I suspect it is blittable. DynamicBuffer is not.

One option is to move the DynamicBuffer outside of the PlantColumn component like the examples here: https://docs.unity3d.com/Packages/com.unity.entities@0.17/manual/dynamic_buffers.html

Another option is to use a blittable array type like the ones in Unity.Collections such as FixedList4096 properties; .

1 Like

Just a FYI: The dedicated DOTS sub-forums are here.

1 Like