#Passing Arrays of Data to Component in ECS Baking

1 messages · Page 1 of 1 (latest)

rare ermine
#

I'm trying to pass some data to a component through Baking and it keeps telling me that there may be a nested pointer and that this is a problem. I understand the underlying problem but I am unsure how to do what I'm trying to achieve. If you look in the code in the next comment you'll see that I try to have an assignable array of BoxColliders and through the data from those establish parameters with which I can have spawn zones by just defining 2 points of a rectangle to spawn within.

The Config is a singleton component that systems will read from to get these spawn zones.

#
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

namespace Game.Prototype
{
    public class ConfigAuthoring : MonoBehaviour
    {
        public float MonsterSpawnDistance = 30f;
        public int NumberOfSpawns = 1000;
        public Vector3 TentSpawnPoint;
        public GameObject TentPrefab;
        public GameObject MonsterCapsulePrefab;
        public BoxCollider[] SpawnZones;

        class Baker : Baker<ConfigAuthoring>
        {
            public override void Bake(ConfigAuthoring authoring)
            {
                var entity = GetEntity(TransformUsageFlags.None);
                SpawnZone[] spawnZones = new SpawnZone[authoring.SpawnZones.Length];
                for (int i = 0; i < authoring.SpawnZones.Length; i++)
                {
                    float minX = authoring.SpawnZones[i].bounds.min.x;
                    float minZ = authoring.SpawnZones[i].bounds.min.z;
                    float maxX = authoring.SpawnZones[i].bounds.max.x;
                    float maxZ = authoring.SpawnZones[i].bounds.max.z;

                    spawnZones[i] = new SpawnZone
                    {
                        Y = 0.75f,
                        StartCorner = new float2 { x = minX, y = minZ },
                        EndCorner = new float2 { x = maxX, y = maxZ }
                    };
                }
#
                AddComponent(entity, new Config()
                {
                    MonsterSpawnDistance = authoring.MonsterSpawnDistance,
                    NumberOfSpawns = authoring.NumberOfSpawns,
                    TentSpawnPoint = new float3
                    {
                        x = authoring.TentSpawnPoint.x,
                        y = authoring.TentSpawnPoint.y,
                        z = authoring.TentSpawnPoint.z
                    },
                    TentEntity = GetEntity(authoring.TentPrefab, TransformUsageFlags.Dynamic),
                    MonsterCapsuleEntity = GetEntity(authoring.MonsterCapsulePrefab, TransformUsageFlags.Dynamic),
                    SpawnZones = new NativeArray<SpawnZone>(spawnZones, Allocator.Persistent)
                });
            }
        }
    }

    public struct Config : IComponentData
    {
        public float MonsterSpawnDistance;
        public int NumberOfSpawns;
        public float3 TentSpawnPoint;
        public Entity TentEntity;
        public Entity MonsterCapsuleEntity;
        public NativeArray<SpawnZone> SpawnZones;
    }

    public struct SpawnZone : IComponentData
    {
        public float Y;
        public float2 StartCorner;
        public float2 EndCorner;
    }
}
grim depot
#

You can't bake native containers

#

Just use a DynamicBuffer

rare ermine
#

So instead of passing a NativeArray I have to pass a dynamic buffer?

grim depot
#

if you want to do it from baking yes

#

but you can always just call dynamicbuffer.AsArray()

#

(and probably should for performance)

#

if you have api that takes a nativearray

rare ermine
#

I see. But can I have standard arrays in an IComponentData type?

#

I don't know the implementation details of NativeArrays as such, other than higher level. So I was unsure.

grim depot
#

sure, icd is just an interface it doesn't stop you storing it anywhere

#

and if you write an icd from an entity into an array it'll just be a copy

rare ermine
#

DynamicBuffer<SpawnZone> spawnZones = new();

public struct Config : IComponentData
{
    public float MonsterSpawnDistance;
    public int NumberOfSpawns;
    public float3 TentSpawnPoint;
    public Entity TentEntity;
    public Entity MonsterCapsuleEntity;
    public DynamicBuffer<SpawnZone> SpawnZones;
}
grim depot
#

no

#

that's not how dynamic buffer works

rare ermine
#

Okay. How would I go about using them?

grim depot
#

var buffer = baker.AddBuffer<SpawnZone>();

#

foreach(var s in spawnZones )
buffer.add(s);

grim depot
#

beat me to it

rare ermine
#

Oh I see. I'll have a look at that. Thanks both of you.

rare ermine
#

Unless I'm just missing something

#

I can't get the EntityManager like they do here

grim depot
#

in baker it's just AddBuffer

#

you don't use entity manager in baker, that's for runtime

rare ermine
#

Ah. Right.

#

So I do this then, from what I understand:

