From d0321df8cfa24b86c15fdca029aadc2c232029cc Mon Sep 17 00:00:00 2001 From: Christopher Cifra Date: Thu, 23 Aug 2018 12:05:31 -0500 Subject: [PATCH] C# support for directly reading and writting to memory other than byte[]. For example, ByteBuffer can be initialized with a custom allocator which uses shared memory / memory mapped files. (#4886) Public access to the backing buffer uses Span instead of ArraySegment. Writing to the buffer now supports Span in addition to T[]. To maintain backwards compatibility ENABLE_SPAN_T must be defined. --- net/FlatBuffers/ByteBuffer.cs | 486 ++++++++++++++++------ net/FlatBuffers/FlatBufferBuilder.cs | 70 +++- net/FlatBuffers/FlatBuffers.csproj | 3 + net/FlatBuffers/Table.cs | 18 + src/idl_gen_general.cpp | 9 + tests/FlatBuffers.Test/FlatBuffersExampleTests.cs | 21 +- tests/MyGame/Example/Monster.cs | 40 ++ tests/MyGame/Example/Stat.cs | 4 + tests/MyGame/Example/TypeAliases.cs | 8 + tests/union_vector/Movie.cs | 7 + 10 files changed, 542 insertions(+), 124 deletions(-) diff --git a/net/FlatBuffers/ByteBuffer.cs b/net/FlatBuffers/ByteBuffer.cs index 7b170c1..cea7ca3 100644 --- a/net/FlatBuffers/ByteBuffer.cs +++ b/net/FlatBuffers/ByteBuffer.cs @@ -14,7 +14,7 @@ * limitations under the License. */ -// There are 2 #defines that have an impact on performance of this ByteBuffer implementation +// There are 3 #defines that have an impact on performance / features of this ByteBuffer implementation // // UNSAFE_BYTEBUFFER // This will use unsafe code to manipulate the underlying byte array. This @@ -24,6 +24,12 @@ // This will disable the bounds check asserts to the byte array. This can // yield a small performance gain in normal code.. // +// ENABLE_SPAN_T +// This will enable reading and writing blocks of memory with a Span instead if just +// T[]. You can also enable writing directly to shared memory or other types of memory +// by providing a custom implementation of ByteBufferAllocator. +// ENABLE_SPAN_T also requires UNSAFE_BYTEBUFFER to be defined +// // Using UNSAFE_BYTEBUFFER and BYTEBUFFER_NO_BOUNDS_CHECK together can yield a // performance gain of ~15% for some operations, however doing so is potentially // dangerous. Do so at your own risk! @@ -32,19 +38,132 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; +#if ENABLE_SPAN_T && !UNSAFE_BYTEBUFFER +#error ENABLE_SPAN_T requires UNSAFE_BYTEBUFFER to also be defined +#endif + namespace FlatBuffers { + public abstract class ByteBufferAllocator : IDisposable + { +#if UNSAFE_BYTEBUFFER + public unsafe byte* Buffer + { + get; + protected set; + } +#else + public byte[] Buffer + { + get; + protected set; + } +#endif + + public int Length + { + get; + protected set; + } + + public abstract void Dispose(); + + public abstract void GrowFront(int newSize); + +#if !ENABLE_SPAN_T + public abstract byte[] ByteArray { get; } +#endif + } + + public class ByteArrayAllocator : ByteBufferAllocator + { + private byte[] _buffer; + + public ByteArrayAllocator(byte[] buffer) + { + _buffer = buffer; + InitPointer(); + } + + public override void GrowFront(int newSize) + { + if ((Length & 0xC0000000) != 0) + throw new Exception( + "ByteBuffer: cannot grow buffer beyond 2 gigabytes."); + + if (newSize < Length) + throw new Exception("ByteBuffer: cannot truncate buffer."); + + byte[] newBuffer = new byte[newSize]; + System.Buffer.BlockCopy(_buffer, 0, newBuffer, newSize - Length, Length); + _buffer = newBuffer; + InitPointer(); + } + + public override void Dispose() + { + GC.SuppressFinalize(this); +#if UNSAFE_BYTEBUFFER + if (_handle.IsAllocated) + { + _handle.Free(); + } +#endif + } + +#if !ENABLE_SPAN_T + public override byte[] ByteArray => _buffer; +#endif + +#if UNSAFE_BYTEBUFFER + private GCHandle _handle; + + ~ByteArrayAllocator() + { + if (_handle.IsAllocated) + { + _handle.Free(); + } + } +#endif + + private void InitPointer() + { + Length = _buffer.Length; +#if UNSAFE_BYTEBUFFER + if (_handle.IsAllocated) + { + _handle.Free(); + } + _handle = GCHandle.Alloc(_buffer, GCHandleType.Pinned); + unsafe + { + Buffer = (byte*)_handle.AddrOfPinnedObject().ToPointer(); + } +#else + Buffer = _buffer; +#endif + } + } + + /// /// Class to mimic Java's ByteBuffer which is used heavily in Flatbuffers. /// - public class ByteBuffer + public class ByteBuffer : IDisposable { - protected byte[] _buffer; + private ByteBufferAllocator _buffer; private int _pos; // Must track start of the buffer. - public int Length { get { return _buffer.Length; } } + public ByteBuffer(ByteBufferAllocator allocator, int position) + { + _buffer = allocator; + _pos = position; + } public ByteBuffer(int size) : this(new byte[size]) { } @@ -52,15 +171,25 @@ namespace FlatBuffers public ByteBuffer(byte[] buffer, int pos) { - _buffer = buffer; + _buffer = new ByteArrayAllocator(buffer); _pos = pos; } + public void Dispose() + { + if (_buffer != null) + { + _buffer.Dispose(); + } + } + public int Position { get { return _pos; } set { _pos = value; } } + public int Length { get { return _buffer.Length; } } + public void Reset() { _pos = 0; @@ -77,17 +206,7 @@ namespace FlatBuffers // the end of the new buffer. public void GrowFront(int newSize) { - if ((Length & 0xC0000000) != 0) - throw new Exception( - "ByteBuffer: cannot grow buffer beyond 2 gigabytes."); - - if (newSize < Length) - throw new Exception("ByteBuffer: cannot truncate buffer."); - - byte[] newBuffer = new byte[newSize]; - Buffer.BlockCopy(_buffer, 0, newBuffer, newSize - Length, - Length); - _buffer = newBuffer; + _buffer.GrowFront(newSize); } public byte[] ToArray(int pos, int len) @@ -145,16 +264,38 @@ namespace FlatBuffers return SizeOf() * x.Length; } +#if ENABLE_SPAN_T + public static int ArraySize(Span x) + { + return SizeOf() * x.Length; + } +#endif + // Get a portion of the buffer casted into an array of type T, given // the buffer position and length. +#if ENABLE_SPAN_T public T[] ToArray(int pos, int len) where T: struct { + unsafe + { + AssertOffsetAndLength(pos, len); + T[] arr = new T[len]; + var typed = MemoryMarshal.Cast(new Span(_buffer.Buffer + pos, _buffer.Length)); + typed.Slice(0, arr.Length).CopyTo(arr); + return arr; + } + } +#else + public T[] ToArray(int pos, int len) + where T : struct + { AssertOffsetAndLength(pos, len); T[] arr = new T[len]; - Buffer.BlockCopy(_buffer, pos, arr, 0, ArraySize(arr)); + Buffer.BlockCopy(_buffer.ByteArray, pos, arr, 0, ArraySize(arr)); return arr; } +#endif public byte[] ToSizedArray() { @@ -166,15 +307,25 @@ namespace FlatBuffers return ToArray(0, Length); } + +#if ENABLE_SPAN_T + public unsafe Span ToSpan(int pos, int len) + { + return new Span(_buffer.Buffer, _buffer.Length).Slice(pos, len); + } +#else public ArraySegment ToArraySegment(int pos, int len) { - return new ArraySegment(_buffer, pos, len); + return new ArraySegment(_buffer.ByteArray, pos, len); } +#endif +#if !ENABLE_SPAN_T public MemoryStream ToMemoryStream(int pos, int len) { - return new MemoryStream(_buffer, pos, len); + return new MemoryStream(_buffer.ByteArray, pos, len); } +#endif #if !UNSAFE_BYTEBUFFER // Pre-allocated helper arrays for convertion. @@ -217,14 +368,14 @@ namespace FlatBuffers { for (int i = 0; i < count; i++) { - _buffer[offset + i] = (byte)(data >> i * 8); + _buffer.Buffer[offset + i] = (byte)(data >> i * 8); } } else { for (int i = 0; i < count; i++) { - _buffer[offset + count - 1 - i] = (byte)(data >> i * 8); + _buffer.Buffer[offset + count - 1 - i] = (byte)(data >> i * 8); } } } @@ -237,14 +388,14 @@ namespace FlatBuffers { for (int i = 0; i < count; i++) { - r |= (ulong)_buffer[offset + i] << i * 8; + r |= (ulong)_buffer.Buffer[offset + i] << i * 8; } } else { for (int i = 0; i < count; i++) { - r |= (ulong)_buffer[offset + count - 1 - i] << i * 8; + r |= (ulong)_buffer.Buffer[offset + count - 1 - i] << i * 8; } } return r; @@ -253,30 +404,57 @@ namespace FlatBuffers private void AssertOffsetAndLength(int offset, int length) { - #if !BYTEBUFFER_NO_BOUNDS_CHECK +#if !BYTEBUFFER_NO_BOUNDS_CHECK if (offset < 0 || offset > _buffer.Length - length) throw new ArgumentOutOfRangeException(); - #endif +#endif + } + +#if UNSAFE_BYTEBUFFER + + public unsafe void PutSbyte(int offset, sbyte value) + { + AssertOffsetAndLength(offset, sizeof(sbyte)); + _buffer.Buffer[offset] = (byte)value; + } + + public unsafe void PutByte(int offset, byte value) + { + AssertOffsetAndLength(offset, sizeof(byte)); + _buffer.Buffer[offset] = value; + } + + public unsafe void PutByte(int offset, byte value, int count) + { + AssertOffsetAndLength(offset, sizeof(byte) * count); + for (var i = 0; i < count; ++i) + _buffer.Buffer[offset + i] = value; } + // this method exists in order to conform with Java ByteBuffer standards + public void Put(int offset, byte value) + { + PutByte(offset, value); + } +#else public void PutSbyte(int offset, sbyte value) { AssertOffsetAndLength(offset, sizeof(sbyte)); - _buffer[offset] = (byte)value; + _buffer.Buffer[offset] = (byte)value; } public void PutByte(int offset, byte value) { AssertOffsetAndLength(offset, sizeof(byte)); - _buffer[offset] = value; + _buffer.Buffer[offset] = value; } public void PutByte(int offset, byte value, int count) { AssertOffsetAndLength(offset, sizeof(byte) * count); for (var i = 0; i < count; ++i) - _buffer[offset + i] = value; + _buffer.Buffer[offset + i] = value; } // this method exists in order to conform with Java ByteBuffer standards @@ -284,13 +462,25 @@ namespace FlatBuffers { PutByte(offset, value); } +#endif +#if ENABLE_SPAN_T + public unsafe void PutStringUTF8(int offset, string value) + { + AssertOffsetAndLength(offset, value.Length); + fixed (char* s = value) + { + Encoding.UTF8.GetBytes(s, value.Length, _buffer.Buffer + offset, Length - offset); + } + } +#else public void PutStringUTF8(int offset, string value) { AssertOffsetAndLength(offset, value.Length); Encoding.UTF8.GetBytes(value, 0, value.Length, - _buffer, offset); + _buffer.ByteArray, offset); } +#endif #if UNSAFE_BYTEBUFFER // Unsafe but more efficient versions of Put*. @@ -302,12 +492,10 @@ namespace FlatBuffers public unsafe void PutUshort(int offset, ushort value) { AssertOffsetAndLength(offset, sizeof(ushort)); - fixed (byte* ptr = _buffer) - { - *(ushort*)(ptr + offset) = BitConverter.IsLittleEndian - ? value - : ReverseBytes(value); - } + byte* ptr = _buffer.Buffer; + *(ushort*)(ptr + offset) = BitConverter.IsLittleEndian + ? value + : ReverseBytes(value); } public void PutInt(int offset, int value) @@ -318,12 +506,10 @@ namespace FlatBuffers public unsafe void PutUint(int offset, uint value) { AssertOffsetAndLength(offset, sizeof(uint)); - fixed (byte* ptr = _buffer) - { - *(uint*)(ptr + offset) = BitConverter.IsLittleEndian - ? value - : ReverseBytes(value); - } + byte* ptr = _buffer.Buffer; + *(uint*)(ptr + offset) = BitConverter.IsLittleEndian + ? value + : ReverseBytes(value); } public unsafe void PutLong(int offset, long value) @@ -334,44 +520,38 @@ namespace FlatBuffers public unsafe void PutUlong(int offset, ulong value) { AssertOffsetAndLength(offset, sizeof(ulong)); - fixed (byte* ptr = _buffer) - { - *(ulong*)(ptr + offset) = BitConverter.IsLittleEndian - ? value - : ReverseBytes(value); - } + byte* ptr = _buffer.Buffer; + *(ulong*)(ptr + offset) = BitConverter.IsLittleEndian + ? value + : ReverseBytes(value); } public unsafe void PutFloat(int offset, float value) { AssertOffsetAndLength(offset, sizeof(float)); - fixed (byte* ptr = _buffer) + byte* ptr = _buffer.Buffer; + if (BitConverter.IsLittleEndian) { - if (BitConverter.IsLittleEndian) - { - *(float*)(ptr + offset) = value; - } - else - { - *(uint*)(ptr + offset) = ReverseBytes(*(uint*)(&value)); - } + *(float*)(ptr + offset) = value; + } + else + { + *(uint*)(ptr + offset) = ReverseBytes(*(uint*)(&value)); } } public unsafe void PutDouble(int offset, double value) { AssertOffsetAndLength(offset, sizeof(double)); - fixed (byte* ptr = _buffer) + byte* ptr = _buffer.Buffer; + if (BitConverter.IsLittleEndian) { - if (BitConverter.IsLittleEndian) - { - *(double*)(ptr + offset) = value; + *(double*)(ptr + offset) = value; - } - else - { - *(ulong*)(ptr + offset) = ReverseBytes(*(ulong*)(ptr + offset)); - } + } + else + { + *(ulong*)(ptr + offset) = ReverseBytes(*(ulong*)(ptr + offset)); } } #else // !UNSAFE_BYTEBUFFER @@ -430,74 +610,43 @@ namespace FlatBuffers #endif // UNSAFE_BYTEBUFFER - /// - /// Copies an array of type T into this buffer, ending at the given - /// offset into this buffer. The starting offset is calculated based on the length - /// of the array and is the value returned. - /// - /// The type of the input data (must be a struct) - /// The offset into this buffer where the copy will end - /// The array to copy data from - /// The 'start' location of this buffer now, after the copy completed - public int Put(int offset, T[] x) - where T : struct +#if UNSAFE_BYTEBUFFER + public unsafe sbyte GetSbyte(int index) { - if(x == null) - { - throw new ArgumentNullException("Cannot put a null array"); - } - - if(x.Length == 0) - { - throw new ArgumentException("Cannot put an empty array"); - } - - if(!IsSupportedType()) - { - throw new ArgumentException("Cannot put an array of type " - + typeof(T) + " into this buffer"); - } - - if (BitConverter.IsLittleEndian) - { - int numBytes = ByteBuffer.ArraySize(x); - offset -= numBytes; - AssertOffsetAndLength(offset, numBytes); - // if we are LE, just do a block copy - Buffer.BlockCopy(x, 0, _buffer, offset, numBytes); - } - 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() functions - //} - } - return offset; + AssertOffsetAndLength(index, sizeof(sbyte)); + return (sbyte)_buffer.Buffer[index]; } - - - + public unsafe byte Get(int index) + { + AssertOffsetAndLength(index, sizeof(byte)); + return _buffer.Buffer[index]; + } +#else public sbyte GetSbyte(int index) { AssertOffsetAndLength(index, sizeof(sbyte)); - return (sbyte)_buffer[index]; + return (sbyte)_buffer.Buffer[index]; } public byte Get(int index) { AssertOffsetAndLength(index, sizeof(byte)); - return _buffer[index]; + return _buffer.Buffer[index]; } +#endif +#if ENABLE_SPAN_T + public unsafe string GetStringUTF8(int startPos, int len) + { + return Encoding.UTF8.GetString(_buffer.Buffer + startPos, len); + } +#else public string GetStringUTF8(int startPos, int len) { - return Encoding.UTF8.GetString(_buffer, startPos, len); + return Encoding.UTF8.GetString(_buffer.ByteArray, startPos, len); } +#endif #if UNSAFE_BYTEBUFFER // Unsafe but more efficient versions of Get*. @@ -509,7 +658,7 @@ namespace FlatBuffers public unsafe ushort GetUshort(int offset) { AssertOffsetAndLength(offset, sizeof(ushort)); - fixed (byte* ptr = _buffer) + byte* ptr = _buffer.Buffer; { return BitConverter.IsLittleEndian ? *(ushort*)(ptr + offset) @@ -525,7 +674,7 @@ namespace FlatBuffers public unsafe uint GetUint(int offset) { AssertOffsetAndLength(offset, sizeof(uint)); - fixed (byte* ptr = _buffer) + byte* ptr = _buffer.Buffer; { return BitConverter.IsLittleEndian ? *(uint*)(ptr + offset) @@ -541,7 +690,7 @@ namespace FlatBuffers public unsafe ulong GetUlong(int offset) { AssertOffsetAndLength(offset, sizeof(ulong)); - fixed (byte* ptr = _buffer) + byte* ptr = _buffer.Buffer; { return BitConverter.IsLittleEndian ? *(ulong*)(ptr + offset) @@ -552,7 +701,7 @@ namespace FlatBuffers public unsafe float GetFloat(int offset) { AssertOffsetAndLength(offset, sizeof(float)); - fixed (byte* ptr = _buffer) + byte* ptr = _buffer.Buffer; { if (BitConverter.IsLittleEndian) { @@ -569,7 +718,7 @@ namespace FlatBuffers public unsafe double GetDouble(int offset) { AssertOffsetAndLength(offset, sizeof(double)); - fixed (byte* ptr = _buffer) + byte* ptr = _buffer.Buffer; { if (BitConverter.IsLittleEndian) { @@ -631,5 +780,98 @@ namespace FlatBuffers return doublehelper[0]; } #endif // UNSAFE_BYTEBUFFER + + /// + /// Copies an array of type T into this buffer, ending at the given + /// offset into this buffer. The starting offset is calculated based on the length + /// of the array and is the value returned. + /// + /// The type of the input data (must be a struct) + /// The offset into this buffer where the copy will end + /// The array to copy data from + /// The 'start' location of this buffer now, after the copy completed + public int Put(int offset, T[] x) + where T : struct + { + if (x == null) + { + throw new ArgumentNullException("Cannot put a null array"); + } + + if (x.Length == 0) + { + throw new ArgumentException("Cannot put an empty array"); + } + + if (!IsSupportedType()) + { + throw new ArgumentException("Cannot put an array of type " + + typeof(T) + " into this buffer"); + } + + if (BitConverter.IsLittleEndian) + { + int numBytes = ByteBuffer.ArraySize(x); + offset -= numBytes; + AssertOffsetAndLength(offset, numBytes); + // if we are LE, just do a block copy +#if ENABLE_SPAN_T + unsafe + { + MemoryMarshal.Cast(x).CopyTo(new Span(_buffer.Buffer, _buffer.Length).Slice(offset, numBytes)); + } +#else + Buffer.BlockCopy(x, 0, _buffer.ByteArray, 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() functions + //} + } + return offset; + } + +#if ENABLE_SPAN_T + public unsafe int Put(int offset, Span x) + where T : struct + { + if (x.Length == 0) + { + throw new ArgumentException("Cannot put an empty array"); + } + + if (!IsSupportedType()) + { + throw new ArgumentException("Cannot put an array of type " + + typeof(T) + " into this buffer"); + } + + if (BitConverter.IsLittleEndian) + { + int numBytes = ByteBuffer.ArraySize(x); + offset -= numBytes; + AssertOffsetAndLength(offset, numBytes); + // if we are LE, just do a block copy + MemoryMarshal.Cast(x).CopyTo(new Span(_buffer.Buffer, _buffer.Length).Slice(offset, numBytes)); + } + 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() functions + //} + } + return offset; + } +#endif } } diff --git a/net/FlatBuffers/FlatBufferBuilder.cs b/net/FlatBuffers/FlatBufferBuilder.cs index 33bba96..93f72be 100644 --- a/net/FlatBuffers/FlatBufferBuilder.cs +++ b/net/FlatBuffers/FlatBufferBuilder.cs @@ -63,6 +63,17 @@ namespace FlatBuffers } /// + /// Create a FlatBufferBuilder backed by the pased in ByteBuffer + /// + /// The ByteBuffer to write to + public FlatBufferBuilder(ByteBuffer buffer) + { + _bb = buffer; + _space = buffer.Length; + buffer.Reset(); + } + + /// /// Reset the FlatBufferBuilder by purging all data that it holds. /// public void Clear() @@ -191,6 +202,20 @@ namespace FlatBuffers _space = _bb.Put(_space, x); } +#if ENABLE_SPAN_T + /// + /// Puts a span of type T into this builder at the + /// current offset + /// + /// The type of the input data + /// The span to copy data from + public void Put(Span x) + where T : struct + { + _space = _bb.Put(_space, x); + } +#endif + public void PutDouble(double x) { _bb.PutDouble(_space -= sizeof(double), x); @@ -288,6 +313,28 @@ namespace FlatBuffers Put(x); } +#if ENABLE_SPAN_T + /// + /// Add a span of type T to the buffer (aligns the data and grows if necessary). + /// + /// The type of the input data + /// The span to copy data from + public void Add(Span x) + where T : struct + { + if (!ByteBuffer.IsSupportedType()) + { + throw new ArgumentException("Cannot add this Type array to the builder"); + } + + int size = ByteBuffer.SizeOf(); + // 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)); + Put(x); + } +#endif + /// /// Add a `double` to the buffer (aligns the data and grows if necessary). /// @@ -511,6 +558,27 @@ namespace FlatBuffers return new StringOffset(EndVector().Value); } + +#if ENABLE_SPAN_T + /// + /// Creates a string in the buffer from a Span containing + /// a UTF8 string. + /// + /// the UTF8 string to add to the buffer + /// + /// The offset in the buffer where the encoded string starts. + /// + public StringOffset CreateUTF8String(Span chars) + { + NotNested(); + AddByte(0); + var utf8StringLen = chars.Length; + StartVector(1, utf8StringLen, 1); + _space = _bb.Put(_space, chars); + return new StringOffset(EndVector().Value); + } +#endif + /// @cond FLATBUFFERS_INTERNAL // Structs are stored inline, so nothing additional is being added. // `d` is always 0. @@ -568,7 +636,7 @@ namespace FlatBuffers break; } - endLoop: { } + endLoop: { } } if (existingVtable != 0) { diff --git a/net/FlatBuffers/FlatBuffers.csproj b/net/FlatBuffers/FlatBuffers.csproj index e8189aa..b0c9ad3 100644 --- a/net/FlatBuffers/FlatBuffers.csproj +++ b/net/FlatBuffers/FlatBuffers.csproj @@ -10,4 +10,7 @@ https://github.com/google/flatbuffers/blob/master/LICENSE.txt + + + \ No newline at end of file diff --git a/net/FlatBuffers/Table.cs b/net/FlatBuffers/Table.cs index 07db5f4..cc7f3c1 100644 --- a/net/FlatBuffers/Table.cs +++ b/net/FlatBuffers/Table.cs @@ -78,6 +78,23 @@ namespace FlatBuffers return offset + bb.GetInt(offset) + sizeof(int); // data starts after the length } +#if ENABLE_SPAN_T + // Get the data of a vector whoses offset is stored at "offset" in this object as an + // Spant<byte>. If the vector is not present in the ByteBuffer, + // then an empty span will be returned. + public Span __vector_as_span(int offset) + { + var o = this.__offset(offset); + if (0 == o) + { + return new Span(); + } + + var pos = this.__vector(o); + var len = this.__vector_len(o); + return bb.ToSpan(pos, len); + } +#else // Get the data of a vector whoses offset is stored at "offset" in this object as an // ArraySegment<byte>. If the vector is not present in the ByteBuffer, // then a null value will be returned. @@ -93,6 +110,7 @@ namespace FlatBuffers var len = this.__vector_len(o); return bb.ToArraySegment(pos, len); } +#endif // Get the data of a vector whoses offset is stored at "offset" in this object as an // T[]. If the vector is not present in the ByteBuffer, then a null value will be diff --git a/src/idl_gen_general.cpp b/src/idl_gen_general.cpp index eda0214..f3ed3e4 100644 --- a/src/idl_gen_general.cpp +++ b/src/idl_gen_general.cpp @@ -1113,12 +1113,21 @@ class GeneralGenerator : public BaseGenerator { code += "); }\n"; break; case IDLOptions::kCSharp: + code += "#if ENABLE_SPAN_T\n"; + code += " public Span Get"; + code += MakeCamel(field.name, lang_.first_camel_upper); + code += "Bytes() { return "; + code += lang_.accessor_prefix + "__vector_as_span("; + code += NumToString(field.value.offset); + code += "); }\n"; + code += "#else\n"; code += " public ArraySegment? Get"; code += MakeCamel(field.name, lang_.first_camel_upper); code += "Bytes() { return "; code += lang_.accessor_prefix + "__vector_as_arraysegment("; code += NumToString(field.value.offset); code += "); }\n"; + code += "#endif\n"; // For direct blockcopying the data into a typed array code += " public "; diff --git a/tests/FlatBuffers.Test/FlatBuffersExampleTests.cs b/tests/FlatBuffers.Test/FlatBuffersExampleTests.cs index d438e0e..603de34 100644 --- a/tests/FlatBuffers.Test/FlatBuffersExampleTests.cs +++ b/tests/FlatBuffers.Test/FlatBuffersExampleTests.cs @@ -110,14 +110,19 @@ namespace FlatBuffers.Test Monster.FinishMonsterBuffer(fbb, mon); } - // Dump to output directory so we can inspect later, if needed +#if ENABLE_SPAN_T + var data = fbb.DataBuffer.ToSizedArray(); + string filename = @"Resources/monsterdata_cstest" + (sizePrefix ? "_sp" : "") + ".mon"; + File.WriteAllBytes(filename, data); +#else using (var ms = fbb.DataBuffer.ToMemoryStream(fbb.DataBuffer.Position, fbb.Offset)) { var data = ms.ToArray(); string filename = @"Resources/monsterdata_cstest" + (sizePrefix ? "_sp" : "") + ".mon"; File.WriteAllBytes(filename, data); } +#endif // Remove the size prefix if necessary for further testing ByteBuffer dataBuffer = fbb.DataBuffer; @@ -243,6 +248,19 @@ namespace FlatBuffers.Test Assert.AreEqual(false, monster.Testbool); +#if ENABLE_SPAN_T + var nameBytes = monster.GetNameBytes(); + Assert.AreEqual("MyMonster", Encoding.UTF8.GetString(nameBytes.ToArray(), 0, nameBytes.Length)); + + if (0 == monster.TestarrayofboolsLength) + { + Assert.IsFalse(monster.GetTestarrayofboolsBytes().Length != 0); + } + else + { + Assert.IsTrue(monster.GetTestarrayofboolsBytes().Length == 0); + } +#else var nameBytes = monster.GetNameBytes().Value; Assert.AreEqual("MyMonster", Encoding.UTF8.GetString(nameBytes.Array, nameBytes.Offset, nameBytes.Count)); @@ -254,6 +272,7 @@ namespace FlatBuffers.Test { Assert.IsTrue(monster.GetTestarrayofboolsBytes().HasValue); } +#endif } [FlatBuffersTestMethod] diff --git a/tests/MyGame/Example/Monster.cs b/tests/MyGame/Example/Monster.cs index b3109e9..3df0d63 100644 --- a/tests/MyGame/Example/Monster.cs +++ b/tests/MyGame/Example/Monster.cs @@ -25,11 +25,19 @@ public struct Monster : IFlatbufferObject public short Hp { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetShort(o + __p.bb_pos) : (short)100; } } public bool MutateHp(short hp) { int o = __p.__offset(8); if (o != 0) { __p.bb.PutShort(o + __p.bb_pos, hp); return true; } else { return false; } } public string Name { get { int o = __p.__offset(10); return o != 0 ? __p.__string(o + __p.bb_pos) : null; } } +#if ENABLE_SPAN_T + public Span GetNameBytes() { return __p.__vector_as_span(10); } +#else public ArraySegment? GetNameBytes() { return __p.__vector_as_arraysegment(10); } +#endif public byte[] GetNameArray() { return __p.__vector_as_array(10); } public byte Inventory(int j) { int o = __p.__offset(14); return o != 0 ? __p.bb.Get(__p.__vector(o) + j * 1) : (byte)0; } public int InventoryLength { get { int o = __p.__offset(14); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetInventoryBytes() { return __p.__vector_as_span(14); } +#else public ArraySegment? GetInventoryBytes() { return __p.__vector_as_arraysegment(14); } +#endif public byte[] GetInventoryArray() { return __p.__vector_as_array(14); } public bool MutateInventory(int j, byte inventory) { int o = __p.__offset(14); if (o != 0) { __p.bb.Put(__p.__vector(o) + j * 1, inventory); return true; } else { return false; } } public Color Color { get { int o = __p.__offset(16); return o != 0 ? (Color)__p.bb.GetSbyte(o + __p.bb_pos) : Color.Blue; } } @@ -49,7 +57,11 @@ public struct Monster : IFlatbufferObject public Monster? Enemy { get { int o = __p.__offset(28); return o != 0 ? (Monster?)(new Monster()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } public byte Testnestedflatbuffer(int j) { int o = __p.__offset(30); return o != 0 ? __p.bb.Get(__p.__vector(o) + j * 1) : (byte)0; } public int TestnestedflatbufferLength { get { int o = __p.__offset(30); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetTestnestedflatbufferBytes() { return __p.__vector_as_span(30); } +#else public ArraySegment? GetTestnestedflatbufferBytes() { return __p.__vector_as_arraysegment(30); } +#endif public byte[] GetTestnestedflatbufferArray() { return __p.__vector_as_array(30); } public Monster? GetTestnestedflatbufferAsMonster() { int o = __p.__offset(30); return o != 0 ? (Monster?)(new Monster()).__assign(__p.__indirect(__p.__vector(o)), __p.bb) : null; } public bool MutateTestnestedflatbuffer(int j, byte testnestedflatbuffer) { int o = __p.__offset(30); if (o != 0) { __p.bb.Put(__p.__vector(o) + j * 1, testnestedflatbuffer); return true; } else { return false; } } @@ -74,7 +86,11 @@ public struct Monster : IFlatbufferObject public bool MutateTesthashu64Fnv1a(ulong testhashu64_fnv1a) { int o = __p.__offset(50); if (o != 0) { __p.bb.PutUlong(o + __p.bb_pos, testhashu64_fnv1a); return true; } else { return false; } } public bool Testarrayofbools(int j) { int o = __p.__offset(52); return o != 0 ? 0!=__p.bb.Get(__p.__vector(o) + j * 1) : false; } public int TestarrayofboolsLength { get { int o = __p.__offset(52); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetTestarrayofboolsBytes() { return __p.__vector_as_span(52); } +#else public ArraySegment? GetTestarrayofboolsBytes() { return __p.__vector_as_arraysegment(52); } +#endif public bool[] GetTestarrayofboolsArray() { return __p.__vector_as_array(52); } public bool MutateTestarrayofbools(int j, bool testarrayofbools) { int o = __p.__offset(52); if (o != 0) { __p.bb.Put(__p.__vector(o) + j * 1, (byte)(testarrayofbools ? 1 : 0)); return true; } else { return false; } } public float Testf { get { int o = __p.__offset(54); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)3.14159f; } } @@ -89,19 +105,31 @@ public struct Monster : IFlatbufferObject public int TestarrayofsortedstructLength { get { int o = __p.__offset(62); return o != 0 ? __p.__vector_len(o) : 0; } } public byte Flex(int j) { int o = __p.__offset(64); return o != 0 ? __p.bb.Get(__p.__vector(o) + j * 1) : (byte)0; } public int FlexLength { get { int o = __p.__offset(64); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetFlexBytes() { return __p.__vector_as_span(64); } +#else public ArraySegment? GetFlexBytes() { return __p.__vector_as_arraysegment(64); } +#endif public byte[] GetFlexArray() { return __p.__vector_as_array(64); } public bool MutateFlex(int j, byte flex) { int o = __p.__offset(64); if (o != 0) { __p.bb.Put(__p.__vector(o) + j * 1, flex); return true; } else { return false; } } public Test? Test5(int j) { int o = __p.__offset(66); return o != 0 ? (Test?)(new Test()).__assign(__p.__vector(o) + j * 4, __p.bb) : null; } public int Test5Length { get { int o = __p.__offset(66); return o != 0 ? __p.__vector_len(o) : 0; } } public long VectorOfLongs(int j) { int o = __p.__offset(68); return o != 0 ? __p.bb.GetLong(__p.__vector(o) + j * 8) : (long)0; } public int VectorOfLongsLength { get { int o = __p.__offset(68); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetVectorOfLongsBytes() { return __p.__vector_as_span(68); } +#else public ArraySegment? GetVectorOfLongsBytes() { return __p.__vector_as_arraysegment(68); } +#endif public long[] GetVectorOfLongsArray() { return __p.__vector_as_array(68); } public bool MutateVectorOfLongs(int j, long vector_of_longs) { int o = __p.__offset(68); if (o != 0) { __p.bb.PutLong(__p.__vector(o) + j * 8, vector_of_longs); return true; } else { return false; } } public double VectorOfDoubles(int j) { int o = __p.__offset(70); return o != 0 ? __p.bb.GetDouble(__p.__vector(o) + j * 8) : (double)0; } public int VectorOfDoublesLength { get { int o = __p.__offset(70); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetVectorOfDoublesBytes() { return __p.__vector_as_span(70); } +#else public ArraySegment? GetVectorOfDoublesBytes() { return __p.__vector_as_arraysegment(70); } +#endif public double[] GetVectorOfDoublesArray() { return __p.__vector_as_array(70); } public bool MutateVectorOfDoubles(int j, double vector_of_doubles) { int o = __p.__offset(70); if (o != 0) { __p.bb.PutDouble(__p.__vector(o) + j * 8, vector_of_doubles); return true; } else { return false; } } public MyGame.InParentNamespace? ParentNamespaceTest { get { int o = __p.__offset(72); return o != 0 ? (MyGame.InParentNamespace?)(new MyGame.InParentNamespace()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } @@ -112,7 +140,11 @@ public struct Monster : IFlatbufferObject public bool MutateSingleWeakReference(ulong single_weak_reference) { int o = __p.__offset(76); if (o != 0) { __p.bb.PutUlong(o + __p.bb_pos, single_weak_reference); return true; } else { return false; } } public ulong VectorOfWeakReferences(int j) { int o = __p.__offset(78); return o != 0 ? __p.bb.GetUlong(__p.__vector(o) + j * 8) : (ulong)0; } public int VectorOfWeakReferencesLength { get { int o = __p.__offset(78); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetVectorOfWeakReferencesBytes() { return __p.__vector_as_span(78); } +#else public ArraySegment? GetVectorOfWeakReferencesBytes() { return __p.__vector_as_arraysegment(78); } +#endif public ulong[] GetVectorOfWeakReferencesArray() { return __p.__vector_as_array(78); } public bool MutateVectorOfWeakReferences(int j, ulong vector_of_weak_references) { int o = __p.__offset(78); if (o != 0) { __p.bb.PutUlong(__p.__vector(o) + j * 8, vector_of_weak_references); return true; } else { return false; } } public Referrable? VectorOfStrongReferrables(int j) { int o = __p.__offset(80); return o != 0 ? (Referrable?)(new Referrable()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } @@ -122,14 +154,22 @@ public struct Monster : IFlatbufferObject public bool MutateCoOwningReference(ulong co_owning_reference) { int o = __p.__offset(82); if (o != 0) { __p.bb.PutUlong(o + __p.bb_pos, co_owning_reference); return true; } else { return false; } } public ulong VectorOfCoOwningReferences(int j) { int o = __p.__offset(84); return o != 0 ? __p.bb.GetUlong(__p.__vector(o) + j * 8) : (ulong)0; } public int VectorOfCoOwningReferencesLength { get { int o = __p.__offset(84); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetVectorOfCoOwningReferencesBytes() { return __p.__vector_as_span(84); } +#else public ArraySegment? GetVectorOfCoOwningReferencesBytes() { return __p.__vector_as_arraysegment(84); } +#endif public ulong[] GetVectorOfCoOwningReferencesArray() { return __p.__vector_as_array(84); } public bool MutateVectorOfCoOwningReferences(int j, ulong vector_of_co_owning_references) { int o = __p.__offset(84); if (o != 0) { __p.bb.PutUlong(__p.__vector(o) + j * 8, vector_of_co_owning_references); return true; } else { return false; } } public ulong NonOwningReference { get { int o = __p.__offset(86); return o != 0 ? __p.bb.GetUlong(o + __p.bb_pos) : (ulong)0; } } public bool MutateNonOwningReference(ulong non_owning_reference) { int o = __p.__offset(86); if (o != 0) { __p.bb.PutUlong(o + __p.bb_pos, non_owning_reference); return true; } else { return false; } } public ulong VectorOfNonOwningReferences(int j) { int o = __p.__offset(88); return o != 0 ? __p.bb.GetUlong(__p.__vector(o) + j * 8) : (ulong)0; } public int VectorOfNonOwningReferencesLength { get { int o = __p.__offset(88); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetVectorOfNonOwningReferencesBytes() { return __p.__vector_as_span(88); } +#else public ArraySegment? GetVectorOfNonOwningReferencesBytes() { return __p.__vector_as_arraysegment(88); } +#endif public ulong[] GetVectorOfNonOwningReferencesArray() { return __p.__vector_as_array(88); } public bool MutateVectorOfNonOwningReferences(int j, ulong vector_of_non_owning_references) { int o = __p.__offset(88); if (o != 0) { __p.bb.PutUlong(__p.__vector(o) + j * 8, vector_of_non_owning_references); return true; } else { return false; } } diff --git a/tests/MyGame/Example/Stat.cs b/tests/MyGame/Example/Stat.cs index 9d78da5..fdd7e85 100644 --- a/tests/MyGame/Example/Stat.cs +++ b/tests/MyGame/Example/Stat.cs @@ -18,7 +18,11 @@ public struct Stat : IFlatbufferObject public Stat __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } public string Id { get { int o = __p.__offset(4); return o != 0 ? __p.__string(o + __p.bb_pos) : null; } } +#if ENABLE_SPAN_T + public Span GetIdBytes() { return __p.__vector_as_span(4); } +#else public ArraySegment? GetIdBytes() { return __p.__vector_as_arraysegment(4); } +#endif public byte[] GetIdArray() { return __p.__vector_as_array(4); } public long Val { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetLong(o + __p.bb_pos) : (long)0; } } public bool MutateVal(long val) { int o = __p.__offset(6); if (o != 0) { __p.bb.PutLong(o + __p.bb_pos, val); return true; } else { return false; } } diff --git a/tests/MyGame/Example/TypeAliases.cs b/tests/MyGame/Example/TypeAliases.cs index c4d8724..5ff1011 100644 --- a/tests/MyGame/Example/TypeAliases.cs +++ b/tests/MyGame/Example/TypeAliases.cs @@ -39,12 +39,20 @@ public struct TypeAliases : IFlatbufferObject public bool MutateF64(double f64) { int o = __p.__offset(22); if (o != 0) { __p.bb.PutDouble(o + __p.bb_pos, f64); return true; } else { return false; } } public sbyte V8(int j) { int o = __p.__offset(24); return o != 0 ? __p.bb.GetSbyte(__p.__vector(o) + j * 1) : (sbyte)0; } public int V8Length { get { int o = __p.__offset(24); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetV8Bytes() { return __p.__vector_as_span(24); } +#else public ArraySegment? GetV8Bytes() { return __p.__vector_as_arraysegment(24); } +#endif public sbyte[] GetV8Array() { return __p.__vector_as_array(24); } public bool MutateV8(int j, sbyte v8) { int o = __p.__offset(24); if (o != 0) { __p.bb.PutSbyte(__p.__vector(o) + j * 1, v8); return true; } else { return false; } } public double Vf64(int j) { int o = __p.__offset(26); return o != 0 ? __p.bb.GetDouble(__p.__vector(o) + j * 8) : (double)0; } public int Vf64Length { get { int o = __p.__offset(26); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetVf64Bytes() { return __p.__vector_as_span(26); } +#else public ArraySegment? GetVf64Bytes() { return __p.__vector_as_arraysegment(26); } +#endif public double[] GetVf64Array() { return __p.__vector_as_array(26); } public bool MutateVf64(int j, double vf64) { int o = __p.__offset(26); if (o != 0) { __p.bb.PutDouble(__p.__vector(o) + j * 8, vf64); return true; } else { return false; } } diff --git a/tests/union_vector/Movie.cs b/tests/union_vector/Movie.cs index 30954aa..b229b4f 100644 --- a/tests/union_vector/Movie.cs +++ b/tests/union_vector/Movie.cs @@ -19,7 +19,12 @@ public struct Movie : IFlatbufferObject public TTable? MainCharacter() where TTable : struct, IFlatbufferObject { int o = __p.__offset(6); return o != 0 ? (TTable?)__p.__union(o) : null; } public Character CharactersType(int j) { int o = __p.__offset(8); return o != 0 ? (Character)__p.bb.Get(__p.__vector(o) + j * 1) : (Character)0; } public int CharactersTypeLength { get { int o = __p.__offset(8); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetCharactersTypeBytes() { return __p.__vector_as_span(8); } +#else public ArraySegment? GetCharactersTypeBytes() { return __p.__vector_as_arraysegment(8); } +#endif + public Character[] GetCharactersTypeArray() { return __p.__vector_as_array(8); } public TTable? Characters(int j) where TTable : struct, IFlatbufferObject { int o = __p.__offset(10); return o != 0 ? (TTable?)__p.__union(__p.__vector(o) + j * 4) : null; } public int CharactersLength { get { int o = __p.__offset(10); return o != 0 ? __p.__vector_len(o) : 0; } } @@ -41,9 +46,11 @@ public struct Movie : IFlatbufferObject public static void AddMainCharacter(FlatBufferBuilder builder, int mainCharacterOffset) { builder.AddOffset(1, mainCharacterOffset, 0); } public static void AddCharactersType(FlatBufferBuilder builder, VectorOffset charactersTypeOffset) { builder.AddOffset(2, charactersTypeOffset.Value, 0); } public static VectorOffset CreateCharactersTypeVector(FlatBufferBuilder builder, Character[] data) { builder.StartVector(1, data.Length, 1); for (int i = data.Length - 1; i >= 0; i--) builder.AddByte((byte)data[i]); return builder.EndVector(); } + public static VectorOffset CreateCharactersTypeVectorBlock(FlatBufferBuilder builder, Character[] data) { builder.StartVector(1, data.Length, 1); builder.Add(data); return builder.EndVector(); } public static void StartCharactersTypeVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(1, numElems, 1); } public static void AddCharacters(FlatBufferBuilder builder, VectorOffset charactersOffset) { builder.AddOffset(3, charactersOffset.Value, 0); } public static VectorOffset CreateCharactersVector(FlatBufferBuilder builder, int[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i]); return builder.EndVector(); } + public static VectorOffset CreateCharactersVectorBlock(FlatBufferBuilder builder, int[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } public static void StartCharactersVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } public static Offset EndMovie(FlatBufferBuilder builder) { int o = builder.EndObject(); -- 2.7.4