1 /* 2 * Copyright 2014 Google Inc. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // There are three conditional compilation symbols that have an impact on performance/features of this ByteBuffer implementation. 18 // 19 // UNSAFE_BYTEBUFFER 20 // This will use unsafe code to manipulate the underlying byte array. This 21 // can yield a reasonable performance increase. 22 // 23 // BYTEBUFFER_NO_BOUNDS_CHECK 24 // This will disable the bounds check asserts to the byte array. This can 25 // yield a small performance gain in normal code. 26 // 27 // ENABLE_SPAN_T 28 // This will enable reading and writing blocks of memory with a Span<T> instead of just 29 // T[]. You can also enable writing directly to shared memory or other types of memory 30 // by providing a custom implementation of ByteBufferAllocator. 31 // ENABLE_SPAN_T also requires UNSAFE_BYTEBUFFER to be defined, or .NET 32 // Standard 2.1. 33 // 34 // Using UNSAFE_BYTEBUFFER and BYTEBUFFER_NO_BOUNDS_CHECK together can yield a 35 // performance gain of ~15% for some operations, however doing so is potentially 36 // dangerous. Do so at your own risk! 37 // 38 39 using System; 40 using System.Collections.Generic; 41 using System.IO; 42 using System.Runtime.CompilerServices; 43 using System.Runtime.InteropServices; 44 using System.Text; 45 46 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 47 using System.Buffers.Binary; 48 #endif 49 50 #if ENABLE_SPAN_T && !UNSAFE_BYTEBUFFER && !NETSTANDARD2_1 51 #warning ENABLE_SPAN_T requires UNSAFE_BYTEBUFFER to also be defined 52 #endif 53 54 namespace FlatBuffers 55 { 56 public abstract class ByteBufferAllocator 57 { 58 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 59 public abstract Span<byte> Span { get; } 60 public abstract ReadOnlySpan<byte> ReadOnlySpan { get; } 61 public abstract Memory<byte> Memory { get; } 62 public abstract ReadOnlyMemory<byte> ReadOnlyMemory { get; } 63 64 #else 65 public byte[] Buffer 66 { 67 get; 68 protected set; 69 } 70 #endif 71 72 public int Length 73 { 74 get; 75 protected set; 76 } 77 GrowFront(int newSize)78 public abstract void GrowFront(int newSize); 79 } 80 81 public sealed class ByteArrayAllocator : ByteBufferAllocator 82 { 83 private byte[] _buffer; 84 ByteArrayAllocator(byte[] buffer)85 public ByteArrayAllocator(byte[] buffer) 86 { 87 _buffer = buffer; 88 InitBuffer(); 89 } 90 GrowFront(int newSize)91 public override void GrowFront(int newSize) 92 { 93 if ((Length & 0xC0000000) != 0) 94 throw new Exception( 95 "ByteBuffer: cannot grow buffer beyond 2 gigabytes."); 96 97 if (newSize < Length) 98 throw new Exception("ByteBuffer: cannot truncate buffer."); 99 100 byte[] newBuffer = new byte[newSize]; 101 System.Buffer.BlockCopy(_buffer, 0, newBuffer, newSize - Length, Length); 102 _buffer = newBuffer; 103 InitBuffer(); 104 } 105 106 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 107 public override Span<byte> Span => _buffer; 108 public override ReadOnlySpan<byte> ReadOnlySpan => _buffer; 109 public override Memory<byte> Memory => _buffer; 110 public override ReadOnlyMemory<byte> ReadOnlyMemory => _buffer; 111 #endif 112 InitBuffer()113 private void InitBuffer() 114 { 115 Length = _buffer.Length; 116 #if !ENABLE_SPAN_T 117 Buffer = _buffer; 118 #endif 119 } 120 } 121 122 /// <summary> 123 /// Class to mimic Java's ByteBuffer which is used heavily in Flatbuffers. 124 /// </summary> 125 public class ByteBuffer 126 { 127 private ByteBufferAllocator _buffer; 128 private int _pos; // Must track start of the buffer. 129 ByteBuffer(ByteBufferAllocator allocator, int position)130 public ByteBuffer(ByteBufferAllocator allocator, int position) 131 { 132 _buffer = allocator; 133 _pos = position; 134 } 135 ByteBuffer(int size)136 public ByteBuffer(int size) : this(new byte[size]) { } 137 ByteBuffer(byte[] buffer)138 public ByteBuffer(byte[] buffer) : this(buffer, 0) { } 139 ByteBuffer(byte[] buffer, int pos)140 public ByteBuffer(byte[] buffer, int pos) 141 { 142 _buffer = new ByteArrayAllocator(buffer); 143 _pos = pos; 144 } 145 146 public int Position 147 { 148 get { return _pos; } 149 set { _pos = value; } 150 } 151 152 public int Length { get { return _buffer.Length; } } 153 Reset()154 public void Reset() 155 { 156 _pos = 0; 157 } 158 159 // Create a new ByteBuffer on the same underlying data. 160 // The new ByteBuffer's position will be same as this buffer's. Duplicate()161 public ByteBuffer Duplicate() 162 { 163 return new ByteBuffer(_buffer, Position); 164 } 165 166 // Increases the size of the ByteBuffer, and copies the old data towards 167 // the end of the new buffer. GrowFront(int newSize)168 public void GrowFront(int newSize) 169 { 170 _buffer.GrowFront(newSize); 171 } 172 ToArray(int pos, int len)173 public byte[] ToArray(int pos, int len) 174 { 175 return ToArray<byte>(pos, len); 176 } 177 178 /// <summary> 179 /// A lookup of type sizes. Used instead of Marshal.SizeOf() which has additional 180 /// overhead, but also is compatible with generic functions for simplified code. 181 /// </summary> 182 private static Dictionary<Type, int> genericSizes = new Dictionary<Type, int>() 183 { 184 { typeof(bool), sizeof(bool) }, 185 { typeof(float), sizeof(float) }, 186 { typeof(double), sizeof(double) }, 187 { typeof(sbyte), sizeof(sbyte) }, 188 { typeof(byte), sizeof(byte) }, 189 { typeof(short), sizeof(short) }, 190 { typeof(ushort), sizeof(ushort) }, 191 { typeof(int), sizeof(int) }, 192 { typeof(uint), sizeof(uint) }, 193 { typeof(ulong), sizeof(ulong) }, 194 { typeof(long), sizeof(long) }, 195 }; 196 197 /// <summary> 198 /// Get the wire-size (in bytes) of a type supported by flatbuffers. 199 /// </summary> 200 /// <param name="t">The type to get the wire size of</param> 201 /// <returns></returns> SizeOf()202 public static int SizeOf<T>() 203 { 204 return genericSizes[typeof(T)]; 205 } 206 207 /// <summary> 208 /// Checks if the Type provided is supported as scalar value 209 /// </summary> 210 /// <typeparam name="T">The Type to check</typeparam> 211 /// <returns>True if the type is a scalar type that is supported, falsed otherwise</returns> IsSupportedType()212 public static bool IsSupportedType<T>() 213 { 214 return genericSizes.ContainsKey(typeof(T)); 215 } 216 217 /// <summary> 218 /// Get the wire-size (in bytes) of an typed array 219 /// </summary> 220 /// <typeparam name="T">The type of the array</typeparam> 221 /// <param name="x">The array to get the size of</param> 222 /// <returns>The number of bytes the array takes on wire</returns> ArraySize(T[] x)223 public static int ArraySize<T>(T[] x) 224 { 225 return SizeOf<T>() * x.Length; 226 } 227 228 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) ArraySize(Span<T> x)229 public static int ArraySize<T>(Span<T> x) 230 { 231 return SizeOf<T>() * x.Length; 232 } 233 #endif 234 235 // Get a portion of the buffer casted into an array of type T, given 236 // the buffer position and length. 237 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 238 public T[] ToArray<T>(int pos, int len) 239 where T : struct 240 { AssertOffsetAndLengthFlatBuffers.ByteBuffer.__anon1241 AssertOffsetAndLength(pos, len); 242 return MemoryMarshal.Cast<byte, T>(_buffer.ReadOnlySpan.Slice(pos)).Slice(0, len).ToArray(); 243 } 244 #else 245 public T[] ToArray<T>(int pos, int len) 246 where T : struct 247 { AssertOffsetAndLengthFlatBuffers.ByteBuffer.__anon2248 AssertOffsetAndLength(pos, len); 249 T[] arr = new T[len]; Buffer.BlockCopyFlatBuffers.ByteBuffer.__anon2250 Buffer.BlockCopy(_buffer.Buffer, pos, arr, 0, ArraySize(arr)); 251 return arr; 252 } 253 #endif 254 ToSizedArray()255 public byte[] ToSizedArray() 256 { 257 return ToArray<byte>(Position, Length - Position); 258 } 259 ToFullArray()260 public byte[] ToFullArray() 261 { 262 return ToArray<byte>(0, Length); 263 } 264 265 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) ToReadOnlyMemory(int pos, int len)266 public ReadOnlyMemory<byte> ToReadOnlyMemory(int pos, int len) 267 { 268 return _buffer.ReadOnlyMemory.Slice(pos, len); 269 } 270 ToMemory(int pos, int len)271 public Memory<byte> ToMemory(int pos, int len) 272 { 273 return _buffer.Memory.Slice(pos, len); 274 } 275 ToSpan(int pos, int len)276 public Span<byte> ToSpan(int pos, int len) 277 { 278 return _buffer.Span.Slice(pos, len); 279 } 280 #else ToArraySegment(int pos, int len)281 public ArraySegment<byte> ToArraySegment(int pos, int len) 282 { 283 return new ArraySegment<byte>(_buffer.Buffer, pos, len); 284 } 285 ToMemoryStream(int pos, int len)286 public MemoryStream ToMemoryStream(int pos, int len) 287 { 288 return new MemoryStream(_buffer.Buffer, pos, len); 289 } 290 #endif 291 292 #if !UNSAFE_BYTEBUFFER 293 // A conversion union where all the members are overlapping. This allows to reinterpret the bytes of one type 294 // as another, without additional copies. 295 [StructLayout(LayoutKind.Explicit)] 296 struct ConversionUnion 297 { 298 [FieldOffset(0)] public int intValue; 299 [FieldOffset(0)] public float floatValue; 300 } 301 #endif // !UNSAFE_BYTEBUFFER 302 303 // Helper functions for the unsafe version. ReverseBytes(ushort input)304 static public ushort ReverseBytes(ushort input) 305 { 306 return (ushort)(((input & 0x00FFU) << 8) | 307 ((input & 0xFF00U) >> 8)); 308 } ReverseBytes(uint input)309 static public uint ReverseBytes(uint input) 310 { 311 return ((input & 0x000000FFU) << 24) | 312 ((input & 0x0000FF00U) << 8) | 313 ((input & 0x00FF0000U) >> 8) | 314 ((input & 0xFF000000U) >> 24); 315 } ReverseBytes(ulong input)316 static public ulong ReverseBytes(ulong input) 317 { 318 return (((input & 0x00000000000000FFUL) << 56) | 319 ((input & 0x000000000000FF00UL) << 40) | 320 ((input & 0x0000000000FF0000UL) << 24) | 321 ((input & 0x00000000FF000000UL) << 8) | 322 ((input & 0x000000FF00000000UL) >> 8) | 323 ((input & 0x0000FF0000000000UL) >> 24) | 324 ((input & 0x00FF000000000000UL) >> 40) | 325 ((input & 0xFF00000000000000UL) >> 56)); 326 } 327 328 #if !UNSAFE_BYTEBUFFER && (!ENABLE_SPAN_T || !NETSTANDARD2_1) 329 // Helper functions for the safe (but slower) version. WriteLittleEndian(int offset, int count, ulong data)330 protected void WriteLittleEndian(int offset, int count, ulong data) 331 { 332 if (BitConverter.IsLittleEndian) 333 { 334 for (int i = 0; i < count; i++) 335 { 336 _buffer.Buffer[offset + i] = (byte)(data >> i * 8); 337 } 338 } 339 else 340 { 341 for (int i = 0; i < count; i++) 342 { 343 _buffer.Buffer[offset + count - 1 - i] = (byte)(data >> i * 8); 344 } 345 } 346 } 347 ReadLittleEndian(int offset, int count)348 protected ulong ReadLittleEndian(int offset, int count) 349 { 350 AssertOffsetAndLength(offset, count); 351 ulong r = 0; 352 if (BitConverter.IsLittleEndian) 353 { 354 for (int i = 0; i < count; i++) 355 { 356 r |= (ulong)_buffer.Buffer[offset + i] << i * 8; 357 } 358 } 359 else 360 { 361 for (int i = 0; i < count; i++) 362 { 363 r |= (ulong)_buffer.Buffer[offset + count - 1 - i] << i * 8; 364 } 365 } 366 return r; 367 } 368 #elif ENABLE_SPAN_T && NETSTANDARD2_1 WriteLittleEndian(int offset, int count, ulong data)369 protected void WriteLittleEndian(int offset, int count, ulong data) 370 { 371 if (BitConverter.IsLittleEndian) 372 { 373 for (int i = 0; i < count; i++) 374 { 375 _buffer.Span[offset + i] = (byte)(data >> i * 8); 376 } 377 } 378 else 379 { 380 for (int i = 0; i < count; i++) 381 { 382 _buffer.Span[offset + count - 1 - i] = (byte)(data >> i * 8); 383 } 384 } 385 } 386 ReadLittleEndian(int offset, int count)387 protected ulong ReadLittleEndian(int offset, int count) 388 { 389 AssertOffsetAndLength(offset, count); 390 ulong r = 0; 391 if (BitConverter.IsLittleEndian) 392 { 393 for (int i = 0; i < count; i++) 394 { 395 r |= (ulong)_buffer.Span[offset + i] << i * 8; 396 } 397 } 398 else 399 { 400 for (int i = 0; i < count; i++) 401 { 402 r |= (ulong)_buffer.Span[offset + count - 1 - i] << i * 8; 403 } 404 } 405 return r; 406 } 407 #endif 408 AssertOffsetAndLength(int offset, int length)409 private void AssertOffsetAndLength(int offset, int length) 410 { 411 #if !BYTEBUFFER_NO_BOUNDS_CHECK 412 if (offset < 0 || 413 offset > _buffer.Length - length) 414 throw new ArgumentOutOfRangeException(); 415 #endif 416 } 417 418 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 419 PutSbyte(int offset, sbyte value)420 public void PutSbyte(int offset, sbyte value) 421 { 422 AssertOffsetAndLength(offset, sizeof(sbyte)); 423 _buffer.Span[offset] = (byte)value; 424 } 425 PutByte(int offset, byte value)426 public void PutByte(int offset, byte value) 427 { 428 AssertOffsetAndLength(offset, sizeof(byte)); 429 _buffer.Span[offset] = value; 430 } 431 PutByte(int offset, byte value, int count)432 public void PutByte(int offset, byte value, int count) 433 { 434 AssertOffsetAndLength(offset, sizeof(byte) * count); 435 Span<byte> span = _buffer.Span.Slice(offset, count); 436 for (var i = 0; i < span.Length; ++i) 437 span[i] = value; 438 } 439 #else PutSbyte(int offset, sbyte value)440 public void PutSbyte(int offset, sbyte value) 441 { 442 AssertOffsetAndLength(offset, sizeof(sbyte)); 443 _buffer.Buffer[offset] = (byte)value; 444 } 445 PutByte(int offset, byte value)446 public void PutByte(int offset, byte value) 447 { 448 AssertOffsetAndLength(offset, sizeof(byte)); 449 _buffer.Buffer[offset] = value; 450 } 451 PutByte(int offset, byte value, int count)452 public void PutByte(int offset, byte value, int count) 453 { 454 AssertOffsetAndLength(offset, sizeof(byte) * count); 455 for (var i = 0; i < count; ++i) 456 _buffer.Buffer[offset + i] = value; 457 } 458 #endif 459 460 // this method exists in order to conform with Java ByteBuffer standards Put(int offset, byte value)461 public void Put(int offset, byte value) 462 { 463 PutByte(offset, value); 464 } 465 466 #if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER PutStringUTF8(int offset, string value)467 public unsafe void PutStringUTF8(int offset, string value) 468 { 469 AssertOffsetAndLength(offset, value.Length); 470 fixed (char* s = value) 471 { 472 fixed (byte* buffer = &MemoryMarshal.GetReference(_buffer.Span)) 473 { 474 Encoding.UTF8.GetBytes(s, value.Length, buffer + offset, Length - offset); 475 } 476 } 477 } 478 #elif ENABLE_SPAN_T && NETSTANDARD2_1 PutStringUTF8(int offset, string value)479 public void PutStringUTF8(int offset, string value) 480 { 481 AssertOffsetAndLength(offset, value.Length); 482 Encoding.UTF8.GetBytes(value.AsSpan().Slice(0, value.Length), 483 _buffer.Span.Slice(offset)); 484 } 485 #else PutStringUTF8(int offset, string value)486 public void PutStringUTF8(int offset, string value) 487 { 488 AssertOffsetAndLength(offset, value.Length); 489 Encoding.UTF8.GetBytes(value, 0, value.Length, 490 _buffer.Buffer, offset); 491 } 492 #endif 493 494 #if UNSAFE_BYTEBUFFER 495 // Unsafe but more efficient versions of Put*. PutShort(int offset, short value)496 public void PutShort(int offset, short value) 497 { 498 PutUshort(offset, (ushort)value); 499 } 500 PutUshort(int offset, ushort value)501 public unsafe void PutUshort(int offset, ushort value) 502 { 503 AssertOffsetAndLength(offset, sizeof(ushort)); 504 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 505 Span<byte> span = _buffer.Span.Slice(offset); 506 BinaryPrimitives.WriteUInt16LittleEndian(span, value); 507 #else 508 fixed (byte* ptr = _buffer.Buffer) 509 { 510 *(ushort*)(ptr + offset) = BitConverter.IsLittleEndian 511 ? value 512 : ReverseBytes(value); 513 } 514 #endif 515 } 516 PutInt(int offset, int value)517 public void PutInt(int offset, int value) 518 { 519 PutUint(offset, (uint)value); 520 } 521 PutUint(int offset, uint value)522 public unsafe void PutUint(int offset, uint value) 523 { 524 AssertOffsetAndLength(offset, sizeof(uint)); 525 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 526 Span<byte> span = _buffer.Span.Slice(offset); 527 BinaryPrimitives.WriteUInt32LittleEndian(span, value); 528 #else 529 fixed (byte* ptr = _buffer.Buffer) 530 { 531 *(uint*)(ptr + offset) = BitConverter.IsLittleEndian 532 ? value 533 : ReverseBytes(value); 534 } 535 #endif 536 } 537 PutLong(int offset, long value)538 public unsafe void PutLong(int offset, long value) 539 { 540 PutUlong(offset, (ulong)value); 541 } 542 PutUlong(int offset, ulong value)543 public unsafe void PutUlong(int offset, ulong value) 544 { 545 AssertOffsetAndLength(offset, sizeof(ulong)); 546 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 547 Span<byte> span = _buffer.Span.Slice(offset); 548 BinaryPrimitives.WriteUInt64LittleEndian(span, value); 549 #else 550 fixed (byte* ptr = _buffer.Buffer) 551 { 552 *(ulong*)(ptr + offset) = BitConverter.IsLittleEndian 553 ? value 554 : ReverseBytes(value); 555 } 556 #endif 557 } 558 PutFloat(int offset, float value)559 public unsafe void PutFloat(int offset, float value) 560 { 561 AssertOffsetAndLength(offset, sizeof(float)); 562 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 563 fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.Span)) 564 #else 565 fixed (byte* ptr = _buffer.Buffer) 566 #endif 567 { 568 if (BitConverter.IsLittleEndian) 569 { 570 *(float*)(ptr + offset) = value; 571 } 572 else 573 { 574 *(uint*)(ptr + offset) = ReverseBytes(*(uint*)(&value)); 575 } 576 } 577 } 578 PutDouble(int offset, double value)579 public unsafe void PutDouble(int offset, double value) 580 { 581 AssertOffsetAndLength(offset, sizeof(double)); 582 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 583 fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.Span)) 584 #else 585 fixed (byte* ptr = _buffer.Buffer) 586 #endif 587 { 588 if (BitConverter.IsLittleEndian) 589 { 590 *(double*)(ptr + offset) = value; 591 } 592 else 593 { 594 *(ulong*)(ptr + offset) = ReverseBytes(*(ulong*)(&value)); 595 } 596 } 597 } 598 #else // !UNSAFE_BYTEBUFFER 599 // Slower versions of Put* for when unsafe code is not allowed. PutShort(int offset, short value)600 public void PutShort(int offset, short value) 601 { 602 AssertOffsetAndLength(offset, sizeof(short)); 603 WriteLittleEndian(offset, sizeof(short), (ulong)value); 604 } 605 PutUshort(int offset, ushort value)606 public void PutUshort(int offset, ushort value) 607 { 608 AssertOffsetAndLength(offset, sizeof(ushort)); 609 WriteLittleEndian(offset, sizeof(ushort), (ulong)value); 610 } 611 PutInt(int offset, int value)612 public void PutInt(int offset, int value) 613 { 614 AssertOffsetAndLength(offset, sizeof(int)); 615 WriteLittleEndian(offset, sizeof(int), (ulong)value); 616 } 617 PutUint(int offset, uint value)618 public void PutUint(int offset, uint value) 619 { 620 AssertOffsetAndLength(offset, sizeof(uint)); 621 WriteLittleEndian(offset, sizeof(uint), (ulong)value); 622 } 623 PutLong(int offset, long value)624 public void PutLong(int offset, long value) 625 { 626 AssertOffsetAndLength(offset, sizeof(long)); 627 WriteLittleEndian(offset, sizeof(long), (ulong)value); 628 } 629 PutUlong(int offset, ulong value)630 public void PutUlong(int offset, ulong value) 631 { 632 AssertOffsetAndLength(offset, sizeof(ulong)); 633 WriteLittleEndian(offset, sizeof(ulong), value); 634 } 635 PutFloat(int offset, float value)636 public void PutFloat(int offset, float value) 637 { 638 AssertOffsetAndLength(offset, sizeof(float)); 639 // TODO(derekbailey): use BitConvert.SingleToInt32Bits() whenever flatbuffers upgrades to a .NET version 640 // that contains it. 641 ConversionUnion union; 642 union.intValue = 0; 643 union.floatValue = value; 644 WriteLittleEndian(offset, sizeof(float), (ulong)union.intValue); 645 } 646 PutDouble(int offset, double value)647 public void PutDouble(int offset, double value) 648 { 649 AssertOffsetAndLength(offset, sizeof(double)); 650 WriteLittleEndian(offset, sizeof(double), (ulong)BitConverter.DoubleToInt64Bits(value)); 651 } 652 653 #endif // UNSAFE_BYTEBUFFER 654 655 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) GetSbyte(int index)656 public sbyte GetSbyte(int index) 657 { 658 AssertOffsetAndLength(index, sizeof(sbyte)); 659 return (sbyte)_buffer.ReadOnlySpan[index]; 660 } 661 Get(int index)662 public byte Get(int index) 663 { 664 AssertOffsetAndLength(index, sizeof(byte)); 665 return _buffer.ReadOnlySpan[index]; 666 } 667 #else GetSbyte(int index)668 public sbyte GetSbyte(int index) 669 { 670 AssertOffsetAndLength(index, sizeof(sbyte)); 671 return (sbyte)_buffer.Buffer[index]; 672 } 673 Get(int index)674 public byte Get(int index) 675 { 676 AssertOffsetAndLength(index, sizeof(byte)); 677 return _buffer.Buffer[index]; 678 } 679 #endif 680 681 #if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER GetStringUTF8(int startPos, int len)682 public unsafe string GetStringUTF8(int startPos, int len) 683 { 684 fixed (byte* buffer = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan.Slice(startPos))) 685 { 686 return Encoding.UTF8.GetString(buffer, len); 687 } 688 } 689 #elif ENABLE_SPAN_T && NETSTANDARD2_1 GetStringUTF8(int startPos, int len)690 public string GetStringUTF8(int startPos, int len) 691 { 692 return Encoding.UTF8.GetString(_buffer.Span.Slice(startPos, len)); 693 } 694 #else GetStringUTF8(int startPos, int len)695 public string GetStringUTF8(int startPos, int len) 696 { 697 return Encoding.UTF8.GetString(_buffer.Buffer, startPos, len); 698 } 699 #endif 700 701 #if UNSAFE_BYTEBUFFER 702 // Unsafe but more efficient versions of Get*. GetShort(int offset)703 public short GetShort(int offset) 704 { 705 return (short)GetUshort(offset); 706 } 707 GetUshort(int offset)708 public unsafe ushort GetUshort(int offset) 709 { 710 AssertOffsetAndLength(offset, sizeof(ushort)); 711 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 712 ReadOnlySpan<byte> span = _buffer.ReadOnlySpan.Slice(offset); 713 return BinaryPrimitives.ReadUInt16LittleEndian(span); 714 #else 715 fixed (byte* ptr = _buffer.Buffer) 716 { 717 return BitConverter.IsLittleEndian 718 ? *(ushort*)(ptr + offset) 719 : ReverseBytes(*(ushort*)(ptr + offset)); 720 } 721 #endif 722 } 723 GetInt(int offset)724 public int GetInt(int offset) 725 { 726 return (int)GetUint(offset); 727 } 728 GetUint(int offset)729 public unsafe uint GetUint(int offset) 730 { 731 AssertOffsetAndLength(offset, sizeof(uint)); 732 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 733 ReadOnlySpan<byte> span = _buffer.ReadOnlySpan.Slice(offset); 734 return BinaryPrimitives.ReadUInt32LittleEndian(span); 735 #else 736 fixed (byte* ptr = _buffer.Buffer) 737 { 738 return BitConverter.IsLittleEndian 739 ? *(uint*)(ptr + offset) 740 : ReverseBytes(*(uint*)(ptr + offset)); 741 } 742 #endif 743 } 744 GetLong(int offset)745 public long GetLong(int offset) 746 { 747 return (long)GetUlong(offset); 748 } 749 GetUlong(int offset)750 public unsafe ulong GetUlong(int offset) 751 { 752 AssertOffsetAndLength(offset, sizeof(ulong)); 753 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 754 ReadOnlySpan<byte> span = _buffer.ReadOnlySpan.Slice(offset); 755 return BinaryPrimitives.ReadUInt64LittleEndian(span); 756 #else 757 fixed (byte* ptr = _buffer.Buffer) 758 { 759 return BitConverter.IsLittleEndian 760 ? *(ulong*)(ptr + offset) 761 : ReverseBytes(*(ulong*)(ptr + offset)); 762 } 763 #endif 764 } 765 GetFloat(int offset)766 public unsafe float GetFloat(int offset) 767 { 768 AssertOffsetAndLength(offset, sizeof(float)); 769 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 770 fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan)) 771 #else 772 fixed (byte* ptr = _buffer.Buffer) 773 #endif 774 { 775 if (BitConverter.IsLittleEndian) 776 { 777 return *(float*)(ptr + offset); 778 } 779 else 780 { 781 uint uvalue = ReverseBytes(*(uint*)(ptr + offset)); 782 return *(float*)(&uvalue); 783 } 784 } 785 } 786 GetDouble(int offset)787 public unsafe double GetDouble(int offset) 788 { 789 AssertOffsetAndLength(offset, sizeof(double)); 790 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 791 fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan)) 792 #else 793 fixed (byte* ptr = _buffer.Buffer) 794 #endif 795 { 796 if (BitConverter.IsLittleEndian) 797 { 798 return *(double*)(ptr + offset); 799 } 800 else 801 { 802 ulong uvalue = ReverseBytes(*(ulong*)(ptr + offset)); 803 return *(double*)(&uvalue); 804 } 805 } 806 } 807 #else // !UNSAFE_BYTEBUFFER 808 // Slower versions of Get* for when unsafe code is not allowed. GetShort(int index)809 public short GetShort(int index) 810 { 811 return (short)ReadLittleEndian(index, sizeof(short)); 812 } 813 GetUshort(int index)814 public ushort GetUshort(int index) 815 { 816 return (ushort)ReadLittleEndian(index, sizeof(ushort)); 817 } 818 GetInt(int index)819 public int GetInt(int index) 820 { 821 return (int)ReadLittleEndian(index, sizeof(int)); 822 } 823 GetUint(int index)824 public uint GetUint(int index) 825 { 826 return (uint)ReadLittleEndian(index, sizeof(uint)); 827 } 828 GetLong(int index)829 public long GetLong(int index) 830 { 831 return (long)ReadLittleEndian(index, sizeof(long)); 832 } 833 GetUlong(int index)834 public ulong GetUlong(int index) 835 { 836 return ReadLittleEndian(index, sizeof(ulong)); 837 } 838 GetFloat(int index)839 public float GetFloat(int index) 840 { 841 // TODO(derekbailey): use BitConvert.Int32BitsToSingle() whenever flatbuffers upgrades to a .NET version 842 // that contains it. 843 ConversionUnion union; 844 union.floatValue = 0; 845 union.intValue = (int)ReadLittleEndian(index, sizeof(float)); 846 return union.floatValue; 847 } 848 GetDouble(int index)849 public double GetDouble(int index) 850 { 851 return BitConverter.Int64BitsToDouble((long)ReadLittleEndian(index, sizeof(double))); 852 } 853 #endif // UNSAFE_BYTEBUFFER 854 855 /// <summary> 856 /// Copies an array of type T into this buffer, ending at the given 857 /// offset into this buffer. The starting offset is calculated based on the length 858 /// of the array and is the value returned. 859 /// </summary> 860 /// <typeparam name="T">The type of the input data (must be a struct)</typeparam> 861 /// <param name="offset">The offset into this buffer where the copy will end</param> 862 /// <param name="x">The array to copy data from</param> 863 /// <returns>The 'start' location of this buffer now, after the copy completed</returns> 864 public int Put<T>(int offset, T[] x) 865 where T : struct 866 { 867 if (x == null) 868 { 869 throw new ArgumentNullException("Cannot put a null array"); 870 } 871 872 if (x.Length == 0) 873 { 874 throw new ArgumentException("Cannot put an empty array"); 875 } 876 877 if (!IsSupportedType<T>()) 878 { 879 throw new ArgumentException("Cannot put an array of type " 880 + typeof(T) + " into this buffer"); 881 } 882 883 if (BitConverter.IsLittleEndian) 884 { 885 int numBytes = ByteBuffer.ArraySize(x); 886 offset -= numBytes; 887 AssertOffsetAndLength(offset, numBytes); 888 // if we are LE, just do a block copy 889 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 890 MemoryMarshal.Cast<T, byte>(x).CopyTo(_buffer.Span.Slice(offset, numBytes)); 891 #else 892 Buffer.BlockCopy(x, 0, _buffer.Buffer, offset, numBytes); 893 #endif 894 } 895 else 896 { 897 throw new NotImplementedException("Big Endian Support not implemented yet " + 898 "for putting typed arrays"); 899 // if we are BE, we have to swap each element by itself 900 //for(int i = x.Length - 1; i >= 0; i--) 901 //{ 902 // todo: low priority, but need to genericize the Put<T>() functions 903 //} 904 } 905 return offset; 906 } 907 908 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 909 public int Put<T>(int offset, Span<T> x) 910 where T : struct 911 { 912 if (x.Length == 0) 913 { 914 throw new ArgumentException("Cannot put an empty array"); 915 } 916 917 if (!IsSupportedType<T>()) 918 { 919 throw new ArgumentException("Cannot put an array of type " 920 + typeof(T) + " into this buffer"); 921 } 922 923 if (BitConverter.IsLittleEndian) 924 { 925 int numBytes = ByteBuffer.ArraySize(x); 926 offset -= numBytes; 927 AssertOffsetAndLength(offset, numBytes); 928 // if we are LE, just do a block copy 929 MemoryMarshal.Cast<T, byte>(x).CopyTo(_buffer.Span.Slice(offset, numBytes)); 930 } 931 else 932 { 933 throw new NotImplementedException("Big Endian Support not implemented yet " + 934 "for putting typed arrays"); 935 // if we are BE, we have to swap each element by itself 936 //for(int i = x.Length - 1; i >= 0; i--) 937 //{ 938 // todo: low priority, but need to genericize the Put<T>() functions 939 //} 940 } 941 return offset; 942 } 943 #endif 944 } 945 } 946