Without access to Memory<T>
, ended up going with option (2), but no marshalling was necessary, only casting: use a fixed
array of bytes in an unsafe struct
and cast to/from these as follows:
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
public class TestStructWithFixed : MonoBehaviour
{
public const int MAX = 5;
public const int SIZEOF_ELEMENT = 8;
public struct Element
{
public uint x;
public uint y;
//8 bytes
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct Container
{
public int id; //4 bytes
public unsafe fixed byte bytes[MAX * SIZEOF_ELEMENT];
}
public Container container;
void Start ()
{
Debug.Log("SizeOf container="+Marshal.SizeOf(container));
Debug.Log("SizeOf element ="+Marshal.SizeOf(new Element()));
unsafe
{
Element* elements;
fixed (byte* bytes = container.bytes)
{
elements = (Element*) bytes;
//show zeroed bytes first...
for (int i = 0; i < MAX; i++)
Debug.Log("i="+i+":"+elements[i].x);
//low order bytes of Element.x are at 0, 8, 16, 24, 32 respectively for the 5 Elements
bytes[0 * SIZEOF_ELEMENT] = 4;
bytes[4 * SIZEOF_ELEMENT] = 7;
}
elements[2].x = 99;
//show modified bytes as part of Element...
for (int i = 0; i < MAX; i++)
Debug.Log("i="+i+":"+elements[i].x); //shows 4, 99, 7 at [0], [2], [4] respectively
}
}
}
unsafe
access is very fast, and with no marshalling or copies - is exactly what I wanted.
If likely to be using 4-byte int
s or float
s for all your struct
members, you might even do better to base your fixed
buffer off such a type (uint
is always a clean choice) - readily debuggable.
UPDATE 2021
I've revisited this topic this year, for prototyping in Unity 5 (due to fast compile / iteration times).
It can be easier to stick with one very large byte array, and use this in managed code, rather than bothering with fixed
+ unsafe
(by the way since C# 7.3 it is no longer necessary to use the fixed
keyword every time to pin a fixed-size buffer in order to access it).
With fixed
we lose type-safety; this being a natural shortcoming of interop data - whether interop between native and managed; CPU and GPU; or between Unity main thread code and that used for the new Burst / Jobs systems. The same applies for managed byte buffers.
Thus it can be easier to accept working with untyped managed buffers and writing offset + sizes yourself. fixed
/ unsafe
offers (a little) more convenience, but not by much, since you equally have to specify compile-time struct field offsets and change these each time the data design changes. At least with managed VLAs, I can sum offsets in code, however this does mean these are not compile-time constants, thus losing some optimisations.
The only real benefit of allocating a fixed
buffer this way vs. a managed VLA (in Unity), is that with the latter, there is a chance the GC will move your entire data model somewhere else in mid-play, which could cause hiccups, though I've yet to see how serious this is in production.
Managed arrays are not, however, directly supported by Burst.