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 18 using System; 19 using System.Collections.Generic; 20 using System.Text; 21 22 /// @file 23 /// @addtogroup flatbuffers_csharp_api 24 /// @{ 25 26 namespace FlatBuffers 27 { 28 /// <summary> 29 /// Responsible for building up and accessing a FlatBuffer formatted byte 30 /// array (via ByteBuffer). 31 /// </summary> 32 public class FlatBufferBuilder 33 { 34 private int _space; 35 private ByteBuffer _bb; 36 private int _minAlign = 1; 37 38 // The vtable for the current table (if _vtableSize >= 0) 39 private int[] _vtable = new int[16]; 40 // The size of the vtable. -1 indicates no vtable 41 private int _vtableSize = -1; 42 // Starting offset of the current struct/table. 43 private int _objectStart; 44 // List of offsets of all vtables. 45 private int[] _vtables = new int[16]; 46 // Number of entries in `vtables` in use. 47 private int _numVtables = 0; 48 // For the current vector being built. 49 private int _vectorNumElems = 0; 50 51 // For CreateSharedString 52 private Dictionary<string, StringOffset> _sharedStringMap = null; 53 54 /// <summary> 55 /// Create a FlatBufferBuilder with a given initial size. 56 /// </summary> 57 /// <param name="initialSize"> 58 /// The initial size to use for the internal buffer. 59 /// </param> FlatBufferBuilder(int initialSize)60 public FlatBufferBuilder(int initialSize) 61 { 62 if (initialSize <= 0) 63 throw new ArgumentOutOfRangeException("initialSize", 64 initialSize, "Must be greater than zero"); 65 _space = initialSize; 66 _bb = new ByteBuffer(initialSize); 67 } 68 69 /// <summary> 70 /// Create a FlatBufferBuilder backed by the pased in ByteBuffer 71 /// </summary> 72 /// <param name="buffer">The ByteBuffer to write to</param> FlatBufferBuilder(ByteBuffer buffer)73 public FlatBufferBuilder(ByteBuffer buffer) 74 { 75 _bb = buffer; 76 _space = buffer.Length; 77 buffer.Reset(); 78 } 79 80 /// <summary> 81 /// Reset the FlatBufferBuilder by purging all data that it holds. 82 /// </summary> Clear()83 public void Clear() 84 { 85 _space = _bb.Length; 86 _bb.Reset(); 87 _minAlign = 1; 88 while (_vtableSize > 0) _vtable[--_vtableSize] = 0; 89 _vtableSize = -1; 90 _objectStart = 0; 91 _numVtables = 0; 92 _vectorNumElems = 0; 93 if (_sharedStringMap != null) 94 { 95 _sharedStringMap.Clear(); 96 } 97 } 98 99 /// <summary> 100 /// Gets and sets a Boolean to disable the optimization when serializing 101 /// default values to a Table. 102 /// 103 /// In order to save space, fields that are set to their default value 104 /// don't get serialized into the buffer. 105 /// </summary> 106 public bool ForceDefaults { get; set; } 107 108 /// @cond FLATBUFFERS_INTERNAL 109 110 public int Offset { get { return _bb.Length - _space; } } 111 Pad(int size)112 public void Pad(int size) 113 { 114 _bb.PutByte(_space -= size, 0, size); 115 } 116 117 // Doubles the size of the ByteBuffer, and copies the old data towards 118 // the end of the new buffer (since we build the buffer backwards). GrowBuffer()119 void GrowBuffer() 120 { 121 _bb.GrowFront(_bb.Length << 1); 122 } 123 124 // Prepare to write an element of `size` after `additional_bytes` 125 // have been written, e.g. if you write a string, you need to align 126 // such the int length field is aligned to SIZEOF_INT, and the string 127 // data follows it directly. 128 // If all you need to do is align, `additional_bytes` will be 0. Prep(int size, int additionalBytes)129 public void Prep(int size, int additionalBytes) 130 { 131 // Track the biggest thing we've ever aligned to. 132 if (size > _minAlign) 133 _minAlign = size; 134 // Find the amount of alignment needed such that `size` is properly 135 // aligned after `additional_bytes` 136 var alignSize = 137 ((~((int)_bb.Length - _space + additionalBytes)) + 1) & 138 (size - 1); 139 // Reallocate the buffer if needed. 140 while (_space < alignSize + size + additionalBytes) 141 { 142 var oldBufSize = (int)_bb.Length; 143 GrowBuffer(); 144 _space += (int)_bb.Length - oldBufSize; 145 146 } 147 if (alignSize > 0) 148 Pad(alignSize); 149 } 150 PutBool(bool x)151 public void PutBool(bool x) 152 { 153 _bb.PutByte(_space -= sizeof(byte), (byte)(x ? 1 : 0)); 154 } 155 PutSbyte(sbyte x)156 public void PutSbyte(sbyte x) 157 { 158 _bb.PutSbyte(_space -= sizeof(sbyte), x); 159 } 160 PutByte(byte x)161 public void PutByte(byte x) 162 { 163 _bb.PutByte(_space -= sizeof(byte), x); 164 } 165 PutShort(short x)166 public void PutShort(short x) 167 { 168 _bb.PutShort(_space -= sizeof(short), x); 169 } 170 PutUshort(ushort x)171 public void PutUshort(ushort x) 172 { 173 _bb.PutUshort(_space -= sizeof(ushort), x); 174 } 175 PutInt(int x)176 public void PutInt(int x) 177 { 178 _bb.PutInt(_space -= sizeof(int), x); 179 } 180 PutUint(uint x)181 public void PutUint(uint x) 182 { 183 _bb.PutUint(_space -= sizeof(uint), x); 184 } 185 PutLong(long x)186 public void PutLong(long x) 187 { 188 _bb.PutLong(_space -= sizeof(long), x); 189 } 190 PutUlong(ulong x)191 public void PutUlong(ulong x) 192 { 193 _bb.PutUlong(_space -= sizeof(ulong), x); 194 } 195 PutFloat(float x)196 public void PutFloat(float x) 197 { 198 _bb.PutFloat(_space -= sizeof(float), x); 199 } 200 201 /// <summary> 202 /// Puts an array of type T into this builder at the 203 /// current offset 204 /// </summary> 205 /// <typeparam name="T">The type of the input data </typeparam> 206 /// <param name="x">The array to copy data from</param> 207 public void Put<T>(T[] x) 208 where T : struct 209 { 210 _space = _bb.Put(_space, x); 211 } 212 213 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 214 /// <summary> 215 /// Puts a span of type T into this builder at the 216 /// current offset 217 /// </summary> 218 /// <typeparam name="T">The type of the input data </typeparam> 219 /// <param name="x">The span to copy data from</param> 220 public void Put<T>(Span<T> x) 221 where T : struct 222 { 223 _space = _bb.Put(_space, x); 224 } 225 #endif 226 PutDouble(double x)227 public void PutDouble(double x) 228 { 229 _bb.PutDouble(_space -= sizeof(double), x); 230 } 231 /// @endcond 232 233 /// <summary> 234 /// Add a `bool` to the buffer (aligns the data and grows if necessary). 235 /// </summary> 236 /// <param name="x">The `bool` to add to the buffer.</param> AddBool(bool x)237 public void AddBool(bool x) { Prep(sizeof(byte), 0); PutBool(x); } 238 239 /// <summary> 240 /// Add a `sbyte` to the buffer (aligns the data and grows if necessary). 241 /// </summary> 242 /// <param name="x">The `sbyte` to add to the buffer.</param> AddSbyte(sbyte x)243 public void AddSbyte(sbyte x) { Prep(sizeof(sbyte), 0); PutSbyte(x); } 244 245 /// <summary> 246 /// Add a `byte` to the buffer (aligns the data and grows if necessary). 247 /// </summary> 248 /// <param name="x">The `byte` to add to the buffer.</param> AddByte(byte x)249 public void AddByte(byte x) { Prep(sizeof(byte), 0); PutByte(x); } 250 251 /// <summary> 252 /// Add a `short` to the buffer (aligns the data and grows if necessary). 253 /// </summary> 254 /// <param name="x">The `short` to add to the buffer.</param> AddShort(short x)255 public void AddShort(short x) { Prep(sizeof(short), 0); PutShort(x); } 256 257 /// <summary> 258 /// Add an `ushort` to the buffer (aligns the data and grows if necessary). 259 /// </summary> 260 /// <param name="x">The `ushort` to add to the buffer.</param> AddUshort(ushort x)261 public void AddUshort(ushort x) { Prep(sizeof(ushort), 0); PutUshort(x); } 262 263 /// <summary> 264 /// Add an `int` to the buffer (aligns the data and grows if necessary). 265 /// </summary> 266 /// <param name="x">The `int` to add to the buffer.</param> AddInt(int x)267 public void AddInt(int x) { Prep(sizeof(int), 0); PutInt(x); } 268 269 /// <summary> 270 /// Add an `uint` to the buffer (aligns the data and grows if necessary). 271 /// </summary> 272 /// <param name="x">The `uint` to add to the buffer.</param> AddUint(uint x)273 public void AddUint(uint x) { Prep(sizeof(uint), 0); PutUint(x); } 274 275 /// <summary> 276 /// Add a `long` to the buffer (aligns the data and grows if necessary). 277 /// </summary> 278 /// <param name="x">The `long` to add to the buffer.</param> AddLong(long x)279 public void AddLong(long x) { Prep(sizeof(long), 0); PutLong(x); } 280 281 /// <summary> 282 /// Add an `ulong` to the buffer (aligns the data and grows if necessary). 283 /// </summary> 284 /// <param name="x">The `ulong` to add to the buffer.</param> AddUlong(ulong x)285 public void AddUlong(ulong x) { Prep(sizeof(ulong), 0); PutUlong(x); } 286 287 /// <summary> 288 /// Add a `float` to the buffer (aligns the data and grows if necessary). 289 /// </summary> 290 /// <param name="x">The `float` to add to the buffer.</param> AddFloat(float x)291 public void AddFloat(float x) { Prep(sizeof(float), 0); PutFloat(x); } 292 293 /// <summary> 294 /// Add an array of type T to the buffer (aligns the data and grows if necessary). 295 /// </summary> 296 /// <typeparam name="T">The type of the input data</typeparam> 297 /// <param name="x">The array to copy data from</param> 298 public void Add<T>(T[] x) 299 where T : struct 300 { 301 if (x == null) 302 { 303 throw new ArgumentNullException("Cannot add a null array"); 304 } 305 306 if( x.Length == 0) 307 { 308 // don't do anything if the array is empty 309 return; 310 } 311 312 if(!ByteBuffer.IsSupportedType<T>()) 313 { 314 throw new ArgumentException("Cannot add this Type array to the builder"); 315 } 316 317 int size = ByteBuffer.SizeOf<T>(); 318 // Need to prep on size (for data alignment) and then we pass the 319 // rest of the length (minus 1) as additional bytes PrepFlatBuffers.FlatBufferBuilder.__anon3320 Prep(size, size * (x.Length - 1)); PutFlatBuffers.FlatBufferBuilder.__anon3321 Put(x); 322 } 323 324 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 325 /// <summary> 326 /// Add a span of type T to the buffer (aligns the data and grows if necessary). 327 /// </summary> 328 /// <typeparam name="T">The type of the input data</typeparam> 329 /// <param name="x">The span to copy data from</param> 330 public void Add<T>(Span<T> x) 331 where T : struct 332 { 333 if (!ByteBuffer.IsSupportedType<T>()) 334 { 335 throw new ArgumentException("Cannot add this Type array to the builder"); 336 } 337 338 int size = ByteBuffer.SizeOf<T>(); 339 // Need to prep on size (for data alignment) and then we pass the 340 // rest of the length (minus 1) as additional bytes PrepFlatBuffers.FlatBufferBuilder.__anon4341 Prep(size, size * (x.Length - 1)); PutFlatBuffers.FlatBufferBuilder.__anon4342 Put(x); 343 } 344 #endif 345 346 /// <summary> 347 /// Add a `double` to the buffer (aligns the data and grows if necessary). 348 /// </summary> 349 /// <param name="x">The `double` to add to the buffer.</param> AddDouble(double x)350 public void AddDouble(double x) { Prep(sizeof(double), 0); 351 PutDouble(x); } 352 353 /// <summary> 354 /// Adds an offset, relative to where it will be written. 355 /// </summary> 356 /// <param name="off">The offset to add to the buffer.</param> AddOffset(int off)357 public void AddOffset(int off) 358 { 359 Prep(sizeof(int), 0); // Ensure alignment is already done. 360 if (off > Offset) 361 throw new ArgumentException(); 362 363 off = Offset - off + sizeof(int); 364 PutInt(off); 365 } 366 367 /// @cond FLATBUFFERS_INTERNAL StartVector(int elemSize, int count, int alignment)368 public void StartVector(int elemSize, int count, int alignment) 369 { 370 NotNested(); 371 _vectorNumElems = count; 372 Prep(sizeof(int), elemSize * count); 373 Prep(alignment, elemSize * count); // Just in case alignment > int. 374 } 375 /// @endcond 376 377 /// <summary> 378 /// Writes data necessary to finish a vector construction. 379 /// </summary> EndVector()380 public VectorOffset EndVector() 381 { 382 PutInt(_vectorNumElems); 383 return new VectorOffset(Offset); 384 } 385 386 /// <summary> 387 /// Creates a vector of tables. 388 /// </summary> 389 /// <param name="offsets">Offsets of the tables.</param> 390 public VectorOffset CreateVectorOfTables<T>(Offset<T>[] offsets) where T : struct 391 { NotNestedFlatBuffers.FlatBufferBuilder.__anon5392 NotNested(); sizeofFlatBuffers.FlatBufferBuilder.__anon5393 StartVector(sizeof(int), offsets.Length, sizeof(int)); AddOffsetFlatBuffers.FlatBufferBuilder.__anon5394 for (int i = offsets.Length - 1; i >= 0; i--) AddOffset(offsets[i].Value); 395 return EndVector(); 396 } 397 398 /// @cond FLATBUFFERS_INTENRAL Nested(int obj)399 public void Nested(int obj) 400 { 401 // Structs are always stored inline, so need to be created right 402 // where they are used. You'll get this assert if you created it 403 // elsewhere. 404 if (obj != Offset) 405 throw new Exception( 406 "FlatBuffers: struct must be serialized inline."); 407 } 408 NotNested()409 public void NotNested() 410 { 411 // You should not be creating any other objects or strings/vectors 412 // while an object is being constructed 413 if (_vtableSize >= 0) 414 throw new Exception( 415 "FlatBuffers: object serialization must not be nested."); 416 } 417 StartTable(int numfields)418 public void StartTable(int numfields) 419 { 420 if (numfields < 0) 421 throw new ArgumentOutOfRangeException("Flatbuffers: invalid numfields"); 422 423 NotNested(); 424 425 if (_vtable.Length < numfields) 426 _vtable = new int[numfields]; 427 428 _vtableSize = numfields; 429 _objectStart = Offset; 430 } 431 432 433 // Set the current vtable at `voffset` to the current location in the 434 // buffer. Slot(int voffset)435 public void Slot(int voffset) 436 { 437 if (voffset >= _vtableSize) 438 throw new IndexOutOfRangeException("Flatbuffers: invalid voffset"); 439 440 _vtable[voffset] = Offset; 441 } 442 443 /// <summary> 444 /// Adds a Boolean to the Table at index `o` in its vtable using the value `x` and default `d` 445 /// </summary> 446 /// <param name="o">The index into the vtable</param> 447 /// <param name="x">The value to put into the buffer. If the value is equal to the default 448 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 449 /// <param name="d">The default value to compare the value against</param> AddBool(int o, bool x, bool d)450 public void AddBool(int o, bool x, bool d) { if (ForceDefaults || x != d) { AddBool(x); Slot(o); } } 451 452 /// <summary> 453 /// Adds a Boolean to the Table at index `o` in its vtable using the nullable value `x` 454 /// </summary> 455 /// <param name="o">The index into the vtable</param> 456 /// <param name="x">The nullable boolean value to put into the buffer. If it doesn't have a value 457 /// it will skip writing to the buffer.</param> AddBool(int o, bool? x)458 public void AddBool(int o, bool? x) { if (x.HasValue) { AddBool(x.Value); Slot(o); } } 459 460 461 /// <summary> 462 /// Adds a SByte to the Table at index `o` in its vtable using the value `x` and default `d` 463 /// </summary> 464 /// <param name="o">The index into the vtable</param> 465 /// <param name="x">The value to put into the buffer. If the value is equal to the default 466 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 467 /// <param name="d">The default value to compare the value against</param> AddSbyte(int o, sbyte x, sbyte d)468 public void AddSbyte(int o, sbyte x, sbyte d) { if (ForceDefaults || x != d) { AddSbyte(x); Slot(o); } } 469 470 /// <summary> 471 /// Adds a SByte to the Table at index `o` in its vtable using the nullable value `x` 472 /// </summary> 473 /// <param name="o">The index into the vtable</param> 474 /// <param name="x">The nullable sbyte value to put into the buffer. If it doesn't have a value 475 /// it will skip writing to the buffer.</param> AddSbyte(int o, sbyte? x)476 public void AddSbyte(int o, sbyte? x) { if (x.HasValue) { AddSbyte(x.Value); Slot(o); } } 477 478 /// <summary> 479 /// Adds a Byte to the Table at index `o` in its vtable using the value `x` and default `d` 480 /// </summary> 481 /// <param name="o">The index into the vtable</param> 482 /// <param name="x">The value to put into the buffer. If the value is equal to the default 483 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 484 /// <param name="d">The default value to compare the value against</param> AddByte(int o, byte x, byte d)485 public void AddByte(int o, byte x, byte d) { if (ForceDefaults || x != d) { AddByte(x); Slot(o); } } 486 487 /// <summary> 488 /// Adds a Byte to the Table at index `o` in its vtable using the nullable value `x` 489 /// </summary> 490 /// <param name="o">The index into the vtable</param> 491 /// <param name="x">The nullable byte value to put into the buffer. If it doesn't have a value 492 /// it will skip writing to the buffer.</param> AddByte(int o, byte? x)493 public void AddByte(int o, byte? x) { if (x.HasValue) { AddByte(x.Value); Slot(o); } } 494 495 /// <summary> 496 /// Adds a Int16 to the Table at index `o` in its vtable using the value `x` and default `d` 497 /// </summary> 498 /// <param name="o">The index into the vtable</param> 499 /// <param name="x">The value to put into the buffer. If the value is equal to the default 500 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 501 /// <param name="d">The default value to compare the value against</param> AddShort(int o, short x, int d)502 public void AddShort(int o, short x, int d) { if (ForceDefaults || x != d) { AddShort(x); Slot(o); } } 503 504 /// <summary> 505 /// Adds a Int16 to the Table at index `o` in its vtable using the nullable value `x` 506 /// </summary> 507 /// <param name="o">The index into the vtable</param> 508 /// <param name="x">The nullable int16 value to put into the buffer. If it doesn't have a value 509 /// it will skip writing to the buffer.</param> AddShort(int o, short? x)510 public void AddShort(int o, short? x) { if (x.HasValue) { AddShort(x.Value); Slot(o); } } 511 512 /// <summary> 513 /// Adds a UInt16 to the Table at index `o` in its vtable using the value `x` and default `d` 514 /// </summary> 515 /// <param name="o">The index into the vtable</param> 516 /// <param name="x">The value to put into the buffer. If the value is equal to the default 517 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 518 /// <param name="d">The default value to compare the value against</param> AddUshort(int o, ushort x, ushort d)519 public void AddUshort(int o, ushort x, ushort d) { if (ForceDefaults || x != d) { AddUshort(x); Slot(o); } } 520 521 /// <summary> 522 /// Adds a Uint16 to the Table at index `o` in its vtable using the nullable value `x` 523 /// </summary> 524 /// <param name="o">The index into the vtable</param> 525 /// <param name="x">The nullable uint16 value to put into the buffer. If it doesn't have a value 526 /// it will skip writing to the buffer.</param> AddUshort(int o, ushort? x)527 public void AddUshort(int o, ushort? x) { if (x.HasValue) { AddUshort(x.Value); Slot(o); } } 528 529 /// <summary> 530 /// Adds an Int32 to the Table at index `o` in its vtable using the value `x` and default `d` 531 /// </summary> 532 /// <param name="o">The index into the vtable</param> 533 /// <param name="x">The value to put into the buffer. If the value is equal to the default 534 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 535 /// <param name="d">The default value to compare the value against</param> AddInt(int o, int x, int d)536 public void AddInt(int o, int x, int d) { if (ForceDefaults || x != d) { AddInt(x); Slot(o); } } 537 538 /// <summary> 539 /// Adds a Int32 to the Table at index `o` in its vtable using the nullable value `x` 540 /// </summary> 541 /// <param name="o">The index into the vtable</param> 542 /// <param name="x">The nullable int32 value to put into the buffer. If it doesn't have a value 543 /// it will skip writing to the buffer.</param> AddInt(int o, int? x)544 public void AddInt(int o, int? x) { if (x.HasValue) { AddInt(x.Value); Slot(o); } } 545 546 /// <summary> 547 /// Adds a UInt32 to the Table at index `o` in its vtable using the value `x` and default `d` 548 /// </summary> 549 /// <param name="o">The index into the vtable</param> 550 /// <param name="x">The value to put into the buffer. If the value is equal to the default 551 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 552 /// <param name="d">The default value to compare the value against</param> AddUint(int o, uint x, uint d)553 public void AddUint(int o, uint x, uint d) { if (ForceDefaults || x != d) { AddUint(x); Slot(o); } } 554 555 /// <summary> 556 /// Adds a UInt32 to the Table at index `o` in its vtable using the nullable value `x` 557 /// </summary> 558 /// <param name="o">The index into the vtable</param> 559 /// <param name="x">The nullable uint32 value to put into the buffer. If it doesn't have a value 560 /// it will skip writing to the buffer.</param> AddUint(int o, uint? x)561 public void AddUint(int o, uint? x) { if (x.HasValue) { AddUint(x.Value); Slot(o); } } 562 563 /// <summary> 564 /// Adds an Int64 to the Table at index `o` in its vtable using the value `x` and default `d` 565 /// </summary> 566 /// <param name="o">The index into the vtable</param> 567 /// <param name="x">The value to put into the buffer. If the value is equal to the default 568 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 569 /// <param name="d">The default value to compare the value against</param> AddLong(int o, long x, long d)570 public void AddLong(int o, long x, long d) { if (ForceDefaults || x != d) { AddLong(x); Slot(o); } } 571 572 /// <summary> 573 /// Adds a Int64 to the Table at index `o` in its vtable using the nullable value `x` 574 /// </summary> 575 /// <param name="o">The index into the vtable</param> 576 /// <param name="x">The nullable int64 value to put into the buffer. If it doesn't have a value 577 /// it will skip writing to the buffer.</param> AddLong(int o, long? x)578 public void AddLong(int o, long? x) { if (x.HasValue) { AddLong(x.Value); Slot(o); } } 579 580 /// <summary> 581 /// Adds a UInt64 to the Table at index `o` in its vtable using the value `x` and default `d` 582 /// </summary> 583 /// <param name="o">The index into the vtable</param> 584 /// <param name="x">The value to put into the buffer. If the value is equal to the default 585 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 586 /// <param name="d">The default value to compare the value against</param> AddUlong(int o, ulong x, ulong d)587 public void AddUlong(int o, ulong x, ulong d) { if (ForceDefaults || x != d) { AddUlong(x); Slot(o); } } 588 589 /// <summary> 590 /// Adds a UInt64 to the Table at index `o` in its vtable using the nullable value `x` 591 /// </summary> 592 /// <param name="o">The index into the vtable</param> 593 /// <param name="x">The nullable int64 value to put into the buffer. If it doesn't have a value 594 /// it will skip writing to the buffer.</param> AddUlong(int o, ulong? x)595 public void AddUlong(int o, ulong? x) { if (x.HasValue) { AddUlong(x.Value); Slot(o); } } 596 597 /// <summary> 598 /// Adds a Single to the Table at index `o` in its vtable using the value `x` and default `d` 599 /// </summary> 600 /// <param name="o">The index into the vtable</param> 601 /// <param name="x">The value to put into the buffer. If the value is equal to the default 602 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 603 /// <param name="d">The default value to compare the value against</param> AddFloat(int o, float x, double d)604 public void AddFloat(int o, float x, double d) { if (ForceDefaults || x != d) { AddFloat(x); Slot(o); } } 605 606 /// <summary> 607 /// Adds a Single to the Table at index `o` in its vtable using the nullable value `x` 608 /// </summary> 609 /// <param name="o">The index into the vtable</param> 610 /// <param name="x">The nullable single value to put into the buffer. If it doesn't have a value 611 /// it will skip writing to the buffer.</param> AddFloat(int o, float? x)612 public void AddFloat(int o, float? x) { if (x.HasValue) { AddFloat(x.Value); Slot(o); } } 613 614 /// <summary> 615 /// Adds a Double to the Table at index `o` in its vtable using the value `x` and default `d` 616 /// </summary> 617 /// <param name="o">The index into the vtable</param> 618 /// <param name="x">The value to put into the buffer. If the value is equal to the default 619 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 620 /// <param name="d">The default value to compare the value against</param> AddDouble(int o, double x, double d)621 public void AddDouble(int o, double x, double d) { if (ForceDefaults || x != d) { AddDouble(x); Slot(o); } } 622 623 /// <summary> 624 /// Adds a Double to the Table at index `o` in its vtable using the nullable value `x` 625 /// </summary> 626 /// <param name="o">The index into the vtable</param> 627 /// <param name="x">The nullable double value to put into the buffer. If it doesn't have a value 628 /// it will skip writing to the buffer.</param> AddDouble(int o, double? x)629 public void AddDouble(int o, double? x) { if (x.HasValue) { AddDouble(x.Value); Slot(o); } } 630 631 /// <summary> 632 /// Adds a buffer offset to the Table at index `o` in its vtable using the value `x` and default `d` 633 /// </summary> 634 /// <param name="o">The index into the vtable</param> 635 /// <param name="x">The value to put into the buffer. If the value is equal to the default 636 /// the value will be skipped.</param> 637 /// <param name="d">The default value to compare the value against</param> AddOffset(int o, int x, int d)638 public void AddOffset(int o, int x, int d) { if (x != d) { AddOffset(x); Slot(o); } } 639 /// @endcond 640 641 /// <summary> 642 /// Encode the string `s` in the buffer using UTF-8. 643 /// </summary> 644 /// <param name="s">The string to encode.</param> 645 /// <returns> 646 /// The offset in the buffer where the encoded string starts. 647 /// </returns> CreateString(string s)648 public StringOffset CreateString(string s) 649 { 650 if (s == null) 651 { 652 return new StringOffset(0); 653 } 654 NotNested(); 655 AddByte(0); 656 var utf8StringLen = Encoding.UTF8.GetByteCount(s); 657 StartVector(1, utf8StringLen, 1); 658 _bb.PutStringUTF8(_space -= utf8StringLen, s); 659 return new StringOffset(EndVector().Value); 660 } 661 662 663 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 664 /// <summary> 665 /// Creates a string in the buffer from a Span containing 666 /// a UTF8 string. 667 /// </summary> 668 /// <param name="chars">the UTF8 string to add to the buffer</param> 669 /// <returns> 670 /// The offset in the buffer where the encoded string starts. 671 /// </returns> CreateUTF8String(Span<byte> chars)672 public StringOffset CreateUTF8String(Span<byte> chars) 673 { 674 NotNested(); 675 AddByte(0); 676 var utf8StringLen = chars.Length; 677 StartVector(1, utf8StringLen, 1); 678 _space = _bb.Put(_space, chars); 679 return new StringOffset(EndVector().Value); 680 } 681 #endif 682 683 /// <summary> 684 /// Store a string in the buffer, which can contain any binary data. 685 /// If a string with this exact contents has already been serialized before, 686 /// instead simply returns the offset of the existing string. 687 /// </summary> 688 /// <param name="s">The string to encode.</param> 689 /// <returns> 690 /// The offset in the buffer where the encoded string starts. 691 /// </returns> CreateSharedString(string s)692 public StringOffset CreateSharedString(string s) 693 { 694 if (s == null) 695 { 696 return new StringOffset(0); 697 } 698 699 if (_sharedStringMap == null) 700 { 701 _sharedStringMap = new Dictionary<string, StringOffset>(); 702 } 703 704 if (_sharedStringMap.ContainsKey(s)) 705 { 706 return _sharedStringMap[s]; 707 } 708 709 var stringOffset = CreateString(s); 710 _sharedStringMap.Add(s, stringOffset); 711 return stringOffset; 712 } 713 714 /// @cond FLATBUFFERS_INTERNAL 715 // Structs are stored inline, so nothing additional is being added. 716 // `d` is always 0. AddStruct(int voffset, int x, int d)717 public void AddStruct(int voffset, int x, int d) 718 { 719 if (x != d) 720 { 721 Nested(x); 722 Slot(voffset); 723 } 724 } 725 EndTable()726 public int EndTable() 727 { 728 if (_vtableSize < 0) 729 throw new InvalidOperationException( 730 "Flatbuffers: calling EndTable without a StartTable"); 731 732 AddInt((int)0); 733 var vtableloc = Offset; 734 // Write out the current vtable. 735 int i = _vtableSize - 1; 736 // Trim trailing zeroes. 737 for (; i >= 0 && _vtable[i] == 0; i--) {} 738 int trimmedSize = i + 1; 739 for (; i >= 0 ; i--) { 740 // Offset relative to the start of the table. 741 short off = (short)(_vtable[i] != 0 742 ? vtableloc - _vtable[i] 743 : 0); 744 AddShort(off); 745 746 // clear out written entry 747 _vtable[i] = 0; 748 } 749 750 const int standardFields = 2; // The fields below: 751 AddShort((short)(vtableloc - _objectStart)); 752 AddShort((short)((trimmedSize + standardFields) * 753 sizeof(short))); 754 755 // Search for an existing vtable that matches the current one. 756 int existingVtable = 0; 757 for (i = 0; i < _numVtables; i++) { 758 int vt1 = _bb.Length - _vtables[i]; 759 int vt2 = _space; 760 short len = _bb.GetShort(vt1); 761 if (len == _bb.GetShort(vt2)) { 762 for (int j = sizeof(short); j < len; j += sizeof(short)) { 763 if (_bb.GetShort(vt1 + j) != _bb.GetShort(vt2 + j)) { 764 goto endLoop; 765 } 766 } 767 existingVtable = _vtables[i]; 768 break; 769 } 770 771 endLoop: { } 772 } 773 774 if (existingVtable != 0) { 775 // Found a match: 776 // Remove the current vtable. 777 _space = _bb.Length - vtableloc; 778 // Point table to existing vtable. 779 _bb.PutInt(_space, existingVtable - vtableloc); 780 } else { 781 // No match: 782 // Add the location of the current vtable to the list of 783 // vtables. 784 if (_numVtables == _vtables.Length) 785 { 786 // Arrays.CopyOf(vtables num_vtables * 2); 787 var newvtables = new int[ _numVtables * 2]; 788 Array.Copy(_vtables, newvtables, _vtables.Length); 789 790 _vtables = newvtables; 791 }; 792 _vtables[_numVtables++] = Offset; 793 // Point table to current vtable. 794 _bb.PutInt(_bb.Length - vtableloc, Offset - vtableloc); 795 } 796 797 _vtableSize = -1; 798 return vtableloc; 799 } 800 801 // This checks a required field has been set in a given table that has 802 // just been constructed. Required(int table, int field)803 public void Required(int table, int field) 804 { 805 int table_start = _bb.Length - table; 806 int vtable_start = table_start - _bb.GetInt(table_start); 807 bool ok = _bb.GetShort(vtable_start + field) != 0; 808 // If this fails, the caller will show what field needs to be set. 809 if (!ok) 810 throw new InvalidOperationException("FlatBuffers: field " + field + 811 " must be set"); 812 } 813 /// @endcond 814 815 /// <summary> 816 /// Finalize a buffer, pointing to the given `root_table`. 817 /// </summary> 818 /// <param name="rootTable"> 819 /// An offset to be added to the buffer. 820 /// </param> 821 /// <param name="sizePrefix"> 822 /// Whether to prefix the size to the buffer. 823 /// </param> Finish(int rootTable, bool sizePrefix)824 protected void Finish(int rootTable, bool sizePrefix) 825 { 826 Prep(_minAlign, sizeof(int) + (sizePrefix ? sizeof(int) : 0)); 827 AddOffset(rootTable); 828 if (sizePrefix) { 829 AddInt(_bb.Length - _space); 830 } 831 _bb.Position = _space; 832 } 833 834 /// <summary> 835 /// Finalize a buffer, pointing to the given `root_table`. 836 /// </summary> 837 /// <param name="rootTable"> 838 /// An offset to be added to the buffer. 839 /// </param> Finish(int rootTable)840 public void Finish(int rootTable) 841 { 842 Finish(rootTable, false); 843 } 844 845 /// <summary> 846 /// Finalize a buffer, pointing to the given `root_table`, with the size prefixed. 847 /// </summary> 848 /// <param name="rootTable"> 849 /// An offset to be added to the buffer. 850 /// </param> FinishSizePrefixed(int rootTable)851 public void FinishSizePrefixed(int rootTable) 852 { 853 Finish(rootTable, true); 854 } 855 856 /// <summary> 857 /// Get the ByteBuffer representing the FlatBuffer. 858 /// </summary> 859 /// <remarks> 860 /// This is typically only called after you call `Finish()`. 861 /// The actual data starts at the ByteBuffer's current position, 862 /// not necessarily at `0`. 863 /// </remarks> 864 /// <returns> 865 /// Returns the ByteBuffer for this FlatBuffer. 866 /// </returns> 867 public ByteBuffer DataBuffer { get { return _bb; } } 868 869 /// <summary> 870 /// A utility function to copy and return the ByteBuffer data as a 871 /// `byte[]`. 872 /// </summary> 873 /// <returns> 874 /// A full copy of the FlatBuffer data. 875 /// </returns> SizedByteArray()876 public byte[] SizedByteArray() 877 { 878 return _bb.ToSizedArray(); 879 } 880 881 /// <summary> 882 /// Finalize a buffer, pointing to the given `rootTable`. 883 /// </summary> 884 /// <param name="rootTable"> 885 /// An offset to be added to the buffer. 886 /// </param> 887 /// <param name="fileIdentifier"> 888 /// A FlatBuffer file identifier to be added to the buffer before 889 /// `root_table`. 890 /// </param> 891 /// <param name="sizePrefix"> 892 /// Whether to prefix the size to the buffer. 893 /// </param> Finish(int rootTable, string fileIdentifier, bool sizePrefix)894 protected void Finish(int rootTable, string fileIdentifier, bool sizePrefix) 895 { 896 Prep(_minAlign, sizeof(int) + (sizePrefix ? sizeof(int) : 0) + 897 FlatBufferConstants.FileIdentifierLength); 898 if (fileIdentifier.Length != 899 FlatBufferConstants.FileIdentifierLength) 900 throw new ArgumentException( 901 "FlatBuffers: file identifier must be length " + 902 FlatBufferConstants.FileIdentifierLength, 903 "fileIdentifier"); 904 for (int i = FlatBufferConstants.FileIdentifierLength - 1; i >= 0; 905 i--) 906 { 907 AddByte((byte)fileIdentifier[i]); 908 } 909 Finish(rootTable, sizePrefix); 910 } 911 912 /// <summary> 913 /// Finalize a buffer, pointing to the given `rootTable`. 914 /// </summary> 915 /// <param name="rootTable"> 916 /// An offset to be added to the buffer. 917 /// </param> 918 /// <param name="fileIdentifier"> 919 /// A FlatBuffer file identifier to be added to the buffer before 920 /// `root_table`. 921 /// </param> Finish(int rootTable, string fileIdentifier)922 public void Finish(int rootTable, string fileIdentifier) 923 { 924 Finish(rootTable, fileIdentifier, false); 925 } 926 927 /// <summary> 928 /// Finalize a buffer, pointing to the given `rootTable`, with the size prefixed. 929 /// </summary> 930 /// <param name="rootTable"> 931 /// An offset to be added to the buffer. 932 /// </param> 933 /// <param name="fileIdentifier"> 934 /// A FlatBuffer file identifier to be added to the buffer before 935 /// `root_table`. 936 /// </param> FinishSizePrefixed(int rootTable, string fileIdentifier)937 public void FinishSizePrefixed(int rootTable, string fileIdentifier) 938 { 939 Finish(rootTable, fileIdentifier, true); 940 } 941 } 942 } 943 944 /// @} 945