mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-07 05:47:36 +00:00
An assert in flexbuffers was bit-shifting a 64-bit number by 64 bits, which throws up warnings in some automated tools. The same assert also checks to see if the number of bytes being shifted is 8. Swapped the order, so that the bitshift only occurs if the number of bits being shifted is not 64. Should be the same behavior, but plays nicer with diagnostic tools.
1524 lines
50 KiB
C++
1524 lines
50 KiB
C++
/*
|
|
* Copyright 2017 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.
|
|
*/
|
|
|
|
#ifndef FLATBUFFERS_FLEXBUFFERS_H_
|
|
#define FLATBUFFERS_FLEXBUFFERS_H_
|
|
|
|
#include <map>
|
|
// Used to select STL variant.
|
|
#include "flatbuffers/base.h"
|
|
// We use the basic binary writing functions from the regular FlatBuffers.
|
|
#include "flatbuffers/util.h"
|
|
|
|
#ifdef _MSC_VER
|
|
# include <intrin.h>
|
|
#endif
|
|
|
|
#if defined(_MSC_VER)
|
|
# pragma warning(push)
|
|
# pragma warning(disable : 4127) // C4127: conditional expression is constant
|
|
#endif
|
|
|
|
namespace flexbuffers {
|
|
|
|
class Reference;
|
|
class Map;
|
|
|
|
// These are used in the lower 2 bits of a type field to determine the size of
|
|
// the elements (and or size field) of the item pointed to (e.g. vector).
|
|
enum BitWidth {
|
|
BIT_WIDTH_8 = 0,
|
|
BIT_WIDTH_16 = 1,
|
|
BIT_WIDTH_32 = 2,
|
|
BIT_WIDTH_64 = 3,
|
|
};
|
|
|
|
// These are used as the upper 6 bits of a type field to indicate the actual
|
|
// type.
|
|
enum Type {
|
|
TYPE_NULL = 0,
|
|
TYPE_INT = 1,
|
|
TYPE_UINT = 2,
|
|
TYPE_FLOAT = 3,
|
|
// Types above stored inline, types below store an offset.
|
|
TYPE_KEY = 4,
|
|
TYPE_STRING = 5,
|
|
TYPE_INDIRECT_INT = 6,
|
|
TYPE_INDIRECT_UINT = 7,
|
|
TYPE_INDIRECT_FLOAT = 8,
|
|
TYPE_MAP = 9,
|
|
TYPE_VECTOR = 10, // Untyped.
|
|
TYPE_VECTOR_INT = 11, // Typed any size (stores no type table).
|
|
TYPE_VECTOR_UINT = 12,
|
|
TYPE_VECTOR_FLOAT = 13,
|
|
TYPE_VECTOR_KEY = 14,
|
|
TYPE_VECTOR_STRING = 15,
|
|
TYPE_VECTOR_INT2 = 16, // Typed tuple (no type table, no size field).
|
|
TYPE_VECTOR_UINT2 = 17,
|
|
TYPE_VECTOR_FLOAT2 = 18,
|
|
TYPE_VECTOR_INT3 = 19, // Typed triple (no type table, no size field).
|
|
TYPE_VECTOR_UINT3 = 20,
|
|
TYPE_VECTOR_FLOAT3 = 21,
|
|
TYPE_VECTOR_INT4 = 22, // Typed quad (no type table, no size field).
|
|
TYPE_VECTOR_UINT4 = 23,
|
|
TYPE_VECTOR_FLOAT4 = 24,
|
|
TYPE_BLOB = 25,
|
|
TYPE_BOOL = 26,
|
|
TYPE_VECTOR_BOOL =
|
|
36, // To Allow the same type of conversion of type to vector type
|
|
};
|
|
|
|
inline bool IsInline(Type t) { return t <= TYPE_FLOAT || t == TYPE_BOOL; }
|
|
|
|
inline bool IsTypedVectorElementType(Type t) {
|
|
return (t >= TYPE_INT && t <= TYPE_STRING) || t == TYPE_BOOL;
|
|
}
|
|
|
|
inline bool IsTypedVector(Type t) {
|
|
return (t >= TYPE_VECTOR_INT && t <= TYPE_VECTOR_STRING) ||
|
|
t == TYPE_VECTOR_BOOL;
|
|
}
|
|
|
|
inline bool IsFixedTypedVector(Type t) {
|
|
return t >= TYPE_VECTOR_INT2 && t <= TYPE_VECTOR_FLOAT4;
|
|
}
|
|
|
|
inline Type ToTypedVector(Type t, size_t fixed_len = 0) {
|
|
assert(IsTypedVectorElementType(t));
|
|
switch (fixed_len) {
|
|
case 0: return static_cast<Type>(t - TYPE_INT + TYPE_VECTOR_INT);
|
|
case 2: return static_cast<Type>(t - TYPE_INT + TYPE_VECTOR_INT2);
|
|
case 3: return static_cast<Type>(t - TYPE_INT + TYPE_VECTOR_INT3);
|
|
case 4: return static_cast<Type>(t - TYPE_INT + TYPE_VECTOR_INT4);
|
|
default: assert(0); return TYPE_NULL;
|
|
}
|
|
}
|
|
|
|
inline Type ToTypedVectorElementType(Type t) {
|
|
assert(IsTypedVector(t));
|
|
return static_cast<Type>(t - TYPE_VECTOR_INT + TYPE_INT);
|
|
}
|
|
|
|
inline Type ToFixedTypedVectorElementType(Type t, uint8_t *len) {
|
|
assert(IsFixedTypedVector(t));
|
|
auto fixed_type = t - TYPE_VECTOR_INT2;
|
|
*len = static_cast<uint8_t>(fixed_type / 3 +
|
|
2); // 3 types each, starting from length 2.
|
|
return static_cast<Type>(fixed_type % 3 + TYPE_INT);
|
|
}
|
|
|
|
// TODO: implement proper support for 8/16bit floats, or decide not to
|
|
// support them.
|
|
typedef int16_t half;
|
|
typedef int8_t quarter;
|
|
|
|
// TODO: can we do this without conditionals using intrinsics or inline asm
|
|
// on some platforms? Given branch prediction the method below should be
|
|
// decently quick, but it is the most frequently executed function.
|
|
// We could do an (unaligned) 64-bit read if we ifdef out the platforms for
|
|
// which that doesn't work (or where we'd read into un-owned memory).
|
|
template<typename R, typename T1, typename T2, typename T4, typename T8>
|
|
R ReadSizedScalar(const uint8_t *data, uint8_t byte_width) {
|
|
return byte_width < 4
|
|
? (byte_width < 2
|
|
? static_cast<R>(flatbuffers::ReadScalar<T1>(data))
|
|
: static_cast<R>(flatbuffers::ReadScalar<T2>(data)))
|
|
: (byte_width < 8
|
|
? static_cast<R>(flatbuffers::ReadScalar<T4>(data))
|
|
: static_cast<R>(flatbuffers::ReadScalar<T8>(data)));
|
|
}
|
|
|
|
inline int64_t ReadInt64(const uint8_t *data, uint8_t byte_width) {
|
|
return ReadSizedScalar<int64_t, int8_t, int16_t, int32_t, int64_t>(
|
|
data, byte_width);
|
|
}
|
|
|
|
inline uint64_t ReadUInt64(const uint8_t *data, uint8_t byte_width) {
|
|
// This is the "hottest" function (all offset lookups use this), so worth
|
|
// optimizing if possible.
|
|
// TODO: GCC apparently replaces memcpy by a rep movsb, but only if count is a
|
|
// constant, which here it isn't. Test if memcpy is still faster than
|
|
// the conditionals in ReadSizedScalar. Can also use inline asm.
|
|
// clang-format off
|
|
#ifdef _MSC_VER
|
|
uint64_t u = 0;
|
|
__movsb(reinterpret_cast<uint8_t *>(&u),
|
|
reinterpret_cast<const uint8_t *>(data), byte_width);
|
|
return flatbuffers::EndianScalar(u);
|
|
#else
|
|
return ReadSizedScalar<uint64_t, uint8_t, uint16_t, uint32_t, uint64_t>(
|
|
data, byte_width);
|
|
#endif
|
|
// clang-format on
|
|
}
|
|
|
|
inline double ReadDouble(const uint8_t *data, uint8_t byte_width) {
|
|
return ReadSizedScalar<double, quarter, half, float, double>(data,
|
|
byte_width);
|
|
}
|
|
|
|
inline const uint8_t *Indirect(const uint8_t *offset, uint8_t byte_width) {
|
|
return offset - ReadUInt64(offset, byte_width);
|
|
}
|
|
|
|
template<typename T> const uint8_t *Indirect(const uint8_t *offset) {
|
|
return offset - flatbuffers::ReadScalar<T>(offset);
|
|
}
|
|
|
|
inline BitWidth WidthU(uint64_t u) {
|
|
#define FLATBUFFERS_GET_FIELD_BIT_WIDTH(value, width) \
|
|
{ \
|
|
if (!((u) & ~((1ULL << (width)) - 1ULL))) return BIT_WIDTH_##width; \
|
|
}
|
|
FLATBUFFERS_GET_FIELD_BIT_WIDTH(u, 8);
|
|
FLATBUFFERS_GET_FIELD_BIT_WIDTH(u, 16);
|
|
FLATBUFFERS_GET_FIELD_BIT_WIDTH(u, 32);
|
|
#undef FLATBUFFERS_GET_FIELD_BIT_WIDTH
|
|
return BIT_WIDTH_64;
|
|
}
|
|
|
|
inline BitWidth WidthI(int64_t i) {
|
|
auto u = static_cast<uint64_t>(i) << 1;
|
|
return WidthU(i >= 0 ? u : ~u);
|
|
}
|
|
|
|
inline BitWidth WidthF(double f) {
|
|
return static_cast<double>(static_cast<float>(f)) == f ? BIT_WIDTH_32
|
|
: BIT_WIDTH_64;
|
|
}
|
|
|
|
// Base class of all types below.
|
|
// Points into the data buffer and allows access to one type.
|
|
class Object {
|
|
public:
|
|
Object(const uint8_t *data, uint8_t byte_width)
|
|
: data_(data), byte_width_(byte_width) {}
|
|
|
|
protected:
|
|
const uint8_t *data_;
|
|
uint8_t byte_width_;
|
|
};
|
|
|
|
// Stores size in `byte_width_` bytes before data_ pointer.
|
|
class Sized : public Object {
|
|
public:
|
|
Sized(const uint8_t *data, uint8_t byte_width) : Object(data, byte_width) {}
|
|
size_t size() const {
|
|
return static_cast<size_t>(ReadUInt64(data_ - byte_width_, byte_width_));
|
|
}
|
|
};
|
|
|
|
class String : public Sized {
|
|
public:
|
|
String(const uint8_t *data, uint8_t byte_width) : Sized(data, byte_width) {}
|
|
|
|
size_t length() const { return size(); }
|
|
const char *c_str() const { return reinterpret_cast<const char *>(data_); }
|
|
std::string str() const { return std::string(c_str(), length()); }
|
|
|
|
static String EmptyString() {
|
|
static const uint8_t empty_string[] = { 0 /*len*/, 0 /*terminator*/ };
|
|
return String(empty_string + 1, 1);
|
|
}
|
|
bool IsTheEmptyString() const { return data_ == EmptyString().data_; }
|
|
};
|
|
|
|
class Blob : public Sized {
|
|
public:
|
|
Blob(const uint8_t *data_buf, uint8_t byte_width)
|
|
: Sized(data_buf, byte_width) {}
|
|
|
|
static Blob EmptyBlob() {
|
|
static const uint8_t empty_blob[] = { 0 /*len*/ };
|
|
return Blob(empty_blob + 1, 1);
|
|
}
|
|
bool IsTheEmptyBlob() const { return data_ == EmptyBlob().data_; }
|
|
const uint8_t *data() const { return data_; }
|
|
};
|
|
|
|
class Vector : public Sized {
|
|
public:
|
|
Vector(const uint8_t *data, uint8_t byte_width) : Sized(data, byte_width) {}
|
|
|
|
Reference operator[](size_t i) const;
|
|
|
|
static Vector EmptyVector() {
|
|
static const uint8_t empty_vector[] = { 0 /*len*/ };
|
|
return Vector(empty_vector + 1, 1);
|
|
}
|
|
bool IsTheEmptyVector() const { return data_ == EmptyVector().data_; }
|
|
};
|
|
|
|
class TypedVector : public Sized {
|
|
public:
|
|
TypedVector(const uint8_t *data, uint8_t byte_width, Type element_type)
|
|
: Sized(data, byte_width), type_(element_type) {}
|
|
|
|
Reference operator[](size_t i) const;
|
|
|
|
static TypedVector EmptyTypedVector() {
|
|
static const uint8_t empty_typed_vector[] = { 0 /*len*/ };
|
|
return TypedVector(empty_typed_vector + 1, 1, TYPE_INT);
|
|
}
|
|
bool IsTheEmptyVector() const {
|
|
return data_ == TypedVector::EmptyTypedVector().data_;
|
|
}
|
|
|
|
Type ElementType() { return type_; }
|
|
|
|
private:
|
|
Type type_;
|
|
|
|
friend Map;
|
|
};
|
|
|
|
class FixedTypedVector : public Object {
|
|
public:
|
|
FixedTypedVector(const uint8_t *data, uint8_t byte_width, Type element_type,
|
|
uint8_t len)
|
|
: Object(data, byte_width), type_(element_type), len_(len) {}
|
|
|
|
Reference operator[](size_t i) const;
|
|
|
|
static FixedTypedVector EmptyFixedTypedVector() {
|
|
static const uint8_t fixed_empty_vector[] = { 0 /* unused */ };
|
|
return FixedTypedVector(fixed_empty_vector, 1, TYPE_INT, 0);
|
|
}
|
|
bool IsTheEmptyFixedTypedVector() const {
|
|
return data_ == FixedTypedVector::EmptyFixedTypedVector().data_;
|
|
}
|
|
|
|
Type ElementType() { return type_; }
|
|
uint8_t size() { return len_; }
|
|
|
|
private:
|
|
Type type_;
|
|
uint8_t len_;
|
|
};
|
|
|
|
class Map : public Vector {
|
|
public:
|
|
Map(const uint8_t *data, uint8_t byte_width) : Vector(data, byte_width) {}
|
|
|
|
Reference operator[](const char *key) const;
|
|
Reference operator[](const std::string &key) const;
|
|
|
|
Vector Values() const { return Vector(data_, byte_width_); }
|
|
|
|
TypedVector Keys() const {
|
|
const size_t num_prefixed_fields = 3;
|
|
auto keys_offset = data_ - byte_width_ * num_prefixed_fields;
|
|
return TypedVector(Indirect(keys_offset, byte_width_),
|
|
static_cast<uint8_t>(
|
|
ReadUInt64(keys_offset + byte_width_, byte_width_)),
|
|
TYPE_KEY);
|
|
}
|
|
|
|
static Map EmptyMap() {
|
|
static const uint8_t empty_map[] = {
|
|
0 /*keys_len*/, 0 /*keys_offset*/, 1 /*keys_width*/, 0 /*len*/
|
|
};
|
|
return Map(empty_map + 4, 1);
|
|
}
|
|
|
|
bool IsTheEmptyMap() const { return data_ == EmptyMap().data_; }
|
|
};
|
|
|
|
class Reference {
|
|
public:
|
|
Reference(const uint8_t *data, uint8_t parent_width, uint8_t byte_width,
|
|
Type type)
|
|
: data_(data),
|
|
parent_width_(parent_width),
|
|
byte_width_(byte_width),
|
|
type_(type) {}
|
|
|
|
Reference(const uint8_t *data, uint8_t parent_width, uint8_t packed_type)
|
|
: data_(data), parent_width_(parent_width) {
|
|
byte_width_ = 1U << static_cast<BitWidth>(packed_type & 3);
|
|
type_ = static_cast<Type>(packed_type >> 2);
|
|
}
|
|
|
|
Type GetType() const { return type_; }
|
|
|
|
bool IsNull() const { return type_ == TYPE_NULL; }
|
|
bool IsBool() const { return type_ == TYPE_BOOL; }
|
|
bool IsInt() const { return type_ == TYPE_INT || type_ == TYPE_INDIRECT_INT; }
|
|
bool IsUInt() const {
|
|
return type_ == TYPE_UINT || type_ == TYPE_INDIRECT_UINT;
|
|
}
|
|
bool IsIntOrUint() const { return IsInt() || IsUInt(); }
|
|
bool IsFloat() const {
|
|
return type_ == TYPE_FLOAT || type_ == TYPE_INDIRECT_FLOAT;
|
|
}
|
|
bool IsNumeric() const { return IsIntOrUint() || IsFloat(); }
|
|
bool IsString() const { return type_ == TYPE_STRING; }
|
|
bool IsKey() const { return type_ == TYPE_KEY; }
|
|
bool IsVector() const { return type_ == TYPE_VECTOR || type_ == TYPE_MAP; }
|
|
bool IsMap() const { return type_ == TYPE_MAP; }
|
|
bool IsBlob() const { return type_ == TYPE_BLOB; }
|
|
|
|
bool AsBool() const {
|
|
return (type_ == TYPE_BOOL ? ReadUInt64(data_, parent_width_)
|
|
: AsUInt64()) != 0;
|
|
}
|
|
|
|
// Reads any type as a int64_t. Never fails, does most sensible conversion.
|
|
// Truncates floats, strings are attempted to be parsed for a number,
|
|
// vectors/maps return their size. Returns 0 if all else fails.
|
|
int64_t AsInt64() const {
|
|
if (type_ == TYPE_INT) {
|
|
// A fast path for the common case.
|
|
return ReadInt64(data_, parent_width_);
|
|
} else
|
|
switch (type_) {
|
|
case TYPE_INDIRECT_INT: return ReadInt64(Indirect(), byte_width_);
|
|
case TYPE_UINT: return ReadUInt64(data_, parent_width_);
|
|
case TYPE_INDIRECT_UINT: return ReadUInt64(Indirect(), byte_width_);
|
|
case TYPE_FLOAT:
|
|
return static_cast<int64_t>(ReadDouble(data_, parent_width_));
|
|
case TYPE_INDIRECT_FLOAT:
|
|
return static_cast<int64_t>(ReadDouble(Indirect(), byte_width_));
|
|
case TYPE_NULL: return 0;
|
|
case TYPE_STRING: return flatbuffers::StringToInt(AsString().c_str());
|
|
case TYPE_VECTOR: return static_cast<int64_t>(AsVector().size());
|
|
case TYPE_BOOL: return ReadInt64(data_, parent_width_);
|
|
default:
|
|
// Convert other things to int.
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// TODO: could specialize these to not use AsInt64() if that saves
|
|
// extension ops in generated code, and use a faster op than ReadInt64.
|
|
int32_t AsInt32() const { return static_cast<int32_t>(AsInt64()); }
|
|
int16_t AsInt16() const { return static_cast<int16_t>(AsInt64()); }
|
|
int8_t AsInt8() const { return static_cast<int8_t>(AsInt64()); }
|
|
|
|
uint64_t AsUInt64() const {
|
|
if (type_ == TYPE_UINT) {
|
|
// A fast path for the common case.
|
|
return ReadUInt64(data_, parent_width_);
|
|
} else
|
|
switch (type_) {
|
|
case TYPE_INDIRECT_UINT: return ReadUInt64(Indirect(), byte_width_);
|
|
case TYPE_INT: return ReadInt64(data_, parent_width_);
|
|
case TYPE_INDIRECT_INT: return ReadInt64(Indirect(), byte_width_);
|
|
case TYPE_FLOAT:
|
|
return static_cast<uint64_t>(ReadDouble(data_, parent_width_));
|
|
case TYPE_INDIRECT_FLOAT:
|
|
return static_cast<uint64_t>(ReadDouble(Indirect(), byte_width_));
|
|
case TYPE_NULL: return 0;
|
|
case TYPE_STRING: return flatbuffers::StringToUInt(AsString().c_str());
|
|
case TYPE_VECTOR: return static_cast<uint64_t>(AsVector().size());
|
|
case TYPE_BOOL: return ReadUInt64(data_, parent_width_);
|
|
default:
|
|
// Convert other things to uint.
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint32_t AsUInt32() const { return static_cast<uint32_t>(AsUInt64()); }
|
|
uint16_t AsUInt16() const { return static_cast<uint16_t>(AsUInt64()); }
|
|
uint8_t AsUInt8() const { return static_cast<uint8_t>(AsUInt64()); }
|
|
|
|
double AsDouble() const {
|
|
if (type_ == TYPE_FLOAT) {
|
|
// A fast path for the common case.
|
|
return ReadDouble(data_, parent_width_);
|
|
} else
|
|
switch (type_) {
|
|
case TYPE_INDIRECT_FLOAT: return ReadDouble(Indirect(), byte_width_);
|
|
case TYPE_INT:
|
|
return static_cast<double>(ReadInt64(data_, parent_width_));
|
|
case TYPE_UINT:
|
|
return static_cast<double>(ReadUInt64(data_, parent_width_));
|
|
case TYPE_INDIRECT_INT:
|
|
return static_cast<double>(ReadInt64(Indirect(), byte_width_));
|
|
case TYPE_INDIRECT_UINT:
|
|
return static_cast<double>(ReadUInt64(Indirect(), byte_width_));
|
|
case TYPE_NULL: return 0.0;
|
|
case TYPE_STRING: return strtod(AsString().c_str(), nullptr);
|
|
case TYPE_VECTOR: return static_cast<double>(AsVector().size());
|
|
case TYPE_BOOL:
|
|
return static_cast<double>(ReadUInt64(data_, parent_width_));
|
|
default:
|
|
// Convert strings and other things to float.
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
float AsFloat() const { return static_cast<float>(AsDouble()); }
|
|
|
|
const char *AsKey() const {
|
|
if (type_ == TYPE_KEY) {
|
|
return reinterpret_cast<const char *>(Indirect());
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// This function returns the empty string if you try to read a not-string.
|
|
String AsString() const {
|
|
if (type_ == TYPE_STRING) {
|
|
return String(Indirect(), byte_width_);
|
|
} else {
|
|
return String::EmptyString();
|
|
}
|
|
}
|
|
|
|
// Unlike AsString(), this will convert any type to a std::string.
|
|
std::string ToString() {
|
|
std::string s;
|
|
ToString(false, false, s);
|
|
return s;
|
|
}
|
|
|
|
// Convert any type to a JSON-like string. strings_quoted determines if
|
|
// string values at the top level receive "" quotes (inside other values
|
|
// they always do). keys_quoted determines if keys are quoted, at any level.
|
|
// TODO(wvo): add further options to have indentation/newlines.
|
|
void ToString(bool strings_quoted, bool keys_quoted, std::string &s) const {
|
|
if (type_ == TYPE_STRING) {
|
|
String str(Indirect(), byte_width_);
|
|
if (strings_quoted) {
|
|
flatbuffers::EscapeString(str.c_str(), str.length(), &s, true);
|
|
} else {
|
|
s.append(str.c_str(), str.length());
|
|
}
|
|
} else if (IsKey()) {
|
|
auto str = AsKey();
|
|
if (keys_quoted) {
|
|
flatbuffers::EscapeString(str, strlen(str), &s, true);
|
|
} else {
|
|
s += str;
|
|
}
|
|
} else if (IsInt()) {
|
|
s += flatbuffers::NumToString(AsInt64());
|
|
} else if (IsUInt()) {
|
|
s += flatbuffers::NumToString(AsUInt64());
|
|
} else if (IsFloat()) {
|
|
s += flatbuffers::NumToString(AsDouble());
|
|
} else if (IsNull()) {
|
|
s += "null";
|
|
} else if (IsBool()) {
|
|
s += AsBool() ? "true" : "false";
|
|
} else if (IsMap()) {
|
|
s += "{ ";
|
|
auto m = AsMap();
|
|
auto keys = m.Keys();
|
|
auto vals = m.Values();
|
|
for (size_t i = 0; i < keys.size(); i++) {
|
|
keys[i].ToString(true, keys_quoted, s);
|
|
s += ": ";
|
|
vals[i].ToString(true, keys_quoted, s);
|
|
if (i < keys.size() - 1) s += ", ";
|
|
}
|
|
s += " }";
|
|
} else if (IsVector()) {
|
|
s += "[ ";
|
|
auto v = AsVector();
|
|
for (size_t i = 0; i < v.size(); i++) {
|
|
v[i].ToString(true, keys_quoted, s);
|
|
if (i < v.size() - 1) s += ", ";
|
|
}
|
|
s += " ]";
|
|
} else {
|
|
s += "(?)";
|
|
}
|
|
}
|
|
|
|
// This function returns the empty blob if you try to read a not-blob.
|
|
// Strings can be viewed as blobs too.
|
|
Blob AsBlob() const {
|
|
if (type_ == TYPE_BLOB || type_ == TYPE_STRING) {
|
|
return Blob(Indirect(), byte_width_);
|
|
} else {
|
|
return Blob::EmptyBlob();
|
|
}
|
|
}
|
|
|
|
// This function returns the empty vector if you try to read a not-vector.
|
|
// Maps can be viewed as vectors too.
|
|
Vector AsVector() const {
|
|
if (type_ == TYPE_VECTOR || type_ == TYPE_MAP) {
|
|
return Vector(Indirect(), byte_width_);
|
|
} else {
|
|
return Vector::EmptyVector();
|
|
}
|
|
}
|
|
|
|
TypedVector AsTypedVector() const {
|
|
if (IsTypedVector(type_)) {
|
|
return TypedVector(Indirect(), byte_width_,
|
|
ToTypedVectorElementType(type_));
|
|
} else {
|
|
return TypedVector::EmptyTypedVector();
|
|
}
|
|
}
|
|
|
|
FixedTypedVector AsFixedTypedVector() const {
|
|
if (IsFixedTypedVector(type_)) {
|
|
uint8_t len = 0;
|
|
auto vtype = ToFixedTypedVectorElementType(type_, &len);
|
|
return FixedTypedVector(Indirect(), byte_width_, vtype, len);
|
|
} else {
|
|
return FixedTypedVector::EmptyFixedTypedVector();
|
|
}
|
|
}
|
|
|
|
Map AsMap() const {
|
|
if (type_ == TYPE_MAP) {
|
|
return Map(Indirect(), byte_width_);
|
|
} else {
|
|
return Map::EmptyMap();
|
|
}
|
|
}
|
|
|
|
template<typename T> T As();
|
|
|
|
// Experimental: Mutation functions.
|
|
// These allow scalars in an already created buffer to be updated in-place.
|
|
// Since by default scalars are stored in the smallest possible space,
|
|
// the new value may not fit, in which case these functions return false.
|
|
// To avoid this, you can construct the values you intend to mutate using
|
|
// Builder::ForceMinimumBitWidth.
|
|
bool MutateInt(int64_t i) {
|
|
if (type_ == TYPE_INT) {
|
|
return Mutate(data_, i, parent_width_, WidthI(i));
|
|
} else if (type_ == TYPE_INDIRECT_INT) {
|
|
return Mutate(Indirect(), i, byte_width_, WidthI(i));
|
|
} else if (type_ == TYPE_UINT) {
|
|
auto u = static_cast<uint64_t>(i);
|
|
return Mutate(data_, u, parent_width_, WidthU(u));
|
|
} else if (type_ == TYPE_INDIRECT_UINT) {
|
|
auto u = static_cast<uint64_t>(i);
|
|
return Mutate(Indirect(), u, byte_width_, WidthU(u));
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool MutateBool(bool b) {
|
|
return type_ == TYPE_BOOL && Mutate(data_, b, parent_width_, BIT_WIDTH_8);
|
|
}
|
|
|
|
bool MutateUInt(uint64_t u) {
|
|
if (type_ == TYPE_UINT) {
|
|
return Mutate(data_, u, parent_width_, WidthU(u));
|
|
} else if (type_ == TYPE_INDIRECT_UINT) {
|
|
return Mutate(Indirect(), u, byte_width_, WidthU(u));
|
|
} else if (type_ == TYPE_INT) {
|
|
auto i = static_cast<int64_t>(u);
|
|
return Mutate(data_, i, parent_width_, WidthI(i));
|
|
} else if (type_ == TYPE_INDIRECT_INT) {
|
|
auto i = static_cast<int64_t>(u);
|
|
return Mutate(Indirect(), i, byte_width_, WidthI(i));
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool MutateFloat(float f) {
|
|
if (type_ == TYPE_FLOAT) {
|
|
return MutateF(data_, f, parent_width_, BIT_WIDTH_32);
|
|
} else if (type_ == TYPE_INDIRECT_FLOAT) {
|
|
return MutateF(Indirect(), f, byte_width_, BIT_WIDTH_32);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool MutateFloat(double d) {
|
|
if (type_ == TYPE_FLOAT) {
|
|
return MutateF(data_, d, parent_width_, WidthF(d));
|
|
} else if (type_ == TYPE_INDIRECT_FLOAT) {
|
|
return MutateF(Indirect(), d, byte_width_, WidthF(d));
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool MutateString(const char *str, size_t len) {
|
|
auto s = AsString();
|
|
if (s.IsTheEmptyString()) return false;
|
|
// This is very strict, could allow shorter strings, but that creates
|
|
// garbage.
|
|
if (s.length() != len) return false;
|
|
memcpy(const_cast<char *>(s.c_str()), str, len);
|
|
return true;
|
|
}
|
|
bool MutateString(const char *str) { return MutateString(str, strlen(str)); }
|
|
bool MutateString(const std::string &str) {
|
|
return MutateString(str.data(), str.length());
|
|
}
|
|
|
|
private:
|
|
const uint8_t *Indirect() const {
|
|
return flexbuffers::Indirect(data_, parent_width_);
|
|
}
|
|
|
|
template<typename T>
|
|
bool Mutate(const uint8_t *dest, T t, size_t byte_width,
|
|
BitWidth value_width) {
|
|
auto fits = static_cast<size_t>(static_cast<size_t>(1U) << value_width) <=
|
|
byte_width;
|
|
if (fits) {
|
|
t = flatbuffers::EndianScalar(t);
|
|
memcpy(const_cast<uint8_t *>(dest), &t, byte_width);
|
|
}
|
|
return fits;
|
|
}
|
|
|
|
template<typename T>
|
|
bool MutateF(const uint8_t *dest, T t, size_t byte_width,
|
|
BitWidth value_width) {
|
|
if (byte_width == sizeof(double))
|
|
return Mutate(dest, static_cast<double>(t), byte_width, value_width);
|
|
if (byte_width == sizeof(float))
|
|
return Mutate(dest, static_cast<float>(t), byte_width, value_width);
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
const uint8_t *data_;
|
|
uint8_t parent_width_;
|
|
uint8_t byte_width_;
|
|
Type type_;
|
|
};
|
|
|
|
// Template specialization for As().
|
|
template<> inline bool Reference::As<bool>() { return AsBool(); }
|
|
|
|
template<> inline int8_t Reference::As<int8_t>() { return AsInt8(); }
|
|
template<> inline int16_t Reference::As<int16_t>() { return AsInt16(); }
|
|
template<> inline int32_t Reference::As<int32_t>() { return AsInt32(); }
|
|
template<> inline int64_t Reference::As<int64_t>() { return AsInt64(); }
|
|
|
|
template<> inline uint8_t Reference::As<uint8_t>() { return AsUInt8(); }
|
|
template<> inline uint16_t Reference::As<uint16_t>() { return AsUInt16(); }
|
|
template<> inline uint32_t Reference::As<uint32_t>() { return AsUInt32(); }
|
|
template<> inline uint64_t Reference::As<uint64_t>() { return AsUInt64(); }
|
|
|
|
template<> inline double Reference::As<double>() { return AsDouble(); }
|
|
template<> inline float Reference::As<float>() { return AsFloat(); }
|
|
|
|
template<> inline String Reference::As<String>() { return AsString(); }
|
|
template<> inline std::string Reference::As<std::string>() {
|
|
return AsString().str();
|
|
}
|
|
|
|
template<> inline Blob Reference::As<Blob>() { return AsBlob(); }
|
|
template<> inline Vector Reference::As<Vector>() { return AsVector(); }
|
|
template<> inline TypedVector Reference::As<TypedVector>() {
|
|
return AsTypedVector();
|
|
}
|
|
template<> inline FixedTypedVector Reference::As<FixedTypedVector>() {
|
|
return AsFixedTypedVector();
|
|
}
|
|
template<> inline Map Reference::As<Map>() { return AsMap(); }
|
|
|
|
inline uint8_t PackedType(BitWidth bit_width, Type type) {
|
|
return static_cast<uint8_t>(bit_width | (type << 2));
|
|
}
|
|
|
|
inline uint8_t NullPackedType() { return PackedType(BIT_WIDTH_8, TYPE_NULL); }
|
|
|
|
// Vector accessors.
|
|
// Note: if you try to access outside of bounds, you get a Null value back
|
|
// instead. Normally this would be an assert, but since this is "dynamically
|
|
// typed" data, you may not want that (someone sends you a 2d vector and you
|
|
// wanted 3d).
|
|
// The Null converts seamlessly into a default value for any other type.
|
|
// TODO(wvo): Could introduce an #ifdef that makes this into an assert?
|
|
inline Reference Vector::operator[](size_t i) const {
|
|
auto len = size();
|
|
if (i >= len) return Reference(nullptr, 1, NullPackedType());
|
|
auto packed_type = (data_ + len * byte_width_)[i];
|
|
auto elem = data_ + i * byte_width_;
|
|
return Reference(elem, byte_width_, packed_type);
|
|
}
|
|
|
|
inline Reference TypedVector::operator[](size_t i) const {
|
|
auto len = size();
|
|
if (i >= len) return Reference(nullptr, 1, NullPackedType());
|
|
auto elem = data_ + i * byte_width_;
|
|
return Reference(elem, byte_width_, 1, type_);
|
|
}
|
|
|
|
inline Reference FixedTypedVector::operator[](size_t i) const {
|
|
if (i >= len_) return Reference(nullptr, 1, NullPackedType());
|
|
auto elem = data_ + i * byte_width_;
|
|
return Reference(elem, byte_width_, 1, type_);
|
|
}
|
|
|
|
template<typename T> int KeyCompare(const void *key, const void *elem) {
|
|
auto str_elem = reinterpret_cast<const char *>(
|
|
Indirect<T>(reinterpret_cast<const uint8_t *>(elem)));
|
|
auto skey = reinterpret_cast<const char *>(key);
|
|
return strcmp(skey, str_elem);
|
|
}
|
|
|
|
inline Reference Map::operator[](const char *key) const {
|
|
auto keys = Keys();
|
|
// We can't pass keys.byte_width_ to the comparison function, so we have
|
|
// to pick the right one ahead of time.
|
|
int (*comp)(const void *, const void *) = nullptr;
|
|
switch (keys.byte_width_) {
|
|
case 1: comp = KeyCompare<uint8_t>; break;
|
|
case 2: comp = KeyCompare<uint16_t>; break;
|
|
case 4: comp = KeyCompare<uint32_t>; break;
|
|
case 8: comp = KeyCompare<uint64_t>; break;
|
|
}
|
|
auto res = std::bsearch(key, keys.data_, keys.size(), keys.byte_width_, comp);
|
|
if (!res) return Reference(nullptr, 1, NullPackedType());
|
|
auto i = (reinterpret_cast<uint8_t *>(res) - keys.data_) / keys.byte_width_;
|
|
return (*static_cast<const Vector *>(this))[i];
|
|
}
|
|
|
|
inline Reference Map::operator[](const std::string &key) const {
|
|
return (*this)[key.c_str()];
|
|
}
|
|
|
|
inline Reference GetRoot(const uint8_t *buffer, size_t size) {
|
|
// See Finish() below for the serialization counterpart of this.
|
|
// The root starts at the end of the buffer, so we parse backwards from there.
|
|
auto end = buffer + size;
|
|
auto byte_width = *--end;
|
|
auto packed_type = *--end;
|
|
end -= byte_width; // The root data item.
|
|
return Reference(end, byte_width, packed_type);
|
|
}
|
|
|
|
inline Reference GetRoot(const std::vector<uint8_t> &buffer) {
|
|
return GetRoot(flatbuffers::vector_data(buffer), buffer.size());
|
|
}
|
|
|
|
// Flags that configure how the Builder behaves.
|
|
// The "Share" flags determine if the Builder automatically tries to pool
|
|
// this type. Pooling can reduce the size of serialized data if there are
|
|
// multiple maps of the same kind, at the expense of slightly slower
|
|
// serialization (the cost of lookups) and more memory use (std::set).
|
|
// By default this is on for keys, but off for strings.
|
|
// Turn keys off if you have e.g. only one map.
|
|
// Turn strings on if you expect many non-unique string values.
|
|
// Additionally, sharing key vectors can save space if you have maps with
|
|
// identical field populations.
|
|
enum BuilderFlag {
|
|
BUILDER_FLAG_NONE = 0,
|
|
BUILDER_FLAG_SHARE_KEYS = 1,
|
|
BUILDER_FLAG_SHARE_STRINGS = 2,
|
|
BUILDER_FLAG_SHARE_KEYS_AND_STRINGS = 3,
|
|
BUILDER_FLAG_SHARE_KEY_VECTORS = 4,
|
|
BUILDER_FLAG_SHARE_ALL = 7,
|
|
};
|
|
|
|
class Builder FLATBUFFERS_FINAL_CLASS {
|
|
public:
|
|
Builder(size_t initial_size = 256,
|
|
BuilderFlag flags = BUILDER_FLAG_SHARE_KEYS)
|
|
: buf_(initial_size),
|
|
finished_(false),
|
|
flags_(flags),
|
|
force_min_bit_width_(BIT_WIDTH_8),
|
|
key_pool(KeyOffsetCompare(buf_)),
|
|
string_pool(StringOffsetCompare(buf_)) {
|
|
buf_.clear();
|
|
}
|
|
|
|
/// @brief Get the serialized buffer (after you call `Finish()`).
|
|
/// @return Returns a vector owned by this class.
|
|
const std::vector<uint8_t> &GetBuffer() const {
|
|
Finished();
|
|
return buf_;
|
|
}
|
|
|
|
// Size of the buffer. Does not include unfinished values.
|
|
size_t GetSize() const { return buf_.size(); }
|
|
|
|
// Reset all state so we can re-use the buffer.
|
|
void Clear() {
|
|
buf_.clear();
|
|
stack_.clear();
|
|
finished_ = false;
|
|
// flags_ remains as-is;
|
|
force_min_bit_width_ = BIT_WIDTH_8;
|
|
key_pool.clear();
|
|
string_pool.clear();
|
|
}
|
|
|
|
// All value constructing functions below have two versions: one that
|
|
// takes a key (for placement inside a map) and one that doesn't (for inside
|
|
// vectors and elsewhere).
|
|
|
|
void Null() { stack_.push_back(Value()); }
|
|
void Null(const char *key) {
|
|
Key(key);
|
|
Null();
|
|
}
|
|
|
|
void Int(int64_t i) { stack_.push_back(Value(i, TYPE_INT, WidthI(i))); }
|
|
void Int(const char *key, int64_t i) {
|
|
Key(key);
|
|
Int(i);
|
|
}
|
|
|
|
void UInt(uint64_t u) { stack_.push_back(Value(u, TYPE_UINT, WidthU(u))); }
|
|
void UInt(const char *key, uint64_t u) {
|
|
Key(key);
|
|
Int(u);
|
|
}
|
|
|
|
void Float(float f) { stack_.push_back(Value(f)); }
|
|
void Float(const char *key, float f) {
|
|
Key(key);
|
|
Float(f);
|
|
}
|
|
|
|
void Double(double f) { stack_.push_back(Value(f)); }
|
|
void Double(const char *key, double d) {
|
|
Key(key);
|
|
Double(d);
|
|
}
|
|
|
|
void Bool(bool b) { stack_.push_back(Value(b)); }
|
|
void Bool(const char *key, bool b) {
|
|
Key(key);
|
|
Bool(b);
|
|
}
|
|
|
|
void IndirectInt(int64_t i) { PushIndirect(i, TYPE_INDIRECT_INT, WidthI(i)); }
|
|
void IndirectInt(const char *key, int64_t i) {
|
|
Key(key);
|
|
IndirectInt(i);
|
|
}
|
|
|
|
void IndirectUInt(uint64_t u) {
|
|
PushIndirect(u, TYPE_INDIRECT_UINT, WidthU(u));
|
|
}
|
|
void IndirectUInt(const char *key, uint64_t u) {
|
|
Key(key);
|
|
IndirectUInt(u);
|
|
}
|
|
|
|
void IndirectFloat(float f) {
|
|
PushIndirect(f, TYPE_INDIRECT_FLOAT, BIT_WIDTH_32);
|
|
}
|
|
void IndirectFloat(const char *key, float f) {
|
|
Key(key);
|
|
IndirectFloat(f);
|
|
}
|
|
|
|
void IndirectDouble(double f) {
|
|
PushIndirect(f, TYPE_INDIRECT_FLOAT, WidthF(f));
|
|
}
|
|
void IndirectDouble(const char *key, double d) {
|
|
Key(key);
|
|
IndirectDouble(d);
|
|
}
|
|
|
|
size_t Key(const char *str, size_t len) {
|
|
auto sloc = buf_.size();
|
|
WriteBytes(str, len + 1);
|
|
if (flags_ & BUILDER_FLAG_SHARE_KEYS) {
|
|
auto it = key_pool.find(sloc);
|
|
if (it != key_pool.end()) {
|
|
// Already in the buffer. Remove key we just serialized, and use
|
|
// existing offset instead.
|
|
buf_.resize(sloc);
|
|
sloc = *it;
|
|
} else {
|
|
key_pool.insert(sloc);
|
|
}
|
|
}
|
|
stack_.push_back(Value(static_cast<uint64_t>(sloc), TYPE_KEY, BIT_WIDTH_8));
|
|
return sloc;
|
|
}
|
|
|
|
size_t Key(const char *str) { return Key(str, strlen(str)); }
|
|
size_t Key(const std::string &str) { return Key(str.c_str(), str.size()); }
|
|
|
|
size_t String(const char *str, size_t len) {
|
|
auto reset_to = buf_.size();
|
|
auto sloc = CreateBlob(str, len, 1, TYPE_STRING);
|
|
if (flags_ & BUILDER_FLAG_SHARE_STRINGS) {
|
|
StringOffset so(sloc, len);
|
|
auto it = string_pool.find(so);
|
|
if (it != string_pool.end()) {
|
|
// Already in the buffer. Remove string we just serialized, and use
|
|
// existing offset instead.
|
|
buf_.resize(reset_to);
|
|
sloc = it->first;
|
|
stack_.back().u_ = sloc;
|
|
} else {
|
|
string_pool.insert(so);
|
|
}
|
|
}
|
|
return sloc;
|
|
}
|
|
size_t String(const char *str) { return String(str, strlen(str)); }
|
|
size_t String(const std::string &str) {
|
|
return String(str.c_str(), str.size());
|
|
}
|
|
void String(const flexbuffers::String &str) {
|
|
String(str.c_str(), str.length());
|
|
}
|
|
|
|
void String(const char *key, const char *str) {
|
|
Key(key);
|
|
String(str);
|
|
}
|
|
void String(const char *key, const std::string &str) {
|
|
Key(key);
|
|
String(str);
|
|
}
|
|
void String(const char *key, const flexbuffers::String &str) {
|
|
Key(key);
|
|
String(str);
|
|
}
|
|
|
|
size_t Blob(const void *data, size_t len) {
|
|
return CreateBlob(data, len, 0, TYPE_BLOB);
|
|
}
|
|
size_t Blob(const std::vector<uint8_t> &v) {
|
|
return CreateBlob(flatbuffers::vector_data(v), v.size(), 0, TYPE_BLOB);
|
|
}
|
|
|
|
// TODO(wvo): support all the FlexBuffer types (like flexbuffers::String),
|
|
// e.g. Vector etc. Also in overloaded versions.
|
|
// Also some FlatBuffers types?
|
|
|
|
size_t StartVector() { return stack_.size(); }
|
|
size_t StartVector(const char *key) {
|
|
Key(key);
|
|
return stack_.size();
|
|
}
|
|
size_t StartMap() { return stack_.size(); }
|
|
size_t StartMap(const char *key) {
|
|
Key(key);
|
|
return stack_.size();
|
|
}
|
|
|
|
// TODO(wvo): allow this to specify an aligment greater than the natural
|
|
// alignment.
|
|
size_t EndVector(size_t start, bool typed, bool fixed) {
|
|
auto vec = CreateVector(start, stack_.size() - start, 1, typed, fixed);
|
|
// Remove temp elements and return vector.
|
|
stack_.resize(start);
|
|
stack_.push_back(vec);
|
|
return static_cast<size_t>(vec.u_);
|
|
}
|
|
|
|
size_t EndMap(size_t start) {
|
|
// We should have interleaved keys and values on the stack.
|
|
// Make sure it is an even number:
|
|
auto len = stack_.size() - start;
|
|
assert(!(len & 1));
|
|
len /= 2;
|
|
// Make sure keys are all strings:
|
|
for (auto key = start; key < stack_.size(); key += 2) {
|
|
assert(stack_[key].type_ == TYPE_KEY);
|
|
}
|
|
// Now sort values, so later we can do a binary seach lookup.
|
|
// We want to sort 2 array elements at a time.
|
|
struct TwoValue {
|
|
Value key;
|
|
Value val;
|
|
};
|
|
// TODO(wvo): strict aliasing?
|
|
// TODO(wvo): allow the caller to indicate the data is already sorted
|
|
// for maximum efficiency? With an assert to check sortedness to make sure
|
|
// we're not breaking binary search.
|
|
// Or, we can track if the map is sorted as keys are added which would be
|
|
// be quite cheap (cheaper than checking it here), so we can skip this
|
|
// step automatically when appliccable, and encourage people to write in
|
|
// sorted fashion.
|
|
// std::sort is typically already a lot faster on sorted data though.
|
|
auto dict =
|
|
reinterpret_cast<TwoValue *>(flatbuffers::vector_data(stack_) + start);
|
|
std::sort(dict, dict + len,
|
|
[&](const TwoValue &a, const TwoValue &b) -> bool {
|
|
auto as = reinterpret_cast<const char *>(
|
|
flatbuffers::vector_data(buf_) + a.key.u_);
|
|
auto bs = reinterpret_cast<const char *>(
|
|
flatbuffers::vector_data(buf_) + b.key.u_);
|
|
auto comp = strcmp(as, bs);
|
|
// If this assertion hits, you've added two keys with the same
|
|
// value to this map.
|
|
// TODO: Have to check for pointer equality, as some sort
|
|
// implementation apparently call this function with the same
|
|
// element?? Why?
|
|
assert(comp || &a == &b);
|
|
return comp < 0;
|
|
});
|
|
// First create a vector out of all keys.
|
|
// TODO(wvo): if kBuilderFlagShareKeyVectors is true, see if we can share
|
|
// the first vector.
|
|
auto keys = CreateVector(start, len, 2, true, false);
|
|
auto vec = CreateVector(start + 1, len, 2, false, false, &keys);
|
|
// Remove temp elements and return map.
|
|
stack_.resize(start);
|
|
stack_.push_back(vec);
|
|
return static_cast<size_t>(vec.u_);
|
|
}
|
|
|
|
template<typename F> size_t Vector(F f) {
|
|
auto start = StartVector();
|
|
f();
|
|
return EndVector(start, false, false);
|
|
}
|
|
template<typename F, typename T> size_t Vector(F f, T &state) {
|
|
auto start = StartVector();
|
|
f(state);
|
|
return EndVector(start, false, false);
|
|
}
|
|
template<typename F> size_t Vector(const char *key, F f) {
|
|
auto start = StartVector(key);
|
|
f();
|
|
return EndVector(start, false, false);
|
|
}
|
|
template<typename F, typename T>
|
|
size_t Vector(const char *key, F f, T &state) {
|
|
auto start = StartVector(key);
|
|
f(state);
|
|
return EndVector(start, false, false);
|
|
}
|
|
|
|
template<typename T> void Vector(const T *elems, size_t len) {
|
|
if (flatbuffers::is_scalar<T>::value) {
|
|
// This path should be a lot quicker and use less space.
|
|
ScalarVector(elems, len, false);
|
|
} else {
|
|
auto start = StartVector();
|
|
for (size_t i = 0; i < len; i++) Add(elems[i]);
|
|
EndVector(start, false, false);
|
|
}
|
|
}
|
|
template<typename T>
|
|
void Vector(const char *key, const T *elems, size_t len) {
|
|
Key(key);
|
|
Vector(elems, len);
|
|
}
|
|
template<typename T> void Vector(const std::vector<T> &vec) {
|
|
Vector(flatbuffers::vector_data(vec), vec.size());
|
|
}
|
|
|
|
template<typename F> size_t TypedVector(F f) {
|
|
auto start = StartVector();
|
|
f();
|
|
return EndVector(start, true, false);
|
|
}
|
|
template<typename F, typename T> size_t TypedVector(F f, T &state) {
|
|
auto start = StartVector();
|
|
f(state);
|
|
return EndVector(start, true, false);
|
|
}
|
|
template<typename F> size_t TypedVector(const char *key, F f) {
|
|
auto start = StartVector(key);
|
|
f();
|
|
return EndVector(start, true, false);
|
|
}
|
|
template<typename F, typename T>
|
|
size_t TypedVector(const char *key, F f, T &state) {
|
|
auto start = StartVector(key);
|
|
f(state);
|
|
return EndVector(start, true, false);
|
|
}
|
|
|
|
template<typename T> size_t FixedTypedVector(const T *elems, size_t len) {
|
|
// We only support a few fixed vector lengths. Anything bigger use a
|
|
// regular typed vector.
|
|
assert(len >= 2 && len <= 4);
|
|
// And only scalar values.
|
|
assert(flatbuffers::is_scalar<T>::value);
|
|
return ScalarVector(elems, len, true);
|
|
}
|
|
|
|
template<typename T>
|
|
size_t FixedTypedVector(const char *key, const T *elems, size_t len) {
|
|
Key(key);
|
|
return FixedTypedVector(elems, len);
|
|
}
|
|
|
|
template<typename F> size_t Map(F f) {
|
|
auto start = StartMap();
|
|
f();
|
|
return EndMap(start);
|
|
}
|
|
template<typename F, typename T> size_t Map(F f, T &state) {
|
|
auto start = StartMap();
|
|
f(state);
|
|
return EndMap(start);
|
|
}
|
|
template<typename F> size_t Map(const char *key, F f) {
|
|
auto start = StartMap(key);
|
|
f();
|
|
return EndMap(start);
|
|
}
|
|
template<typename F, typename T> size_t Map(const char *key, F f, T &state) {
|
|
auto start = StartMap(key);
|
|
f(state);
|
|
return EndMap(start);
|
|
}
|
|
template<typename T> void Map(const std::map<std::string, T> &map) {
|
|
auto start = StartMap();
|
|
for (auto it = map.begin(); it != map.end(); ++it)
|
|
Add(it->first.c_str(), it->second);
|
|
EndMap(start);
|
|
}
|
|
|
|
// Overloaded Add that tries to call the correct function above.
|
|
void Add(int8_t i) { Int(i); }
|
|
void Add(int16_t i) { Int(i); }
|
|
void Add(int32_t i) { Int(i); }
|
|
void Add(int64_t i) { Int(i); }
|
|
void Add(uint8_t u) { UInt(u); }
|
|
void Add(uint16_t u) { UInt(u); }
|
|
void Add(uint32_t u) { UInt(u); }
|
|
void Add(uint64_t u) { UInt(u); }
|
|
void Add(float f) { Float(f); }
|
|
void Add(double d) { Double(d); }
|
|
void Add(bool b) { Bool(b); }
|
|
void Add(const char *str) { String(str); }
|
|
void Add(const std::string &str) { String(str); }
|
|
void Add(const flexbuffers::String &str) { String(str); }
|
|
|
|
template<typename T> void Add(const std::vector<T> &vec) { Vector(vec); }
|
|
|
|
template<typename T> void Add(const char *key, const T &t) {
|
|
Key(key);
|
|
Add(t);
|
|
}
|
|
|
|
template<typename T> void Add(const std::map<std::string, T> &map) {
|
|
Map(map);
|
|
}
|
|
|
|
template<typename T> void operator+=(const T &t) { Add(t); }
|
|
|
|
// This function is useful in combination with the Mutate* functions above.
|
|
// It forces elements of vectors and maps to have a minimum size, such that
|
|
// they can later be updated without failing.
|
|
// Call with no arguments to reset.
|
|
void ForceMinimumBitWidth(BitWidth bw = BIT_WIDTH_8) {
|
|
force_min_bit_width_ = bw;
|
|
}
|
|
|
|
void Finish() {
|
|
// If you hit this assert, you likely have objects that were never included
|
|
// in a parent. You need to have exactly one root to finish a buffer.
|
|
// Check your Start/End calls are matched, and all objects are inside
|
|
// some other object.
|
|
assert(stack_.size() == 1);
|
|
|
|
// Write root value.
|
|
auto byte_width = Align(stack_[0].ElemWidth(buf_.size(), 0));
|
|
WriteAny(stack_[0], byte_width);
|
|
// Write root type.
|
|
Write(stack_[0].StoredPackedType(), 1);
|
|
// Write root size. Normally determined by parent, but root has no parent :)
|
|
Write(byte_width, 1);
|
|
|
|
finished_ = true;
|
|
}
|
|
|
|
private:
|
|
void Finished() const {
|
|
// If you get this assert, you're attempting to get access a buffer
|
|
// which hasn't been finished yet. Be sure to call
|
|
// Builder::Finish with your root object.
|
|
assert(finished_);
|
|
}
|
|
|
|
// Align to prepare for writing a scalar with a certain size.
|
|
uint8_t Align(BitWidth alignment) {
|
|
auto byte_width = 1U << alignment;
|
|
buf_.insert(buf_.end(), flatbuffers::PaddingBytes(buf_.size(), byte_width),
|
|
0);
|
|
return static_cast<uint8_t>(byte_width);
|
|
}
|
|
|
|
void WriteBytes(const void *val, size_t size) {
|
|
buf_.insert(buf_.end(), reinterpret_cast<const uint8_t *>(val),
|
|
reinterpret_cast<const uint8_t *>(val) + size);
|
|
}
|
|
|
|
template<typename T> void Write(T val, size_t byte_width) {
|
|
assert(sizeof(T) >= byte_width);
|
|
val = flatbuffers::EndianScalar(val);
|
|
WriteBytes(&val, byte_width);
|
|
}
|
|
|
|
void WriteDouble(double f, uint8_t byte_width) {
|
|
switch (byte_width) {
|
|
case 8: Write(f, byte_width); break;
|
|
case 4: Write(static_cast<float>(f), byte_width); break;
|
|
// case 2: Write(static_cast<half>(f), byte_width); break;
|
|
// case 1: Write(static_cast<quarter>(f), byte_width); break;
|
|
default: assert(0);
|
|
}
|
|
}
|
|
|
|
void WriteOffset(uint64_t o, uint8_t byte_width) {
|
|
auto reloff = buf_.size() - o;
|
|
assert(byte_width == 8 || reloff < 1ULL << (byte_width * 8));
|
|
Write(reloff, byte_width);
|
|
}
|
|
|
|
template<typename T> void PushIndirect(T val, Type type, BitWidth bit_width) {
|
|
auto byte_width = Align(bit_width);
|
|
auto iloc = buf_.size();
|
|
Write(val, byte_width);
|
|
stack_.push_back(Value(static_cast<uint64_t>(iloc), type, bit_width));
|
|
}
|
|
|
|
static BitWidth WidthB(size_t byte_width) {
|
|
switch (byte_width) {
|
|
case 1: return BIT_WIDTH_8;
|
|
case 2: return BIT_WIDTH_16;
|
|
case 4: return BIT_WIDTH_32;
|
|
case 8: return BIT_WIDTH_64;
|
|
default: assert(false); return BIT_WIDTH_64;
|
|
}
|
|
}
|
|
|
|
template<typename T> static Type GetScalarType() {
|
|
assert(flatbuffers::is_scalar<T>::value);
|
|
return flatbuffers::is_floating_point<T>::value
|
|
? TYPE_FLOAT
|
|
: flatbuffers::is_same<T, bool>::value
|
|
? TYPE_BOOL
|
|
: (flatbuffers::is_unsigned<T>::value ? TYPE_UINT
|
|
: TYPE_INT);
|
|
}
|
|
|
|
struct Value {
|
|
union {
|
|
int64_t i_;
|
|
uint64_t u_;
|
|
double f_;
|
|
};
|
|
|
|
Type type_;
|
|
|
|
// For scalars: of itself, for vector: of its elements, for string: length.
|
|
BitWidth min_bit_width_;
|
|
|
|
Value() : i_(0), type_(TYPE_NULL), min_bit_width_(BIT_WIDTH_8) {}
|
|
|
|
Value(bool b)
|
|
: u_(static_cast<uint64_t>(b)),
|
|
type_(TYPE_BOOL),
|
|
min_bit_width_(BIT_WIDTH_8) {}
|
|
|
|
Value(int64_t i, Type t, BitWidth bw)
|
|
: i_(i), type_(t), min_bit_width_(bw) {}
|
|
Value(uint64_t u, Type t, BitWidth bw)
|
|
: u_(u), type_(t), min_bit_width_(bw) {}
|
|
|
|
Value(float f) : f_(f), type_(TYPE_FLOAT), min_bit_width_(BIT_WIDTH_32) {}
|
|
Value(double f) : f_(f), type_(TYPE_FLOAT), min_bit_width_(WidthF(f)) {}
|
|
|
|
uint8_t StoredPackedType(BitWidth parent_bit_width_ = BIT_WIDTH_8) const {
|
|
return PackedType(StoredWidth(parent_bit_width_), type_);
|
|
}
|
|
|
|
BitWidth ElemWidth(size_t buf_size, size_t elem_index) const {
|
|
if (IsInline(type_)) {
|
|
return min_bit_width_;
|
|
} else {
|
|
// We have an absolute offset, but want to store a relative offset
|
|
// elem_index elements beyond the current buffer end. Since whether
|
|
// the relative offset fits in a certain byte_width depends on
|
|
// the size of the elements before it (and their alignment), we have
|
|
// to test for each size in turn.
|
|
for (size_t byte_width = 1;
|
|
byte_width <= sizeof(flatbuffers::largest_scalar_t);
|
|
byte_width *= 2) {
|
|
// Where are we going to write this offset?
|
|
auto offset_loc = buf_size +
|
|
flatbuffers::PaddingBytes(buf_size, byte_width) +
|
|
elem_index * byte_width;
|
|
// Compute relative offset.
|
|
auto offset = offset_loc - u_;
|
|
// Does it fit?
|
|
auto bit_width = WidthU(offset);
|
|
if (static_cast<size_t>(static_cast<size_t>(1U) << bit_width) ==
|
|
byte_width)
|
|
return bit_width;
|
|
}
|
|
assert(false); // Must match one of the sizes above.
|
|
return BIT_WIDTH_64;
|
|
}
|
|
}
|
|
|
|
BitWidth StoredWidth(BitWidth parent_bit_width_ = BIT_WIDTH_8) const {
|
|
if (IsInline(type_)) {
|
|
return (std::max)(min_bit_width_, parent_bit_width_);
|
|
} else {
|
|
return min_bit_width_;
|
|
}
|
|
}
|
|
};
|
|
|
|
void WriteAny(const Value &val, uint8_t byte_width) {
|
|
switch (val.type_) {
|
|
case TYPE_NULL:
|
|
case TYPE_INT: Write(val.i_, byte_width); break;
|
|
case TYPE_BOOL:
|
|
case TYPE_UINT: Write(val.u_, byte_width); break;
|
|
case TYPE_FLOAT: WriteDouble(val.f_, byte_width); break;
|
|
default: WriteOffset(val.u_, byte_width); break;
|
|
}
|
|
}
|
|
|
|
size_t CreateBlob(const void *data, size_t len, size_t trailing, Type type) {
|
|
auto bit_width = WidthU(len);
|
|
auto byte_width = Align(bit_width);
|
|
Write<uint64_t>(len, byte_width);
|
|
auto sloc = buf_.size();
|
|
WriteBytes(data, len + trailing);
|
|
stack_.push_back(Value(static_cast<uint64_t>(sloc), type, bit_width));
|
|
return sloc;
|
|
}
|
|
|
|
template<typename T>
|
|
size_t ScalarVector(const T *elems, size_t len, bool fixed) {
|
|
auto vector_type = GetScalarType<T>();
|
|
auto byte_width = sizeof(T);
|
|
auto bit_width = WidthB(byte_width);
|
|
// If you get this assert, you're trying to write a vector with a size
|
|
// field that is bigger than the scalars you're trying to write (e.g. a
|
|
// byte vector > 255 elements). For such types, write a "blob" instead.
|
|
// TODO: instead of asserting, could write vector with larger elements
|
|
// instead, though that would be wasteful.
|
|
assert(WidthU(len) <= bit_width);
|
|
if (!fixed) Write<uint64_t>(len, byte_width);
|
|
auto vloc = buf_.size();
|
|
for (size_t i = 0; i < len; i++) Write(elems[i], byte_width);
|
|
stack_.push_back(Value(static_cast<uint64_t>(vloc),
|
|
ToTypedVector(vector_type, fixed ? len : 0),
|
|
bit_width));
|
|
return vloc;
|
|
}
|
|
|
|
Value CreateVector(size_t start, size_t vec_len, size_t step, bool typed,
|
|
bool fixed, const Value *keys = nullptr) {
|
|
// Figure out smallest bit width we can store this vector with.
|
|
auto bit_width = (std::max)(force_min_bit_width_, WidthU(vec_len));
|
|
auto prefix_elems = 1;
|
|
if (keys) {
|
|
// If this vector is part of a map, we will pre-fix an offset to the keys
|
|
// to this vector.
|
|
bit_width = (std::max)(bit_width, keys->ElemWidth(buf_.size(), 0));
|
|
prefix_elems += 2;
|
|
}
|
|
Type vector_type = TYPE_KEY;
|
|
// Check bit widths and types for all elements.
|
|
for (size_t i = start; i < stack_.size(); i += step) {
|
|
auto elem_width = stack_[i].ElemWidth(buf_.size(), i + prefix_elems);
|
|
bit_width = (std::max)(bit_width, elem_width);
|
|
if (typed) {
|
|
if (i == start) {
|
|
vector_type = stack_[i].type_;
|
|
} else {
|
|
// If you get this assert, you are writing a typed vector with
|
|
// elements that are not all the same type.
|
|
assert(vector_type == stack_[i].type_);
|
|
}
|
|
}
|
|
}
|
|
// If you get this assert, your fixed types are not one of:
|
|
// Int / UInt / Float / Key.
|
|
assert(IsTypedVectorElementType(vector_type));
|
|
auto byte_width = Align(bit_width);
|
|
// Write vector. First the keys width/offset if available, and size.
|
|
if (keys) {
|
|
WriteOffset(keys->u_, byte_width);
|
|
Write<uint64_t>(1ULL << keys->min_bit_width_, byte_width);
|
|
}
|
|
if (!fixed) Write<uint64_t>(vec_len, byte_width);
|
|
// Then the actual data.
|
|
auto vloc = buf_.size();
|
|
for (size_t i = start; i < stack_.size(); i += step) {
|
|
WriteAny(stack_[i], byte_width);
|
|
}
|
|
// Then the types.
|
|
if (!typed) {
|
|
for (size_t i = start; i < stack_.size(); i += step) {
|
|
buf_.push_back(stack_[i].StoredPackedType(bit_width));
|
|
}
|
|
}
|
|
return Value(static_cast<uint64_t>(vloc),
|
|
keys ? TYPE_MAP
|
|
: (typed ? ToTypedVector(vector_type, fixed ? vec_len : 0)
|
|
: TYPE_VECTOR),
|
|
bit_width);
|
|
}
|
|
|
|
// You shouldn't really be copying instances of this class.
|
|
Builder(const Builder &);
|
|
Builder &operator=(const Builder &);
|
|
|
|
std::vector<uint8_t> buf_;
|
|
std::vector<Value> stack_;
|
|
|
|
bool finished_;
|
|
|
|
BuilderFlag flags_;
|
|
|
|
BitWidth force_min_bit_width_;
|
|
|
|
struct KeyOffsetCompare {
|
|
KeyOffsetCompare(const std::vector<uint8_t> &buf) : buf_(&buf) {}
|
|
bool operator()(size_t a, size_t b) const {
|
|
auto stra =
|
|
reinterpret_cast<const char *>(flatbuffers::vector_data(*buf_) + a);
|
|
auto strb =
|
|
reinterpret_cast<const char *>(flatbuffers::vector_data(*buf_) + b);
|
|
return strcmp(stra, strb) < 0;
|
|
}
|
|
const std::vector<uint8_t> *buf_;
|
|
};
|
|
|
|
typedef std::pair<size_t, size_t> StringOffset;
|
|
struct StringOffsetCompare {
|
|
StringOffsetCompare(const std::vector<uint8_t> &buf) : buf_(&buf) {}
|
|
bool operator()(const StringOffset &a, const StringOffset &b) const {
|
|
auto stra = reinterpret_cast<const char *>(
|
|
flatbuffers::vector_data(*buf_) + a.first);
|
|
auto strb = reinterpret_cast<const char *>(
|
|
flatbuffers::vector_data(*buf_) + b.first);
|
|
return strncmp(stra, strb, (std::min)(a.second, b.second) + 1) < 0;
|
|
}
|
|
const std::vector<uint8_t> *buf_;
|
|
};
|
|
|
|
typedef std::set<size_t, KeyOffsetCompare> KeyOffsetMap;
|
|
typedef std::set<StringOffset, StringOffsetCompare> StringOffsetMap;
|
|
|
|
KeyOffsetMap key_pool;
|
|
StringOffsetMap string_pool;
|
|
};
|
|
|
|
} // namespace flexbuffers
|
|
|
|
# if defined(_MSC_VER)
|
|
# pragma warning(pop)
|
|
# endif
|
|
|
|
#endif // FLATBUFFERS_FLEXBUFFERS_H_
|