Add overloads for C# ByteBuffer/FlatBufferBuilder to allow adding vector blocks from ArraySegments or IntPtr (#7193)

* Add overloads to Add/Put for ArraySegment and IntPtr

In order to allow using code to reduce memory allocations, add overloads to ByteBuffer's and FlatBuffersBuilder's Put/Add methods that take ArraySegment<T> or IntPtr respectively.
Also, adaptions to the c# code generator in flatc to emit corresponding CreateVectorBlock() overloads

* Add missing files generated with generate_code.py

The previous commit changed the C# code generate, but didn't contain the updated generated test files.

* Incorporate review findings

(1) Adhere to 80 characters limit.
(2) In FlatBufferBuilder.Add(IntPtr,int), move zero length check topmost and add sanity check against negative input
This commit is contained in:
Jamie-Jameson
2022-04-01 22:35:07 +02:00
committed by GitHub
parent 26c3b3adab
commit c9651b7420
11 changed files with 536 additions and 14 deletions

View File

@@ -225,6 +225,18 @@ namespace FlatBuffers
return SizeOf<T>() * x.Length;
}
/// <summary>
/// Get the wire-size (in bytes) of an typed array segment, taking only the
/// range specified by <paramref name="x"/> into account.
/// </summary>
/// <typeparam name="T">The type of the array</typeparam>
/// <param name="x">The array segment to get the size of</param>
/// <returns>The number of bytes the array segment takes on wire</returns>
public static int ArraySize<T>(ArraySegment<T> x)
{
return SizeOf<T>() * x.Count;
}
#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
public static int ArraySize<T>(Span<T> x)
{
@@ -869,7 +881,30 @@ namespace FlatBuffers
throw new ArgumentNullException("Cannot put a null array");
}
if (x.Length == 0)
return Put(offset, new ArraySegment<T>(x));
}
/// <summary>
/// Copies an array segment of type T into this buffer, ending at the
/// given offset into this buffer. The starting offset is calculated
/// based on the count of the array segment and is the value returned.
/// </summary>
/// <typeparam name="T">The type of the input data (must be a struct)
/// </typeparam>
/// <param name="offset">The offset into this buffer where the copy
/// will end</param>
/// <param name="x">The array segment to copy data from</param>
/// <returns>The 'start' location of this buffer now, after the copy
/// completed</returns>
public int Put<T>(int offset, ArraySegment<T> x)
where T : struct
{
if (x.Equals(default(ArraySegment<T>)))
{
throw new ArgumentNullException("Cannot put a uninitialized array segment");
}
if (x.Count == 0)
{
throw new ArgumentException("Cannot put an empty array");
}
@@ -889,7 +924,68 @@ namespace FlatBuffers
#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
MemoryMarshal.Cast<T, byte>(x).CopyTo(_buffer.Span.Slice(offset, numBytes));
#else
Buffer.BlockCopy(x, 0, _buffer.Buffer, offset, numBytes);
var srcOffset = ByteBuffer.SizeOf<T>() * x.Offset;
Buffer.BlockCopy(x.Array, srcOffset, _buffer.Buffer, offset, numBytes);
#endif
}
else
{
throw new NotImplementedException("Big Endian Support not implemented yet " +
"for putting typed arrays");
// if we are BE, we have to swap each element by itself
//for(int i = x.Length - 1; i >= 0; i--)
//{
// todo: low priority, but need to genericize the Put<T>() functions
//}
}
return offset;
}
/// <summary>
/// Copies an array segment of type T into this buffer, ending at the
/// given offset into this buffer. The starting offset is calculated
/// based on the count of the array segment and is the value returned.
/// </summary>
/// <typeparam name="T">The type of the input data (must be a struct)
/// </typeparam>
/// <param name="offset">The offset into this buffer where the copy
/// will end</param>
/// <param name="ptr">The pointer to copy data from</param>
/// <param name="sizeInBytes">The number of bytes to copy</param>
/// <returns>The 'start' location of this buffer now, after the copy
/// completed</returns>
public int Put<T>(int offset, IntPtr ptr, int sizeInBytes)
where T : struct
{
if (ptr == IntPtr.Zero)
{
throw new ArgumentNullException("Cannot add a null pointer");
}
if(sizeInBytes <= 0)
{
throw new ArgumentException("Cannot put an empty array");
}
if (!IsSupportedType<T>())
{
throw new ArgumentException("Cannot put an array of type "
+ typeof(T) + " into this buffer");
}
if (BitConverter.IsLittleEndian)
{
offset -= sizeInBytes;
AssertOffsetAndLength(offset, sizeInBytes);
// if we are LE, just do a block copy
#if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER
unsafe
{
var span = new Span<byte>(ptr.ToPointer(), sizeInBytes);
span.CopyTo(_buffer.Span.Slice(offset, sizeInBytes));
}
#else
Marshal.Copy(ptr, _buffer.Buffer, offset, sizeInBytes);
#endif
}
else

View File

@@ -210,6 +210,31 @@ namespace FlatBuffers
_space = _bb.Put(_space, x);
}
/// <summary>
/// Puts an array of type T into this builder at the
/// current offset
/// </summary>
/// <typeparam name="T">The type of the input data </typeparam>
/// <param name="x">The array segment to copy data from</param>
public void Put<T>(ArraySegment<T> x)
where T : struct
{
_space = _bb.Put(_space, x);
}
/// <summary>
/// Puts data of type T into this builder at the
/// current offset
/// </summary>
/// <typeparam name="T">The type of the input data </typeparam>
/// <param name="ptr">The pointer to copy data from</param>
/// <param name="sizeInBytes">The length of the data in bytes</param>
public void Put<T>(IntPtr ptr, int sizeInBytes)
where T : struct
{
_space = _bb.Put<T>(_space, ptr, sizeInBytes);
}
#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
/// <summary>
/// Puts a span of type T into this builder at the
@@ -297,13 +322,24 @@ namespace FlatBuffers
/// <param name="x">The array to copy data from</param>
public void Add<T>(T[] x)
where T : struct
{
Add(new ArraySegment<T>(x));
}
/// <summary>
/// Add an array of type T to the buffer (aligns the data and grows if necessary).
/// </summary>
/// <typeparam name="T">The type of the input data</typeparam>
/// <param name="x">The array segment to copy data from</param>
public void Add<T>(ArraySegment<T> x)
where T : struct
{
if (x == null)
{
throw new ArgumentNullException("Cannot add a null array");
}
if( x.Length == 0)
if( x.Count == 0)
{
// don't do anything if the array is empty
return;
@@ -317,10 +353,52 @@ namespace FlatBuffers
int size = ByteBuffer.SizeOf<T>();
// Need to prep on size (for data alignment) and then we pass the
// rest of the length (minus 1) as additional bytes
Prep(size, size * (x.Length - 1));
Prep(size, size * (x.Count - 1));
Put(x);
}
/// <summary>
/// Adds the data of type T pointed to by the given pointer to the buffer (aligns the data and grows if necessary).
/// </summary>
/// <typeparam name="T">The type of the input data</typeparam>
/// <param name="ptr">The pointer to copy data from</param>
/// <param name="sizeInBytes">The data size in bytes</param>
public void Add<T>(IntPtr ptr, int sizeInBytes)
where T : struct
{
if(sizeInBytes == 0)
{
// don't do anything if the array is empty
return;
}
if (ptr == IntPtr.Zero)
{
throw new ArgumentNullException("Cannot add a null pointer");
}
if(sizeInBytes < 0)
{
throw new ArgumentOutOfRangeException("sizeInBytes", "sizeInBytes cannot be negative");
}
if(!ByteBuffer.IsSupportedType<T>())
{
throw new ArgumentException("Cannot add this Type array to the builder");
}
int size = ByteBuffer.SizeOf<T>();
if((sizeInBytes % size) != 0)
{
throw new ArgumentException("The given size in bytes " + sizeInBytes + " doesn't match the element size of T ( " + size + ")", "sizeInBytes");
}
// Need to prep on size (for data alignment) and then we pass the
// rest of the length (minus 1) as additional bytes
Prep(size, sizeInBytes - size);
Put<T>(ptr, sizeInBytes);
}
#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1)
/// <summary>
/// Add a span of type T to the buffer (aligns the data and grows if necessary).