mirror of
https://github.com/google/flatbuffers.git
synced 2026-07-03 07:52:26 +00:00
Added support for adding new tables/strings to an existing FlatBuffer.
As part of the reflection support. Change-Id: Ie0a8e233bca7dffa4cff7e564660035d97ff8902 Tested: on Linux. Bug:22637258
This commit is contained in:
@@ -293,11 +293,21 @@ public:
|
|||||||
const_iterator end() const { return const_iterator(Data(), size()); }
|
const_iterator end() const { return const_iterator(Data(), size()); }
|
||||||
|
|
||||||
// Change elements if you have a non-const pointer to this object.
|
// Change elements if you have a non-const pointer to this object.
|
||||||
|
// Scalars only. See reflection.h, and the documentation.
|
||||||
void Mutate(uoffset_t i, T val) {
|
void Mutate(uoffset_t i, T val) {
|
||||||
assert(i < size());
|
assert(i < size());
|
||||||
WriteScalar(data() + i, val);
|
WriteScalar(data() + i, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change an element of a vector of tables (or strings).
|
||||||
|
// "val" points to the new table/string, as you can obtain from
|
||||||
|
// e.g. reflection::AddFlatBuffer().
|
||||||
|
void MutateOffset(uoffset_t i, const uint8_t *val) {
|
||||||
|
assert(i < size());
|
||||||
|
assert(sizeof(T) == sizeof(uoffset_t));
|
||||||
|
WriteScalar(data() + i, val - (Data() + i * sizeof(uoffset_t)));
|
||||||
|
}
|
||||||
|
|
||||||
// The raw data in little endian format. Use with care.
|
// The raw data in little endian format. Use with care.
|
||||||
const uint8_t *Data() const {
|
const uint8_t *Data() const {
|
||||||
return reinterpret_cast<const uint8_t *>(&length_ + 1);
|
return reinterpret_cast<const uint8_t *>(&length_ + 1);
|
||||||
@@ -1035,6 +1045,13 @@ class Table {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SetPointer(voffset_t field, const uint8_t *val) {
|
||||||
|
auto field_offset = GetOptionalFieldOffset(field);
|
||||||
|
if (!field_offset) return false;
|
||||||
|
WriteScalar(data_ + field_offset, val - (data_ + field_offset));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t *GetVTable() { return data_ - ReadScalar<soffset_t>(data_); }
|
uint8_t *GetVTable() { return data_ - ReadScalar<soffset_t>(data_); }
|
||||||
|
|
||||||
bool CheckField(voffset_t field) const {
|
bool CheckField(voffset_t field) const {
|
||||||
|
|||||||
@@ -68,19 +68,19 @@ inline const String *GetFieldS(const Table &table,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get a field, if you know it's a vector.
|
// Get a field, if you know it's a vector.
|
||||||
template<typename T> const Vector<T> *GetFieldV(const Table &table,
|
template<typename T> Vector<T> *GetFieldV(const Table &table,
|
||||||
const reflection::Field &field) {
|
const reflection::Field &field) {
|
||||||
assert(field.type()->base_type() == reflection::Vector &&
|
assert(field.type()->base_type() == reflection::Vector &&
|
||||||
sizeof(T) == GetTypeSize(field.type()->element()));
|
sizeof(T) == GetTypeSize(field.type()->element()));
|
||||||
return table.GetPointer<const Vector<T> *>(field.offset());
|
return table.GetPointer<Vector<T> *>(field.offset());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a field, if you know it's a table.
|
// Get a field, if you know it's a table.
|
||||||
inline const Table *GetFieldT(const Table &table,
|
inline Table *GetFieldT(const Table &table,
|
||||||
const reflection::Field &field) {
|
const reflection::Field &field) {
|
||||||
assert(field.type()->base_type() == reflection::Obj ||
|
assert(field.type()->base_type() == reflection::Obj ||
|
||||||
field.type()->base_type() == reflection::Union);
|
field.type()->base_type() == reflection::Union);
|
||||||
return table.GetPointer<const Table *>(field.offset());
|
return table.GetPointer<Table *>(field.offset());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get any field as a 64bit int, regardless of what it is (bool/int/float/str).
|
// Get any field as a 64bit int, regardless of what it is (bool/int/float/str).
|
||||||
@@ -227,27 +227,27 @@ inline void SetAnyFieldS(Table *table, const reflection::Field &field,
|
|||||||
// a vector into a relative offset, such that it is not affected by resizes.
|
// a vector into a relative offset, such that it is not affected by resizes.
|
||||||
template<typename T, typename U> class pointer_inside_vector {
|
template<typename T, typename U> class pointer_inside_vector {
|
||||||
public:
|
public:
|
||||||
pointer_inside_vector(const T *ptr, const std::vector<U> &vec)
|
pointer_inside_vector(T *ptr, std::vector<U> &vec)
|
||||||
: offset_(reinterpret_cast<const uint8_t *>(ptr) -
|
: offset_(reinterpret_cast<uint8_t *>(ptr) -
|
||||||
reinterpret_cast<const uint8_t *>(vec.data())),
|
reinterpret_cast<uint8_t *>(vec.data())),
|
||||||
vec_(vec) {}
|
vec_(vec) {}
|
||||||
|
|
||||||
const T *operator*() const {
|
T *operator*() const {
|
||||||
return reinterpret_cast<const T *>(
|
return reinterpret_cast<T *>(
|
||||||
reinterpret_cast<const uint8_t *>(vec_.data()) + offset_);
|
reinterpret_cast<uint8_t *>(vec_.data()) + offset_);
|
||||||
}
|
}
|
||||||
const T *operator->() const {
|
T *operator->() const {
|
||||||
return operator*();
|
return operator*();
|
||||||
}
|
}
|
||||||
void operator=(const pointer_inside_vector &piv);
|
void operator=(const pointer_inside_vector &piv);
|
||||||
private:
|
private:
|
||||||
size_t offset_;
|
size_t offset_;
|
||||||
const std::vector<U> &vec_;
|
std::vector<U> &vec_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to create the above easily without specifying template args.
|
// Helper to create the above easily without specifying template args.
|
||||||
template<typename T, typename U> pointer_inside_vector<T, U> piv(
|
template<typename T, typename U> pointer_inside_vector<T, U> piv(T *ptr,
|
||||||
const T *ptr, const std::vector<U> &vec) {
|
std::vector<U> &vec) {
|
||||||
return pointer_inside_vector<T, U>(ptr, vec);
|
return pointer_inside_vector<T, U>(ptr, vec);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,12 +412,10 @@ inline void SetString(const reflection::Schema &schema, const std::string &val,
|
|||||||
flatbuf->data() +
|
flatbuf->data() +
|
||||||
sizeof(uoffset_t));
|
sizeof(uoffset_t));
|
||||||
if (delta) {
|
if (delta) {
|
||||||
|
// Clear the old string, since we don't want parts of it remaining.
|
||||||
|
memset(flatbuf->data() + start, 0, str->Length());
|
||||||
// Different size, we must expand (or contract).
|
// Different size, we must expand (or contract).
|
||||||
ResizeContext(schema, start, delta, flatbuf, root_table);
|
ResizeContext(schema, start, delta, flatbuf, root_table);
|
||||||
if (delta < 0) {
|
|
||||||
// Clear the old string, since we don't want parts of it remaining.
|
|
||||||
memset(flatbuf->data() + start, 0, str->Length());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Copy new data. Safe because we created the right amount of space.
|
// Copy new data. Safe because we created the right amount of space.
|
||||||
memcpy(flatbuf->data() + start, val.c_str(), val.size() + 1);
|
memcpy(flatbuf->data() + start, val.c_str(), val.size() + 1);
|
||||||
@@ -438,6 +436,12 @@ void ResizeVector(const reflection::Schema &schema, uoffset_t newsize, T val,
|
|||||||
auto start = static_cast<uoffset_t>(vec_start + sizeof(uoffset_t) +
|
auto start = static_cast<uoffset_t>(vec_start + sizeof(uoffset_t) +
|
||||||
sizeof(T) * vec->size());
|
sizeof(T) * vec->size());
|
||||||
if (delta_bytes) {
|
if (delta_bytes) {
|
||||||
|
if (delta_elem < 0) {
|
||||||
|
// Clear elements we're throwing away, since some might remain in the
|
||||||
|
// buffer.
|
||||||
|
memset(flatbuf->data() + start + delta_elem * sizeof(T), 0,
|
||||||
|
-delta_elem * sizeof(T));
|
||||||
|
}
|
||||||
ResizeContext(schema, start, delta_bytes, flatbuf, root_table);
|
ResizeContext(schema, start, delta_bytes, flatbuf, root_table);
|
||||||
WriteScalar(flatbuf->data() + vec_start, newsize); // Length field.
|
WriteScalar(flatbuf->data() + vec_start, newsize); // Length field.
|
||||||
// Set new elements to "val".
|
// Set new elements to "val".
|
||||||
@@ -453,6 +457,36 @@ void ResizeVector(const reflection::Schema &schema, uoffset_t newsize, T val,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds any new data (in the form of a new FlatBuffer) to an existing
|
||||||
|
// FlatBuffer. This can be used when any of the above methods are not
|
||||||
|
// sufficient, in particular for adding new tables and new fields.
|
||||||
|
// This is potentially slightly less efficient than a FlatBuffer constructed
|
||||||
|
// in one piece, since the new FlatBuffer doesn't share any vtables with the
|
||||||
|
// existing one.
|
||||||
|
// The return value can now be set using Vector::MutateOffset or SetFieldT
|
||||||
|
// below.
|
||||||
|
inline const uint8_t *AddFlatBuffer(std::vector<uint8_t> &flatbuf,
|
||||||
|
const uint8_t *newbuf, size_t newlen) {
|
||||||
|
// Align to sizeof(uoffset_t) past sizeof(largest_scalar_t) since we're
|
||||||
|
// going to chop off the root offset.
|
||||||
|
while ((flatbuf.size() & (sizeof(uoffset_t) - 1)) ||
|
||||||
|
!(flatbuf.size() & (sizeof(largest_scalar_t) - 1))) {
|
||||||
|
flatbuf.push_back(0);
|
||||||
|
}
|
||||||
|
auto insertion_point = static_cast<uoffset_t>(flatbuf.size());
|
||||||
|
// Insert the entire FlatBuffer minus the root pointer.
|
||||||
|
flatbuf.insert(flatbuf.end(), newbuf + sizeof(uoffset_t),
|
||||||
|
newbuf + newlen - sizeof(uoffset_t));
|
||||||
|
auto root_offset = ReadScalar<uoffset_t>(newbuf) - sizeof(uoffset_t);
|
||||||
|
return flatbuf.data() + insertion_point + root_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool SetFieldT(Table *table, const reflection::Field &field,
|
||||||
|
const uint8_t *val) {
|
||||||
|
assert(sizeof(uoffset_t) == GetTypeSize(field.type()->base_type()));
|
||||||
|
return table->SetPointer(field.offset(), val);
|
||||||
|
}
|
||||||
|
|
||||||
// Generic copying of tables from a FlatBuffer into a FlatBuffer builder.
|
// Generic copying of tables from a FlatBuffer into a FlatBuffer builder.
|
||||||
// Can be used to do any kind of merging/selecting you may want to do out
|
// Can be used to do any kind of merging/selecting you may want to do out
|
||||||
// of existing buffers. Also useful to reconstruct a whole buffer if the
|
// of existing buffers. Also useful to reconstruct a whole buffer if the
|
||||||
|
|||||||
@@ -370,8 +370,44 @@ void ReflectionTest(uint8_t *flatbuf, size_t length) {
|
|||||||
// rinventory still valid, so lets read from it.
|
// rinventory still valid, so lets read from it.
|
||||||
TEST_EQ(rinventory->Get(10), 50);
|
TEST_EQ(rinventory->Get(10), 50);
|
||||||
|
|
||||||
// Using reflection, we can also copy tables and other things out of
|
// For reflection uses not covered already, there is a more powerful way:
|
||||||
// other FlatBuffers into a new one, either part or whole.
|
// we can simply generate whatever object we want to add/modify in a
|
||||||
|
// FlatBuffer of its own, then add that to an existing FlatBuffer:
|
||||||
|
// As an example, let's add a string to an array of strings.
|
||||||
|
// First, find our field:
|
||||||
|
auto &testarrayofstring_field = *fields->LookupByKey("testarrayofstring");
|
||||||
|
// Find the vector value:
|
||||||
|
auto rtestarrayofstring = flatbuffers::piv(
|
||||||
|
flatbuffers::GetFieldV<flatbuffers::Offset<flatbuffers::String>>(
|
||||||
|
**rroot, testarrayofstring_field),
|
||||||
|
resizingbuf);
|
||||||
|
// It's a vector of 2 strings, to which we add one more, initialized to
|
||||||
|
// offset 0.
|
||||||
|
flatbuffers::ResizeVector<flatbuffers::Offset<flatbuffers::String>>(
|
||||||
|
schema, 3, 0, *rtestarrayofstring, &resizingbuf);
|
||||||
|
// Here we just create a buffer that contans a single string, but this
|
||||||
|
// could also be any complex set of tables and other values.
|
||||||
|
flatbuffers::FlatBufferBuilder stringfbb;
|
||||||
|
stringfbb.Finish(stringfbb.CreateString("hank"));
|
||||||
|
// Add the contents of it to our existing FlatBuffer.
|
||||||
|
// We do this last, so the pointer doesn't get invalidated (since it is
|
||||||
|
// at the end of the buffer):
|
||||||
|
auto string_ptr = flatbuffers::AddFlatBuffer(resizingbuf,
|
||||||
|
stringfbb.GetBufferPointer(),
|
||||||
|
stringfbb.GetSize());
|
||||||
|
// Finally, set the new value in the vector.
|
||||||
|
rtestarrayofstring->MutateOffset(2, string_ptr);
|
||||||
|
TEST_EQ_STR(rtestarrayofstring->Get(0)->c_str(), "bob");
|
||||||
|
TEST_EQ_STR(rtestarrayofstring->Get(2)->c_str(), "hank");
|
||||||
|
// As an additional test, also set it on the name field.
|
||||||
|
// Note: unlike the name change above, this just overwrites the offset,
|
||||||
|
// rather than changing the string in-place.
|
||||||
|
SetFieldT(*rroot, name_field, string_ptr);
|
||||||
|
TEST_EQ_STR(GetFieldS(**rroot, name_field)->c_str(), "hank");
|
||||||
|
|
||||||
|
// Using reflection, rather than mutating binary FlatBuffers, we can also copy
|
||||||
|
// tables and other things out of other FlatBuffers into a FlatBufferBuilder,
|
||||||
|
// either part or whole.
|
||||||
flatbuffers::FlatBufferBuilder fbb;
|
flatbuffers::FlatBufferBuilder fbb;
|
||||||
auto root_offset = flatbuffers::CopyTable(fbb, schema, *root_table,
|
auto root_offset = flatbuffers::CopyTable(fbb, schema, *root_table,
|
||||||
*flatbuffers::GetAnyRoot(flatbuf));
|
*flatbuffers::GetAnyRoot(flatbuf));
|
||||||
|
|||||||
Reference in New Issue
Block a user