/*
* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Text;
/// @file
/// @addtogroup flatbuffers_csharp_api
/// @{
namespace FlatBuffers
{
///
/// Responsible for building up and accessing a FlatBuffer formatted byte
/// array (via ByteBuffer).
///
public class FlatBufferBuilder
{
private int _space;
private ByteBuffer _bb;
private int _minAlign = 1;
// The vtable for the current table (if _vtableSize >= 0)
private int[] _vtable = new int[16];
// The size of the vtable. -1 indicates no vtable
private int _vtableSize = -1;
// Starting offset of the current struct/table.
private int _objectStart;
// List of offsets of all vtables.
private int[] _vtables = new int[16];
// Number of entries in `vtables` in use.
private int _numVtables = 0;
// For the current vector being built.
private int _vectorNumElems = 0;
// For CreateSharedString
private Dictionary _sharedStringMap = null;
///
/// Create a FlatBufferBuilder with a given initial size.
///
///
/// The initial size to use for the internal buffer.
///
public FlatBufferBuilder(int initialSize)
{
if (initialSize <= 0)
throw new ArgumentOutOfRangeException("initialSize",
initialSize, "Must be greater than zero");
_space = initialSize;
_bb = new ByteBuffer(initialSize);
}
///
/// 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()
{
_space = _bb.Length;
_bb.Reset();
_minAlign = 1;
while (_vtableSize > 0) _vtable[--_vtableSize] = 0;
_vtableSize = -1;
_objectStart = 0;
_numVtables = 0;
_vectorNumElems = 0;
}
///
/// Gets and sets a Boolean to disable the optimization when serializing
/// default values to a Table.
///
/// In order to save space, fields that are set to their default value
/// don't get serialized into the buffer.
///
public bool ForceDefaults { get; set; }
/// @cond FLATBUFFERS_INTERNAL
public int Offset { get { return _bb.Length - _space; } }
public void Pad(int size)
{
_bb.PutByte(_space -= size, 0, size);
}
// Doubles the size of the ByteBuffer, and copies the old data towards
// the end of the new buffer (since we build the buffer backwards).
void GrowBuffer()
{
_bb.GrowFront(_bb.Length << 1);
}
// Prepare to write an element of `size` after `additional_bytes`
// have been written, e.g. if you write a string, you need to align
// such the int length field is aligned to SIZEOF_INT, and the string
// data follows it directly.
// If all you need to do is align, `additional_bytes` will be 0.
public void Prep(int size, int additionalBytes)
{
// Track the biggest thing we've ever aligned to.
if (size > _minAlign)
_minAlign = size;
// Find the amount of alignment needed such that `size` is properly
// aligned after `additional_bytes`
var alignSize =
((~((int)_bb.Length - _space + additionalBytes)) + 1) &
(size - 1);
// Reallocate the buffer if needed.
while (_space < alignSize + size + additionalBytes)
{
var oldBufSize = (int)_bb.Length;
GrowBuffer();
_space += (int)_bb.Length - oldBufSize;
}
if (alignSize > 0)
Pad(alignSize);
}
public void PutBool(bool x)
{
_bb.PutByte(_space -= sizeof(byte), (byte)(x ? 1 : 0));
}
public void PutSbyte(sbyte x)
{
_bb.PutSbyte(_space -= sizeof(sbyte), x);
}
public void PutByte(byte x)
{
_bb.PutByte(_space -= sizeof(byte), x);
}
public void PutShort(short x)
{
_bb.PutShort(_space -= sizeof(short), x);
}
public void PutUshort(ushort x)
{
_bb.PutUshort(_space -= sizeof(ushort), x);
}
public void PutInt(int x)
{
_bb.PutInt(_space -= sizeof(int), x);
}
public void PutUint(uint x)
{
_bb.PutUint(_space -= sizeof(uint), x);
}
public void PutLong(long x)
{
_bb.PutLong(_space -= sizeof(long), x);
}
public void PutUlong(ulong x)
{
_bb.PutUlong(_space -= sizeof(ulong), x);
}
public void PutFloat(float x)
{
_bb.PutFloat(_space -= sizeof(float), x);
}
///
/// Puts an array of type T into this builder at the
/// current offset
///
/// The type of the input data
/// The array to copy data from
public void Put(T[] x)
where T : struct
{
_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);
}
/// @endcond
///
/// Add a `bool` to the buffer (aligns the data and grows if necessary).
///
/// The `bool` to add to the buffer.
public void AddBool(bool x) { Prep(sizeof(byte), 0); PutBool(x); }
///
/// Add a `sbyte` to the buffer (aligns the data and grows if necessary).
///
/// The `sbyte` to add to the buffer.
public void AddSbyte(sbyte x) { Prep(sizeof(sbyte), 0); PutSbyte(x); }
///
/// Add a `byte` to the buffer (aligns the data and grows if necessary).
///
/// The `byte` to add to the buffer.
public void AddByte(byte x) { Prep(sizeof(byte), 0); PutByte(x); }
///
/// Add a `short` to the buffer (aligns the data and grows if necessary).
///
/// The `short` to add to the buffer.
public void AddShort(short x) { Prep(sizeof(short), 0); PutShort(x); }
///
/// Add an `ushort` to the buffer (aligns the data and grows if necessary).
///
/// The `ushort` to add to the buffer.
public void AddUshort(ushort x) { Prep(sizeof(ushort), 0); PutUshort(x); }
///
/// Add an `int` to the buffer (aligns the data and grows if necessary).
///
/// The `int` to add to the buffer.
public void AddInt(int x) { Prep(sizeof(int), 0); PutInt(x); }
///
/// Add an `uint` to the buffer (aligns the data and grows if necessary).
///
/// The `uint` to add to the buffer.
public void AddUint(uint x) { Prep(sizeof(uint), 0); PutUint(x); }
///
/// Add a `long` to the buffer (aligns the data and grows if necessary).
///
/// The `long` to add to the buffer.
public void AddLong(long x) { Prep(sizeof(long), 0); PutLong(x); }
///
/// Add an `ulong` to the buffer (aligns the data and grows if necessary).
///
/// The `ulong` to add to the buffer.
public void AddUlong(ulong x) { Prep(sizeof(ulong), 0); PutUlong(x); }
///
/// Add a `float` to the buffer (aligns the data and grows if necessary).
///
/// The `float` to add to the buffer.
public void AddFloat(float x) { Prep(sizeof(float), 0); PutFloat(x); }
///
/// Add an array of type T to the buffer (aligns the data and grows if necessary).
///
/// The type of the input data
/// The array to copy data from
public void Add(T[] x)
where T : struct
{
if (x == null)
{
throw new ArgumentNullException("Cannot add a null array");
}
if( x.Length == 0)
{
// don't do anything if the array is empty
return;
}
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);
}
#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).
///
/// The `double` to add to the buffer.
public void AddDouble(double x) { Prep(sizeof(double), 0);
PutDouble(x); }
///
/// Adds an offset, relative to where it will be written.
///
/// The offset to add to the buffer.
public void AddOffset(int off)
{
Prep(sizeof(int), 0); // Ensure alignment is already done.
if (off > Offset)
throw new ArgumentException();
off = Offset - off + sizeof(int);
PutInt(off);
}
/// @cond FLATBUFFERS_INTERNAL
public void StartVector(int elemSize, int count, int alignment)
{
NotNested();
_vectorNumElems = count;
Prep(sizeof(int), elemSize * count);
Prep(alignment, elemSize * count); // Just in case alignment > int.
}
/// @endcond
///
/// Writes data necessary to finish a vector construction.
///
public VectorOffset EndVector()
{
PutInt(_vectorNumElems);
return new VectorOffset(Offset);
}
///
/// Creates a vector of tables.
///
/// Offsets of the tables.
public VectorOffset CreateVectorOfTables(Offset[] offsets) where T : struct
{
NotNested();
StartVector(sizeof(int), offsets.Length, sizeof(int));
for (int i = offsets.Length - 1; i >= 0; i--) AddOffset(offsets[i].Value);
return EndVector();
}
/// @cond FLATBUFFERS_INTENRAL
public void Nested(int obj)
{
// Structs are always stored inline, so need to be created right
// where they are used. You'll get this assert if you created it
// elsewhere.
if (obj != Offset)
throw new Exception(
"FlatBuffers: struct must be serialized inline.");
}
public void NotNested()
{
// You should not be creating any other objects or strings/vectors
// while an object is being constructed
if (_vtableSize >= 0)
throw new Exception(
"FlatBuffers: object serialization must not be nested.");
}
public void StartTable(int numfields)
{
if (numfields < 0)
throw new ArgumentOutOfRangeException("Flatbuffers: invalid numfields");
NotNested();
if (_vtable.Length < numfields)
_vtable = new int[numfields];
_vtableSize = numfields;
_objectStart = Offset;
}
// Set the current vtable at `voffset` to the current location in the
// buffer.
public void Slot(int voffset)
{
if (voffset >= _vtableSize)
throw new IndexOutOfRangeException("Flatbuffers: invalid voffset");
_vtable[voffset] = Offset;
}
///
/// Adds a Boolean to the Table at index `o` in its vtable using the value `x` and default `d`
///
/// The index into the vtable
/// The value to put into the buffer. If the value is equal to the default
/// and is false, the value will be skipped.
/// The default value to compare the value against
public void AddBool(int o, bool x, bool d) { if (ForceDefaults || x != d) { AddBool(x); Slot(o); } }
///
/// Adds a SByte to the Table at index `o` in its vtable using the value `x` and default `d`
///
/// The index into the vtable
/// The value to put into the buffer. If the value is equal to the default
/// and is false, the value will be skipped.
/// The default value to compare the value against
public void AddSbyte(int o, sbyte x, sbyte d) { if (ForceDefaults || x != d) { AddSbyte(x); Slot(o); } }
///
/// Adds a Byte to the Table at index `o` in its vtable using the value `x` and default `d`
///
/// The index into the vtable
/// The value to put into the buffer. If the value is equal to the default
/// and is false, the value will be skipped.
/// The default value to compare the value against
public void AddByte(int o, byte x, byte d) { if (ForceDefaults || x != d) { AddByte(x); Slot(o); } }
///
/// Adds a Int16 to the Table at index `o` in its vtable using the value `x` and default `d`
///
/// The index into the vtable
/// The value to put into the buffer. If the value is equal to the default
/// and is false, the value will be skipped.
/// The default value to compare the value against
public void AddShort(int o, short x, int d) { if (ForceDefaults || x != d) { AddShort(x); Slot(o); } }
///
/// Adds a UInt16 to the Table at index `o` in its vtable using the value `x` and default `d`
///
/// The index into the vtable
/// The value to put into the buffer. If the value is equal to the default
/// and is false, the value will be skipped.
/// The default value to compare the value against
public void AddUshort(int o, ushort x, ushort d) { if (ForceDefaults || x != d) { AddUshort(x); Slot(o); } }
///
/// Adds an Int32 to the Table at index `o` in its vtable using the value `x` and default `d`
///
/// The index into the vtable
/// The value to put into the buffer. If the value is equal to the default
/// and is false, the value will be skipped.
/// The default value to compare the value against
public void AddInt(int o, int x, int d) { if (ForceDefaults || x != d) { AddInt(x); Slot(o); } }
///
/// Adds a UInt32 to the Table at index `o` in its vtable using the value `x` and default `d`
///
/// The index into the vtable
/// The value to put into the buffer. If the value is equal to the default
/// and is false, the value will be skipped.
/// The default value to compare the value against
public void AddUint(int o, uint x, uint d) { if (ForceDefaults || x != d) { AddUint(x); Slot(o); } }
///
/// Adds an Int64 to the Table at index `o` in its vtable using the value `x` and default `d`
///
/// The index into the vtable
/// The value to put into the buffer. If the value is equal to the default
/// and is false, the value will be skipped.
/// The default value to compare the value against
public void AddLong(int o, long x, long d) { if (ForceDefaults || x != d) { AddLong(x); Slot(o); } }
///
/// Adds a UInt64 to the Table at index `o` in its vtable using the value `x` and default `d`
///
/// The index into the vtable
/// The value to put into the buffer. If the value is equal to the default
/// and is false, the value will be skipped.
/// The default value to compare the value against
public void AddUlong(int o, ulong x, ulong d) { if (ForceDefaults || x != d) { AddUlong(x); Slot(o); } }
///
/// Adds a Single to the Table at index `o` in its vtable using the value `x` and default `d`
///
/// The index into the vtable
/// The value to put into the buffer. If the value is equal to the default
/// and is false, the value will be skipped.
/// The default value to compare the value against
public void AddFloat(int o, float x, double d) { if (ForceDefaults || x != d) { AddFloat(x); Slot(o); } }
///
/// Adds a Double to the Table at index `o` in its vtable using the value `x` and default `d`
///
/// The index into the vtable
/// The value to put into the buffer. If the value is equal to the default
/// and is false, the value will be skipped.
/// The default value to compare the value against
public void AddDouble(int o, double x, double d) { if (ForceDefaults || x != d) { AddDouble(x); Slot(o); } }
///
/// Adds a buffer offset to the Table at index `o` in its vtable using the value `x` and default `d`
///
/// The index into the vtable
/// The value to put into the buffer. If the value is equal to the default
/// the value will be skipped.
/// The default value to compare the value against
public void AddOffset(int o, int x, int d) { if (x != d) { AddOffset(x); Slot(o); } }
/// @endcond
///
/// Encode the string `s` in the buffer using UTF-8.
///
/// The string to encode.
///
/// The offset in the buffer where the encoded string starts.
///
public StringOffset CreateString(string s)
{
NotNested();
AddByte(0);
var utf8StringLen = Encoding.UTF8.GetByteCount(s);
StartVector(1, utf8StringLen, 1);
_bb.PutStringUTF8(_space -= utf8StringLen, s);
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
///
/// Store a string in the buffer, which can contain any binary data.
/// If a string with this exact contents has already been serialized before,
/// instead simply returns the offset of the existing string.
///
/// The string to encode.
///
/// The offset in the buffer where the encoded string starts.
///
public StringOffset CreateSharedString(string s)
{
if (_sharedStringMap == null)
{
_sharedStringMap = new Dictionary();
}
if (_sharedStringMap.ContainsKey(s))
{
return _sharedStringMap[s];
}
var stringOffset = CreateString(s);
_sharedStringMap.Add(s, stringOffset);
return stringOffset;
}
/// @cond FLATBUFFERS_INTERNAL
// Structs are stored inline, so nothing additional is being added.
// `d` is always 0.
public void AddStruct(int voffset, int x, int d)
{
if (x != d)
{
Nested(x);
Slot(voffset);
}
}
public int EndTable()
{
if (_vtableSize < 0)
throw new InvalidOperationException(
"Flatbuffers: calling EndTable without a StartTable");
AddInt((int)0);
var vtableloc = Offset;
// Write out the current vtable.
int i = _vtableSize - 1;
// Trim trailing zeroes.
for (; i >= 0 && _vtable[i] == 0; i--) {}
int trimmedSize = i + 1;
for (; i >= 0 ; i--) {
// Offset relative to the start of the table.
short off = (short)(_vtable[i] != 0
? vtableloc - _vtable[i]
: 0);
AddShort(off);
// clear out written entry
_vtable[i] = 0;
}
const int standardFields = 2; // The fields below:
AddShort((short)(vtableloc - _objectStart));
AddShort((short)((trimmedSize + standardFields) *
sizeof(short)));
// Search for an existing vtable that matches the current one.
int existingVtable = 0;
for (i = 0; i < _numVtables; i++) {
int vt1 = _bb.Length - _vtables[i];
int vt2 = _space;
short len = _bb.GetShort(vt1);
if (len == _bb.GetShort(vt2)) {
for (int j = sizeof(short); j < len; j += sizeof(short)) {
if (_bb.GetShort(vt1 + j) != _bb.GetShort(vt2 + j)) {
goto endLoop;
}
}
existingVtable = _vtables[i];
break;
}
endLoop: { }
}
if (existingVtable != 0) {
// Found a match:
// Remove the current vtable.
_space = _bb.Length - vtableloc;
// Point table to existing vtable.
_bb.PutInt(_space, existingVtable - vtableloc);
} else {
// No match:
// Add the location of the current vtable to the list of
// vtables.
if (_numVtables == _vtables.Length)
{
// Arrays.CopyOf(vtables num_vtables * 2);
var newvtables = new int[ _numVtables * 2];
Array.Copy(_vtables, newvtables, _vtables.Length);
_vtables = newvtables;
};
_vtables[_numVtables++] = Offset;
// Point table to current vtable.
_bb.PutInt(_bb.Length - vtableloc, Offset - vtableloc);
}
_vtableSize = -1;
return vtableloc;
}
// This checks a required field has been set in a given table that has
// just been constructed.
public void Required(int table, int field)
{
int table_start = _bb.Length - table;
int vtable_start = table_start - _bb.GetInt(table_start);
bool ok = _bb.GetShort(vtable_start + field) != 0;
// If this fails, the caller will show what field needs to be set.
if (!ok)
throw new InvalidOperationException("FlatBuffers: field " + field +
" must be set");
}
/// @endcond
///
/// Finalize a buffer, pointing to the given `root_table`.
///
///
/// An offset to be added to the buffer.
///
///
/// Whether to prefix the size to the buffer.
///
protected void Finish(int rootTable, bool sizePrefix)
{
Prep(_minAlign, sizeof(int) + (sizePrefix ? sizeof(int) : 0));
AddOffset(rootTable);
if (sizePrefix) {
AddInt(_bb.Length - _space);
}
_bb.Position = _space;
}
///
/// Finalize a buffer, pointing to the given `root_table`.
///
///
/// An offset to be added to the buffer.
///
public void Finish(int rootTable)
{
Finish(rootTable, false);
}
///
/// Finalize a buffer, pointing to the given `root_table`, with the size prefixed.
///
///
/// An offset to be added to the buffer.
///
public void FinishSizePrefixed(int rootTable)
{
Finish(rootTable, true);
}
///
/// Get the ByteBuffer representing the FlatBuffer.
///
///
/// This is typically only called after you call `Finish()`.
/// The actual data starts at the ByteBuffer's current position,
/// not necessarily at `0`.
///
///
/// Returns the ByteBuffer for this FlatBuffer.
///
public ByteBuffer DataBuffer { get { return _bb; } }
///
/// A utility function to copy and return the ByteBuffer data as a
/// `byte[]`.
///
///
/// A full copy of the FlatBuffer data.
///
public byte[] SizedByteArray()
{
return _bb.ToSizedArray();
}
///
/// Finalize a buffer, pointing to the given `rootTable`.
///
///
/// An offset to be added to the buffer.
///
///
/// A FlatBuffer file identifier to be added to the buffer before
/// `root_table`.
///
///
/// Whether to prefix the size to the buffer.
///
protected void Finish(int rootTable, string fileIdentifier, bool sizePrefix)
{
Prep(_minAlign, sizeof(int) + (sizePrefix ? sizeof(int) : 0) +
FlatBufferConstants.FileIdentifierLength);
if (fileIdentifier.Length !=
FlatBufferConstants.FileIdentifierLength)
throw new ArgumentException(
"FlatBuffers: file identifier must be length " +
FlatBufferConstants.FileIdentifierLength,
"fileIdentifier");
for (int i = FlatBufferConstants.FileIdentifierLength - 1; i >= 0;
i--)
{
AddByte((byte)fileIdentifier[i]);
}
Finish(rootTable, sizePrefix);
}
///
/// Finalize a buffer, pointing to the given `rootTable`.
///
///
/// An offset to be added to the buffer.
///
///
/// A FlatBuffer file identifier to be added to the buffer before
/// `root_table`.
///
public void Finish(int rootTable, string fileIdentifier)
{
Finish(rootTable, fileIdentifier, false);
}
///
/// Finalize a buffer, pointing to the given `rootTable`, with the size prefixed.
///
///
/// An offset to be added to the buffer.
///
///
/// A FlatBuffer file identifier to be added to the buffer before
/// `root_table`.
///
public void FinishSizePrefixed(int rootTable, string fileIdentifier)
{
Finish(rootTable, fileIdentifier, true);
}
}
}
/// @}