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 18using System; 19using System.Text; 20 21/// @file 22/// @addtogroup flatbuffers_csharp_api 23/// @{ 24 25namespace FlatBuffers 26{ 27 /// <summary> 28 /// Responsible for building up and accessing a FlatBuffer formatted byte 29 /// array (via ByteBuffer). 30 /// </summary> 31 public class FlatBufferBuilder 32 { 33 private int _space; 34 private ByteBuffer _bb; 35 private int _minAlign = 1; 36 37 // The vtable for the current table (if _vtableSize >= 0) 38 private int[] _vtable = new int[16]; 39 // The size of the vtable. -1 indicates no vtable 40 private int _vtableSize = -1; 41 // Starting offset of the current struct/table. 42 private int _objectStart; 43 // List of offsets of all vtables. 44 private int[] _vtables = new int[16]; 45 // Number of entries in `vtables` in use. 46 private int _numVtables = 0; 47 // For the current vector being built. 48 private int _vectorNumElems = 0; 49 50 /// <summary> 51 /// Create a FlatBufferBuilder with a given initial size. 52 /// </summary> 53 /// <param name="initialSize"> 54 /// The initial size to use for the internal buffer. 55 /// </param> 56 public FlatBufferBuilder(int initialSize) 57 { 58 if (initialSize <= 0) 59 throw new ArgumentOutOfRangeException("initialSize", 60 initialSize, "Must be greater than zero"); 61 _space = initialSize; 62 _bb = new ByteBuffer(new byte[initialSize]); 63 } 64 65 /// <summary> 66 /// Reset the FlatBufferBuilder by purging all data that it holds. 67 /// </summary> 68 public void Clear() 69 { 70 _space = _bb.Length; 71 _bb.Reset(); 72 _minAlign = 1; 73 while (_vtableSize > 0) _vtable[--_vtableSize] = 0; 74 _vtableSize = -1; 75 _objectStart = 0; 76 _numVtables = 0; 77 _vectorNumElems = 0; 78 } 79 80 /// <summary> 81 /// Gets and sets a Boolean to disable the optimization when serializing 82 /// default values to a Table. 83 /// 84 /// In order to save space, fields that are set to their default value 85 /// don't get serialized into the buffer. 86 /// </summary> 87 public bool ForceDefaults { get; set; } 88 89 /// @cond FLATBUFFERS_INTERNAL 90 91 public int Offset { get { return _bb.Length - _space; } } 92 93 public void Pad(int size) 94 { 95 _bb.PutByte(_space -= size, 0, size); 96 } 97 98 // Doubles the size of the ByteBuffer, and copies the old data towards 99 // the end of the new buffer (since we build the buffer backwards). 100 void GrowBuffer() 101 { 102 var oldBuf = _bb.Data; 103 var oldBufSize = oldBuf.Length; 104 if ((oldBufSize & 0xC0000000) != 0) 105 throw new Exception( 106 "FlatBuffers: cannot grow buffer beyond 2 gigabytes."); 107 108 var newBufSize = oldBufSize << 1; 109 var newBuf = new byte[newBufSize]; 110 111 Buffer.BlockCopy(oldBuf, 0, newBuf, newBufSize - oldBufSize, 112 oldBufSize); 113 _bb = new ByteBuffer(newBuf, newBufSize); 114 } 115 116 // Prepare to write an element of `size` after `additional_bytes` 117 // have been written, e.g. if you write a string, you need to align 118 // such the int length field is aligned to SIZEOF_INT, and the string 119 // data follows it directly. 120 // If all you need to do is align, `additional_bytes` will be 0. 121 public void Prep(int size, int additionalBytes) 122 { 123 // Track the biggest thing we've ever aligned to. 124 if (size > _minAlign) 125 _minAlign = size; 126 // Find the amount of alignment needed such that `size` is properly 127 // aligned after `additional_bytes` 128 var alignSize = 129 ((~((int)_bb.Length - _space + additionalBytes)) + 1) & 130 (size - 1); 131 // Reallocate the buffer if needed. 132 while (_space < alignSize + size + additionalBytes) 133 { 134 var oldBufSize = (int)_bb.Length; 135 GrowBuffer(); 136 _space += (int)_bb.Length - oldBufSize; 137 138 } 139 if (alignSize > 0) 140 Pad(alignSize); 141 } 142 143 public void PutBool(bool x) 144 { 145 _bb.PutByte(_space -= sizeof(byte), (byte)(x ? 1 : 0)); 146 } 147 148 public void PutSbyte(sbyte x) 149 { 150 _bb.PutSbyte(_space -= sizeof(sbyte), x); 151 } 152 153 public void PutByte(byte x) 154 { 155 _bb.PutByte(_space -= sizeof(byte), x); 156 } 157 158 public void PutShort(short x) 159 { 160 _bb.PutShort(_space -= sizeof(short), x); 161 } 162 163 public void PutUshort(ushort x) 164 { 165 _bb.PutUshort(_space -= sizeof(ushort), x); 166 } 167 168 public void PutInt(int x) 169 { 170 _bb.PutInt(_space -= sizeof(int), x); 171 } 172 173 public void PutUint(uint x) 174 { 175 _bb.PutUint(_space -= sizeof(uint), x); 176 } 177 178 public void PutLong(long x) 179 { 180 _bb.PutLong(_space -= sizeof(long), x); 181 } 182 183 public void PutUlong(ulong x) 184 { 185 _bb.PutUlong(_space -= sizeof(ulong), x); 186 } 187 188 public void PutFloat(float x) 189 { 190 _bb.PutFloat(_space -= sizeof(float), x); 191 } 192 193 public void PutDouble(double x) 194 { 195 _bb.PutDouble(_space -= sizeof(double), x); 196 } 197 /// @endcond 198 199 /// <summary> 200 /// Add a `bool` to the buffer (aligns the data and grows if necessary). 201 /// </summary> 202 /// <param name="x">The `bool` to add to the buffer.</param> 203 public void AddBool(bool x) { Prep(sizeof(byte), 0); PutBool(x); } 204 205 /// <summary> 206 /// Add a `sbyte` to the buffer (aligns the data and grows if necessary). 207 /// </summary> 208 /// <param name="x">The `sbyte` to add to the buffer.</param> 209 public void AddSbyte(sbyte x) { Prep(sizeof(sbyte), 0); PutSbyte(x); } 210 211 /// <summary> 212 /// Add a `byte` to the buffer (aligns the data and grows if necessary). 213 /// </summary> 214 /// <param name="x">The `byte` to add to the buffer.</param> 215 public void AddByte(byte x) { Prep(sizeof(byte), 0); PutByte(x); } 216 217 /// <summary> 218 /// Add a `short` to the buffer (aligns the data and grows if necessary). 219 /// </summary> 220 /// <param name="x">The `short` to add to the buffer.</param> 221 public void AddShort(short x) { Prep(sizeof(short), 0); PutShort(x); } 222 223 /// <summary> 224 /// Add an `ushort` to the buffer (aligns the data and grows if necessary). 225 /// </summary> 226 /// <param name="x">The `ushort` to add to the buffer.</param> 227 public void AddUshort(ushort x) { Prep(sizeof(ushort), 0); PutUshort(x); } 228 229 /// <summary> 230 /// Add an `int` to the buffer (aligns the data and grows if necessary). 231 /// </summary> 232 /// <param name="x">The `int` to add to the buffer.</param> 233 public void AddInt(int x) { Prep(sizeof(int), 0); PutInt(x); } 234 235 /// <summary> 236 /// Add an `uint` to the buffer (aligns the data and grows if necessary). 237 /// </summary> 238 /// <param name="x">The `uint` to add to the buffer.</param> 239 public void AddUint(uint x) { Prep(sizeof(uint), 0); PutUint(x); } 240 241 /// <summary> 242 /// Add a `long` to the buffer (aligns the data and grows if necessary). 243 /// </summary> 244 /// <param name="x">The `long` to add to the buffer.</param> 245 public void AddLong(long x) { Prep(sizeof(long), 0); PutLong(x); } 246 247 /// <summary> 248 /// Add an `ulong` to the buffer (aligns the data and grows if necessary). 249 /// </summary> 250 /// <param name="x">The `ulong` to add to the buffer.</param> 251 public void AddUlong(ulong x) { Prep(sizeof(ulong), 0); PutUlong(x); } 252 253 /// <summary> 254 /// Add a `float` to the buffer (aligns the data and grows if necessary). 255 /// </summary> 256 /// <param name="x">The `float` to add to the buffer.</param> 257 public void AddFloat(float x) { Prep(sizeof(float), 0); PutFloat(x); } 258 259 /// <summary> 260 /// Add a `double` to the buffer (aligns the data and grows if necessary). 261 /// </summary> 262 /// <param name="x">The `double` to add to the buffer.</param> 263 public void AddDouble(double x) { Prep(sizeof(double), 0); 264 PutDouble(x); } 265 266 /// <summary> 267 /// Adds an offset, relative to where it will be written. 268 /// </summary> 269 /// <param name="off">The offset to add to the buffer.</param> 270 public void AddOffset(int off) 271 { 272 Prep(sizeof(int), 0); // Ensure alignment is already done. 273 if (off > Offset) 274 throw new ArgumentException(); 275 276 off = Offset - off + sizeof(int); 277 PutInt(off); 278 } 279 280 /// @cond FLATBUFFERS_INTERNAL 281 public void StartVector(int elemSize, int count, int alignment) 282 { 283 NotNested(); 284 _vectorNumElems = count; 285 Prep(sizeof(int), elemSize * count); 286 Prep(alignment, elemSize * count); // Just in case alignment > int. 287 } 288 /// @endcond 289 290 /// <summary> 291 /// Writes data necessary to finish a vector construction. 292 /// </summary> 293 public VectorOffset EndVector() 294 { 295 PutInt(_vectorNumElems); 296 return new VectorOffset(Offset); 297 } 298 299 /// <summary> 300 /// Creates a vector of tables. 301 /// </summary> 302 /// <param name="offsets">Offsets of the tables.</param> 303 public VectorOffset CreateVectorOfTables<T>(Offset<T>[] offsets) where T : struct 304 { 305 NotNested(); 306 StartVector(sizeof(int), offsets.Length, sizeof(int)); 307 for (int i = offsets.Length - 1; i >= 0; i--) AddOffset(offsets[i].Value); 308 return EndVector(); 309 } 310 311 /// @cond FLATBUFFERS_INTENRAL 312 public void Nested(int obj) 313 { 314 // Structs are always stored inline, so need to be created right 315 // where they are used. You'll get this assert if you created it 316 // elsewhere. 317 if (obj != Offset) 318 throw new Exception( 319 "FlatBuffers: struct must be serialized inline."); 320 } 321 322 public void NotNested() 323 { 324 // You should not be creating any other objects or strings/vectors 325 // while an object is being constructed 326 if (_vtableSize >= 0) 327 throw new Exception( 328 "FlatBuffers: object serialization must not be nested."); 329 } 330 331 public void StartObject(int numfields) 332 { 333 if (numfields < 0) 334 throw new ArgumentOutOfRangeException("Flatbuffers: invalid numfields"); 335 336 NotNested(); 337 338 if (_vtable.Length < numfields) 339 _vtable = new int[numfields]; 340 341 _vtableSize = numfields; 342 _objectStart = Offset; 343 } 344 345 346 // Set the current vtable at `voffset` to the current location in the 347 // buffer. 348 public void Slot(int voffset) 349 { 350 if (voffset >= _vtableSize) 351 throw new IndexOutOfRangeException("Flatbuffers: invalid voffset"); 352 353 _vtable[voffset] = Offset; 354 } 355 356 /// <summary> 357 /// Adds a Boolean to the Table at index `o` in its vtable using the value `x` and default `d` 358 /// </summary> 359 /// <param name="o">The index into the vtable</param> 360 /// <param name="x">The value to put into the buffer. If the value is equal to the default 361 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 362 /// <param name="d">The default value to compare the value against</param> 363 public void AddBool(int o, bool x, bool d) { if (ForceDefaults || x != d) { AddBool(x); Slot(o); } } 364 365 /// <summary> 366 /// Adds a SByte to the Table at index `o` in its vtable using the value `x` and default `d` 367 /// </summary> 368 /// <param name="o">The index into the vtable</param> 369 /// <param name="x">The value to put into the buffer. If the value is equal to the default 370 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 371 /// <param name="d">The default value to compare the value against</param> 372 public void AddSbyte(int o, sbyte x, sbyte d) { if (ForceDefaults || x != d) { AddSbyte(x); Slot(o); } } 373 374 /// <summary> 375 /// Adds a Byte to the Table at index `o` in its vtable using the value `x` and default `d` 376 /// </summary> 377 /// <param name="o">The index into the vtable</param> 378 /// <param name="x">The value to put into the buffer. If the value is equal to the default 379 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 380 /// <param name="d">The default value to compare the value against</param> 381 public void AddByte(int o, byte x, byte d) { if (ForceDefaults || x != d) { AddByte(x); Slot(o); } } 382 383 /// <summary> 384 /// Adds a Int16 to the Table at index `o` in its vtable using the value `x` and default `d` 385 /// </summary> 386 /// <param name="o">The index into the vtable</param> 387 /// <param name="x">The value to put into the buffer. If the value is equal to the default 388 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 389 /// <param name="d">The default value to compare the value against</param> 390 public void AddShort(int o, short x, int d) { if (ForceDefaults || x != d) { AddShort(x); Slot(o); } } 391 392 /// <summary> 393 /// Adds a UInt16 to the Table at index `o` in its vtable using the value `x` and default `d` 394 /// </summary> 395 /// <param name="o">The index into the vtable</param> 396 /// <param name="x">The value to put into the buffer. If the value is equal to the default 397 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 398 /// <param name="d">The default value to compare the value against</param> 399 public void AddUshort(int o, ushort x, ushort d) { if (ForceDefaults || x != d) { AddUshort(x); Slot(o); } } 400 401 /// <summary> 402 /// Adds an Int32 to the Table at index `o` in its vtable using the value `x` and default `d` 403 /// </summary> 404 /// <param name="o">The index into the vtable</param> 405 /// <param name="x">The value to put into the buffer. If the value is equal to the default 406 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 407 /// <param name="d">The default value to compare the value against</param> 408 public void AddInt(int o, int x, int d) { if (ForceDefaults || x != d) { AddInt(x); Slot(o); } } 409 410 /// <summary> 411 /// Adds a UInt32 to the Table at index `o` in its vtable using the value `x` and default `d` 412 /// </summary> 413 /// <param name="o">The index into the vtable</param> 414 /// <param name="x">The value to put into the buffer. If the value is equal to the default 415 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 416 /// <param name="d">The default value to compare the value against</param> 417 public void AddUint(int o, uint x, uint d) { if (ForceDefaults || x != d) { AddUint(x); Slot(o); } } 418 419 /// <summary> 420 /// Adds an Int64 to the Table at index `o` in its vtable using the value `x` and default `d` 421 /// </summary> 422 /// <param name="o">The index into the vtable</param> 423 /// <param name="x">The value to put into the buffer. If the value is equal to the default 424 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 425 /// <param name="d">The default value to compare the value against</param> 426 public void AddLong(int o, long x, long d) { if (ForceDefaults || x != d) { AddLong(x); Slot(o); } } 427 428 /// <summary> 429 /// Adds a UInt64 to the Table at index `o` in its vtable using the value `x` and default `d` 430 /// </summary> 431 /// <param name="o">The index into the vtable</param> 432 /// <param name="x">The value to put into the buffer. If the value is equal to the default 433 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 434 /// <param name="d">The default value to compare the value against</param> 435 public void AddUlong(int o, ulong x, ulong d) { if (ForceDefaults || x != d) { AddUlong(x); Slot(o); } } 436 437 /// <summary> 438 /// Adds a Single to the Table at index `o` in its vtable using the value `x` and default `d` 439 /// </summary> 440 /// <param name="o">The index into the vtable</param> 441 /// <param name="x">The value to put into the buffer. If the value is equal to the default 442 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 443 /// <param name="d">The default value to compare the value against</param> 444 public void AddFloat(int o, float x, double d) { if (ForceDefaults || x != d) { AddFloat(x); Slot(o); } } 445 446 /// <summary> 447 /// Adds a Double to the Table at index `o` in its vtable using the value `x` and default `d` 448 /// </summary> 449 /// <param name="o">The index into the vtable</param> 450 /// <param name="x">The value to put into the buffer. If the value is equal to the default 451 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 452 /// <param name="d">The default value to compare the value against</param> 453 public void AddDouble(int o, double x, double d) { if (ForceDefaults || x != d) { AddDouble(x); Slot(o); } } 454 455 /// <summary> 456 /// Adds a buffer offset to the Table at index `o` in its vtable using the value `x` and default `d` 457 /// </summary> 458 /// <param name="o">The index into the vtable</param> 459 /// <param name="x">The value to put into the buffer. If the value is equal to the default 460 /// and <see cref="ForceDefaults"/> is false, the value will be skipped.</param> 461 /// <param name="d">The default value to compare the value against</param> 462 public void AddOffset(int o, int x, int d) { if (ForceDefaults || x != d) { AddOffset(x); Slot(o); } } 463 /// @endcond 464 465 /// <summary> 466 /// Encode the string `s` in the buffer using UTF-8. 467 /// </summary> 468 /// <param name="s">The string to encode.</param> 469 /// <returns> 470 /// The offset in the buffer where the encoded string starts. 471 /// </returns> 472 public StringOffset CreateString(string s) 473 { 474 NotNested(); 475 AddByte(0); 476 var utf8StringLen = Encoding.UTF8.GetByteCount(s); 477 StartVector(1, utf8StringLen, 1); 478 Encoding.UTF8.GetBytes(s, 0, s.Length, _bb.Data, _space -= utf8StringLen); 479 return new StringOffset(EndVector().Value); 480 } 481 482 /// @cond FLATBUFFERS_INTERNAL 483 // Structs are stored inline, so nothing additional is being added. 484 // `d` is always 0. 485 public void AddStruct(int voffset, int x, int d) 486 { 487 if (x != d) 488 { 489 Nested(x); 490 Slot(voffset); 491 } 492 } 493 494 public int EndObject() 495 { 496 if (_vtableSize < 0) 497 throw new InvalidOperationException( 498 "Flatbuffers: calling endObject without a startObject"); 499 500 AddInt((int)0); 501 var vtableloc = Offset; 502 // Write out the current vtable. 503 for (int i = _vtableSize - 1; i >= 0 ; i--) { 504 // Offset relative to the start of the table. 505 short off = (short)(_vtable[i] != 0 506 ? vtableloc - _vtable[i] 507 : 0); 508 AddShort(off); 509 510 // clear out written entry 511 _vtable[i] = 0; 512 } 513 514 const int standardFields = 2; // The fields below: 515 AddShort((short)(vtableloc - _objectStart)); 516 AddShort((short)((_vtableSize + standardFields) * 517 sizeof(short))); 518 519 // Search for an existing vtable that matches the current one. 520 int existingVtable = 0; 521 for (int i = 0; i < _numVtables; i++) { 522 int vt1 = _bb.Length - _vtables[i]; 523 int vt2 = _space; 524 short len = _bb.GetShort(vt1); 525 if (len == _bb.GetShort(vt2)) { 526 for (int j = sizeof(short); j < len; j += sizeof(short)) { 527 if (_bb.GetShort(vt1 + j) != _bb.GetShort(vt2 + j)) { 528 goto endLoop; 529 } 530 } 531 existingVtable = _vtables[i]; 532 break; 533 } 534 535 endLoop: { } 536 } 537 538 if (existingVtable != 0) { 539 // Found a match: 540 // Remove the current vtable. 541 _space = _bb.Length - vtableloc; 542 // Point table to existing vtable. 543 _bb.PutInt(_space, existingVtable - vtableloc); 544 } else { 545 // No match: 546 // Add the location of the current vtable to the list of 547 // vtables. 548 if (_numVtables == _vtables.Length) 549 { 550 // Arrays.CopyOf(vtables num_vtables * 2); 551 var newvtables = new int[ _numVtables * 2]; 552 Array.Copy(_vtables, newvtables, _vtables.Length); 553 554 _vtables = newvtables; 555 }; 556 _vtables[_numVtables++] = Offset; 557 // Point table to current vtable. 558 _bb.PutInt(_bb.Length - vtableloc, Offset - vtableloc); 559 } 560 561 _vtableSize = -1; 562 return vtableloc; 563 } 564 565 // This checks a required field has been set in a given table that has 566 // just been constructed. 567 public void Required(int table, int field) 568 { 569 int table_start = _bb.Length - table; 570 int vtable_start = table_start - _bb.GetInt(table_start); 571 bool ok = _bb.GetShort(vtable_start + field) != 0; 572 // If this fails, the caller will show what field needs to be set. 573 if (!ok) 574 throw new InvalidOperationException("FlatBuffers: field " + field + 575 " must be set"); 576 } 577 /// @endcond 578 579 /// <summary> 580 /// Finalize a buffer, pointing to the given `root_table`. 581 /// </summary> 582 /// <param name="rootTable"> 583 /// An offset to be added to the buffer. 584 /// </param> 585 public void Finish(int rootTable) 586 { 587 Prep(_minAlign, sizeof(int)); 588 AddOffset(rootTable); 589 _bb.Position = _space; 590 } 591 592 /// <summary> 593 /// Get the ByteBuffer representing the FlatBuffer. 594 /// </summary> 595 /// <remarks> 596 /// This is typically only called after you call `Finish()`. 597 /// The actual data starts at the ByteBuffer's current position, 598 /// not necessarily at `0`. 599 /// </remarks> 600 /// <returns> 601 /// Returns the ByteBuffer for this FlatBuffer. 602 /// </returns> 603 public ByteBuffer DataBuffer { get { return _bb; } } 604 605 /// <summary> 606 /// A utility function to copy and return the ByteBuffer data as a 607 /// `byte[]`. 608 /// </summary> 609 /// <returns> 610 /// A full copy of the FlatBuffer data. 611 /// </returns> 612 public byte[] SizedByteArray() 613 { 614 var newArray = new byte[_bb.Data.Length - _bb.Position]; 615 Buffer.BlockCopy(_bb.Data, _bb.Position, newArray, 0, 616 _bb.Data.Length - _bb.Position); 617 return newArray; 618 } 619 620 /// <summary> 621 /// Finalize a buffer, pointing to the given `rootTable`. 622 /// </summary> 623 /// <param name="rootTable"> 624 /// An offset to be added to the buffer. 625 /// </param> 626 /// <param name="fileIdentifier"> 627 /// A FlatBuffer file identifier to be added to the buffer before 628 /// `root_table`. 629 /// </param> 630 public void Finish(int rootTable, string fileIdentifier) 631 { 632 Prep(_minAlign, sizeof(int) + 633 FlatBufferConstants.FileIdentifierLength); 634 if (fileIdentifier.Length != 635 FlatBufferConstants.FileIdentifierLength) 636 throw new ArgumentException( 637 "FlatBuffers: file identifier must be length " + 638 FlatBufferConstants.FileIdentifierLength, 639 "fileIdentifier"); 640 for (int i = FlatBufferConstants.FileIdentifierLength - 1; i >= 0; 641 i--) 642 { 643 AddByte((byte)fileIdentifier[i]); 644 } 645 Finish(rootTable); 646 } 647 648 649 } 650} 651 652/// @} 653