DynamicBuffer<SpawnZoneBuffer> spawnZoneBuffers = AddBuffer<SpawnZoneBuffer>(entity);
for (int i = 0; i < authoring.SpawnZones.Length; i++)
{
    float minX = authoring.SpawnZones[i].bounds.min.x;
    float minZ = authoring.SpawnZones[i].bounds.min.z;
    float maxX = authoring.SpawnZones[i].bounds.max.x;
    float maxZ = authoring.SpawnZones[i].bounds.max.z;

    spawnZoneBuffers.Add(new SpawnZoneBuffer
    {
        Y = 0.75f,
        StartCorner = new float2 { x = minX, y = minZ },
        EndCorner = new float2 { x = maxX, y = maxZ }
    });
}
grim depot
#

looks good

rare ermine
#

But how would I get the buffer from my component later?
It's not assigned to anything in a field.
So would I do something like GetBuffer<MyBufferType> and it returns the buffer that I can iterate?

grim depot
#

it's not on your component

#

it's a separate component

rare ermine
#

Sorry, I meant on my entity

grim depot
#

the same way you get your component

#

except instead of getcomponent

#

you use getbuffer

#

(or getsingleton vs getsingletonbuffer etc)

rare ermine
#

What happens if you want multiple buffers of the same type on the same entity?
Is that not allowed then?

grim depot
#

of the same type? no

rare ermine
#

Alright. Good to know.

#

Thank you.

#

@grim depot and just for completeness sake, I made my buffer type like this:

[InternalBufferCapacity(20)]
public struct SpawnZoneBuffer : IBufferElementData
{
    public float Y;
    public float2 StartCorner;
    public float2 EndCorner;
}

Far as I understand the capacity was about the space for the types stored, and so if my math is correct that would be 5*4 bytes so 20.

grim depot
#

contrary to its name capacity, InternalBufferCapacity is number of elements so this would take 100 bytes

#
        /// <summary>
        /// The number of elements stored inside the chunk.
        /// </summary>
        public readonly int Capacity;```
#

i nearly always set this to 0 except on singletons

rare ermine
# grim depot contrary to its name capacity, InternalBufferCapacity is number of elements so t...

I was going by this explanation here from the documentation:

The initial capacity of a dynamic buffer is defined by the type that the buffer stores. By default, the capacity defaults to the number of elements that fit within 128 bytes. For more information, see DefaultBufferCapacityNumerator. You can specify a custom capacity using the InternalBufferCapacity attribute. For information on how to create a dynamic buffer component type, see Create a dynamic buffer component type.

grim depot
#

yeah the initial capacity is always 128

rare ermine
#

So I was under the impression that 5*4, due to 5 floats, would be right.

#

So how come it's 100?

grim depot
#

beacuse 20 elements * 5 [sizeof(SpawnZoneBuffer)] == 100

rare ermine
#

20 elements?

#

Where do you get that from?

grim depot
#

thats what 20 means

#

because you wrote 20 ^_^'

#

it's number of elements

#

not capacity

rare ermine
#

Aah

grim depot
#

even though it's called capacity

rare ermine
#

Right, sorry. That is..confusing.

grim depot
#

if you want proof

#
                var capacityAttribute = (InternalBufferCapacityAttribute)type.GetCustomAttribute(typeof(InternalBufferCapacityAttribute));
                if (capacityAttribute != null)
                    bufferCapacity = capacityAttribute.Capacity;
                else
                    bufferCapacity = DefaultBufferCapacityNumerator / elementSize; // Rather than 2*cachelinesize, to make it cross platform deterministic

                sizeInChunk = sizeof(BufferHeader) + bufferCapacity * elementSize;```
rare ermine
#

So you say setting it to 0 is what you typically do?

#

Regardless of number of elements?

grim depot
#

yeah i don't store in the chunk

#

i like to keep my archetype size as small as possible

rare ermine
#

Sure, smaller is better.

grim depot
#

to fit as many entities per chunk as i can

rare ermine
#

But that is quite a misleading name, perhaps. Or I don't know.
Odd.

grim depot
#

a chunk is 16kb

#

and can have 128 entities max

#

which gives them 128bytes per entity

rare ermine
#

Right

grim depot
#

to have max capacity in a chunk

#

at 100 bytes you've used up 80% of your per entity capacity if you wanted max entiites

#

now it's unlikely for most entities you'll ever get 128 in a chunk except for small utility entities you've broken off

#

but it's still beneficial to fit as much as possible

#

but if i'm a singleton entity, capacity no longer matters

rare ermine
#

It is a singleton, yes.

#

Readonly singleton buffer in this case I suppose

grim depot
#

if it's a singleton then yeah just fit as much as you need in the chunk

#

if you know an upper limit that isn't ridiculous

rare ermine
#

Following the Unity samples I've made a config object with some prefabs and data in it that I want systems to use for instantiation and informing decisions for spawnpoints and whatnot.

The Config is a singleton.

#

Every prefab of course has Authoring scripts to bake

grim depot
#

that's totally fine