first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_renderer_mapbuffer_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_renderer_mapbuffer OBJECT ${react_renderer_mapbuffer_SRC})
target_include_directories(react_renderer_mapbuffer PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_renderer_mapbuffer glog glog_init react_debug)
target_compile_reactnative_options(react_renderer_mapbuffer PRIVATE)
target_compile_options(react_renderer_mapbuffer PRIVATE -Wpedantic)

View File

@@ -0,0 +1,157 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "MapBuffer.h"
namespace facebook::react {
static inline int32_t bucketOffset(int32_t index) {
return sizeof(MapBuffer::Header) + sizeof(MapBuffer::Bucket) * index;
}
static inline int32_t valueOffset(int32_t bucketIndex) {
return bucketOffset(bucketIndex) + offsetof(MapBuffer::Bucket, data);
}
// TODO T83483191: Extend MapBuffer C++ implementation to support basic random
// access
MapBuffer::MapBuffer(std::vector<uint8_t> data) : bytes_(std::move(data)) {
auto header = reinterpret_cast<const Header*>(bytes_.data());
count_ = header->count;
if (header->bufferSize != bytes_.size()) {
LOG(ERROR) << "Error: Data size does not match, expected "
<< header->bufferSize << " found: " << bytes_.size();
abort();
}
}
int32_t MapBuffer::getKeyBucket(Key key) const {
int32_t lo = 0;
int32_t hi = count_ - 1;
while (lo <= hi) {
int32_t mid = (lo + hi) >> 1;
Key midVal =
*reinterpret_cast<const Key*>(bytes_.data() + bucketOffset(mid));
if (midVal < key) {
lo = mid + 1;
} else if (midVal > key) {
hi = mid - 1;
} else {
return mid;
}
}
return -1;
}
int32_t MapBuffer::getInt(Key key) const {
auto bucketIndex = getKeyBucket(key);
react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer");
return *reinterpret_cast<const int32_t*>(
bytes_.data() + valueOffset(bucketIndex));
}
int64_t MapBuffer::getLong(Key key) const {
auto bucketIndex = getKeyBucket(key);
react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer");
return *reinterpret_cast<const int64_t*>(
bytes_.data() + valueOffset(bucketIndex));
}
bool MapBuffer::getBool(Key key) const {
return getInt(key) != 0;
}
double MapBuffer::getDouble(Key key) const {
auto bucketIndex = getKeyBucket(key);
react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer");
return *reinterpret_cast<const double*>(
bytes_.data() + valueOffset(bucketIndex));
}
int32_t MapBuffer::getDynamicDataOffset() const {
// The start of dynamic data can be calculated as the offset of the next
// key in the map
return bucketOffset(count_);
}
std::string MapBuffer::getString(Key key) const {
// TODO T83483191:Add checks to verify that offsets are under the boundaries
// of the map buffer
int32_t dynamicDataOffset = getDynamicDataOffset();
int32_t offset = getInt(key);
int32_t stringLength = *reinterpret_cast<const int32_t*>(
bytes_.data() + dynamicDataOffset + offset);
const uint8_t* stringPtr =
bytes_.data() + dynamicDataOffset + offset + sizeof(int);
return {stringPtr, stringPtr + stringLength};
}
MapBuffer MapBuffer::getMapBuffer(Key key) const {
// TODO T83483191: Add checks to verify that offsets are under the boundaries
// of the map buffer
int32_t dynamicDataOffset = getDynamicDataOffset();
int32_t offset = getInt(key);
int32_t mapBufferLength = *reinterpret_cast<const int32_t*>(
bytes_.data() + dynamicDataOffset + offset);
std::vector<uint8_t> value(mapBufferLength);
memcpy(
value.data(),
bytes_.data() + dynamicDataOffset + offset + sizeof(int32_t),
mapBufferLength);
return MapBuffer(std::move(value));
}
std::vector<MapBuffer> MapBuffer::getMapBufferList(MapBuffer::Key key) const {
std::vector<MapBuffer> mapBufferList;
int32_t dynamicDataOffset = getDynamicDataOffset();
int32_t offset = getInt(key);
int32_t mapBufferListLength = *reinterpret_cast<const int32_t*>(
bytes_.data() + dynamicDataOffset + offset);
offset = offset + sizeof(uint32_t);
int32_t curLen = 0;
while (curLen < mapBufferListLength) {
int32_t mapBufferLength = *reinterpret_cast<const int32_t*>(
bytes_.data() + dynamicDataOffset + offset + curLen);
curLen = curLen + sizeof(uint32_t);
std::vector<uint8_t> value(mapBufferLength);
memcpy(
value.data(),
bytes_.data() + dynamicDataOffset + offset + curLen,
mapBufferLength);
mapBufferList.emplace_back(std::move(value));
curLen = curLen + mapBufferLength;
}
return mapBufferList;
}
size_t MapBuffer::size() const {
return bytes_.size();
}
const uint8_t* MapBuffer::data() const {
return bytes_.data();
}
uint16_t MapBuffer::count() const {
return count_;
}
} // namespace facebook::react

View File

@@ -0,0 +1,155 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/debug/react_native_assert.h>
#include <glog/logging.h>
#include <cstdint>
#include <cstdlib>
#include <limits>
#include <string>
#include <vector>
namespace facebook::react {
class JReadableMapBuffer;
// clang-format off
/**
* MapBuffer is an optimized sparse array format for transferring props-like
* objects between C++ and other VMs. The implementation of this map is optimized to:
* - be compact to optimize space when sparse (sparse is the common case).
* - be accessible through JNI with zero/minimal copying via ByteBuffer.
* - have excellent C++ single-write and many-read performance by maximizing
* CPU cache performance through compactness, data locality, and fixed offsets
* where possible.
* - be optimized for iteration and intersection against other maps, but with
* reasonably good random access as well.
* - work recursively for nested maps/arrays.
* - support dynamic types that map to JSON.
* - don't require mutability/copy - single-write on creation and move semantics.
* - have minimal APK size and build time impact.
*
* MapBuffer data is stored in a continuous chunk of memory (bytes_ field below) with the following layout:
*
* ┌─────────────────────Header──────────────────────┐
* │ 10 bytes │
* ├─Alignment─┬─Item count─┬──────Buffer size───────┤
* │ 2 bytes │ 2 bytes │ 4 bytes │
* └───────────┴────────────┴────────────────────────┘
* ┌────────────────────────────────────────────────────────────────────────────────────────┐
* │ Buckets (one per item in the map) │
* │ │
* ├───────────────────────────Bucket───────────────────────────┬───Bucket────┬─────────────┤
* │ 12 bytes │ 12 bytes │ │
* ├───Key───┬──Type───┬──────Value (primitive or offset)───────┤ ... │ ... │
* │ 2 bytes │ 2 bytes │ 8 bytes │ │ │
* └─────────┴─────────┴────────────────────────────────────────┴─────────────┴─────────────┘
* ┌────────────────────────────────────────────────────────────────────────────────────────┐
* │ Dynamic data │
* │ │
* │ Free-form data for complex objects (e.g. strings or nested MapBuffers). │
* │ When dynamic data is serialized with some object, bucket value contains an offset of │
* │ associated byte in the array. The format of the data is not restricted, but common │
* │ practice is to use [length | bytes]. │
* └────────────────────────────────────────────────────────────────────────────────────────┘
*/
// clang-format on
class MapBuffer {
public:
using Key = uint16_t;
// The first value in the buffer, used to check correct encoding/endianness on
// JVM side.
constexpr static uint16_t HEADER_ALIGNMENT = 0xFE;
struct Header {
uint16_t alignment = HEADER_ALIGNMENT; // alignment of serialization
uint16_t count; // amount of items in the map
uint32_t bufferSize; // Amount of bytes used to store the map in memory
};
#pragma pack(push, 1)
struct Bucket {
Key key;
uint16_t type;
uint64_t data;
Bucket(Key key, uint16_t type, uint64_t data) : key(key), type(type), data(data) {}
};
#pragma pack(pop)
static_assert(sizeof(Header) == 8, "MapBuffer header size is incorrect.");
static_assert(sizeof(Bucket) == 12, "MapBuffer bucket size is incorrect.");
/**
* Data types available for serialization in MapBuffer
* Keep in sync with `DataType` enum in `JReadableMapBuffer.java`, which
* expects the same values after reading them through JNI.
*/
enum DataType : uint16_t {
Boolean = 0,
Int = 1,
Double = 2,
String = 3,
Map = 4,
Long = 5,
};
explicit MapBuffer(std::vector<uint8_t> data);
MapBuffer(const MapBuffer &buffer) = delete;
MapBuffer &operator=(const MapBuffer &other) = delete;
MapBuffer(MapBuffer &&buffer) = default;
MapBuffer &operator=(MapBuffer &&other) = default;
int32_t getInt(MapBuffer::Key key) const;
int64_t getLong(MapBuffer::Key key) const;
bool getBool(MapBuffer::Key key) const;
double getDouble(MapBuffer::Key key) const;
std::string getString(MapBuffer::Key key) const;
// TODO T83483191: review this declaration
MapBuffer getMapBuffer(MapBuffer::Key key) const;
std::vector<MapBuffer> getMapBufferList(MapBuffer::Key key) const;
size_t size() const;
const uint8_t *data() const;
uint16_t count() const;
private:
// Buffer and its size
std::vector<uint8_t> bytes_;
// amount of items in the MapBuffer
uint16_t count_ = 0;
// returns the relative offset of the first byte of dynamic data
int32_t getDynamicDataOffset() const;
int32_t getKeyBucket(MapBuffer::Key key) const;
friend JReadableMapBuffer;
};
} // namespace facebook::react

View File

@@ -0,0 +1,187 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "MapBufferBuilder.h"
#include <algorithm>
namespace facebook::react {
constexpr uint32_t INT_SIZE = sizeof(uint32_t);
constexpr uint32_t LONG_SIZE = sizeof(uint64_t);
constexpr uint32_t DOUBLE_SIZE = sizeof(double);
constexpr uint32_t MAX_BUCKET_VALUE_SIZE = sizeof(uint64_t);
MapBuffer MapBufferBuilder::EMPTY() {
return MapBufferBuilder(0).build();
}
MapBufferBuilder::MapBufferBuilder(uint32_t initialSize) {
buckets_.reserve(initialSize);
header_.count = 0;
header_.bufferSize = 0;
}
void MapBufferBuilder::storeKeyValue(
MapBuffer::Key key,
MapBuffer::DataType type,
const uint8_t* value,
uint32_t valueSize) {
if (valueSize > MAX_BUCKET_VALUE_SIZE) {
LOG(ERROR) << "Error: size of value must be <= MAX_VALUE_SIZE. ValueSize: "
<< valueSize;
abort();
}
uint64_t data = 0;
auto* dataPtr = reinterpret_cast<uint8_t*>(&data);
memcpy(dataPtr, value, valueSize);
buckets_.emplace_back(key, static_cast<uint16_t>(type), data);
header_.count++;
if (lastKey_ > key) {
needsSort_ = true;
}
lastKey_ = key;
}
void MapBufferBuilder::putBool(MapBuffer::Key key, bool value) {
int intValue = (int)value;
storeKeyValue(
key,
MapBuffer::DataType::Boolean,
reinterpret_cast<const uint8_t*>(&intValue),
INT_SIZE);
}
void MapBufferBuilder::putDouble(MapBuffer::Key key, double value) {
storeKeyValue(
key,
MapBuffer::DataType::Double,
reinterpret_cast<const uint8_t*>(&value),
DOUBLE_SIZE);
}
void MapBufferBuilder::putInt(MapBuffer::Key key, int32_t value) {
storeKeyValue(
key,
MapBuffer::DataType::Int,
reinterpret_cast<const uint8_t*>(&value),
INT_SIZE);
}
void MapBufferBuilder::putLong(MapBuffer::Key key, int64_t value) {
storeKeyValue(
key,
MapBuffer::DataType::Long,
reinterpret_cast<const uint8_t*>(&value),
LONG_SIZE);
}
void MapBufferBuilder::putString(MapBuffer::Key key, const std::string& value) {
auto strSize = value.size();
const char* strData = value.data();
// format [length of string (int)] + [Array of Characters in the string]
auto offset = dynamicData_.size();
dynamicData_.resize(offset + INT_SIZE + strSize, 0);
memcpy(dynamicData_.data() + offset, &strSize, INT_SIZE);
memcpy(dynamicData_.data() + offset + INT_SIZE, strData, strSize);
// Store Key and pointer to the string
storeKeyValue(
key,
MapBuffer::DataType::String,
reinterpret_cast<const uint8_t*>(&offset),
INT_SIZE);
}
void MapBufferBuilder::putMapBuffer(MapBuffer::Key key, const MapBuffer& map) {
auto mapBufferSize = map.size();
auto offset = dynamicData_.size();
// format [length of buffer (int)] + [bytes of MapBuffer]
dynamicData_.resize(offset + INT_SIZE + mapBufferSize, 0);
memcpy(dynamicData_.data() + offset, &mapBufferSize, INT_SIZE);
// Copy the content of the map into dynamicData_
memcpy(dynamicData_.data() + offset + INT_SIZE, map.data(), mapBufferSize);
// Store Key and pointer to the string
storeKeyValue(
key,
MapBuffer::DataType::Map,
reinterpret_cast<const uint8_t*>(&offset),
INT_SIZE);
}
void MapBufferBuilder::putMapBufferList(
MapBuffer::Key key,
const std::vector<MapBuffer>& mapBufferList) {
auto offset = static_cast<int32_t>(dynamicData_.size());
int32_t dataSize = 0;
for (const MapBuffer& mapBuffer : mapBufferList) {
dataSize = dataSize + INT_SIZE + static_cast<int32_t>(mapBuffer.size());
}
dynamicData_.resize(offset + INT_SIZE, 0);
memcpy(dynamicData_.data() + offset, &dataSize, INT_SIZE);
for (const MapBuffer& mapBuffer : mapBufferList) {
auto mapBufferSize = static_cast<int32_t>(mapBuffer.size());
auto dynamicDataSize = static_cast<int32_t>(dynamicData_.size());
dynamicData_.resize(dynamicDataSize + INT_SIZE + mapBufferSize, 0);
// format [length of buffer (int)] + [bytes of MapBuffer]
memcpy(dynamicData_.data() + dynamicDataSize, &mapBufferSize, INT_SIZE);
// Copy the content of the map into dynamicData_
memcpy(
dynamicData_.data() + dynamicDataSize + INT_SIZE,
mapBuffer.data(),
mapBufferSize);
}
// Store Key and pointer to the string
storeKeyValue(
key,
MapBuffer::DataType::Map,
reinterpret_cast<const uint8_t*>(&offset),
INT_SIZE);
}
static inline bool compareBuckets(
const MapBuffer::Bucket& a,
const MapBuffer::Bucket& b) {
return a.key < b.key;
}
MapBuffer MapBufferBuilder::build() {
// Create buffer: [header] + [key, values] + [dynamic data]
auto bucketSize = buckets_.size() * sizeof(MapBuffer::Bucket);
auto headerSize = sizeof(MapBuffer::Header);
auto bufferSize = headerSize + bucketSize + dynamicData_.size();
header_.bufferSize = static_cast<uint32_t>(bufferSize);
if (needsSort_) {
std::sort(buckets_.begin(), buckets_.end(), compareBuckets);
}
// TODO(T83483191): add pass to check for duplicates
std::vector<uint8_t> buffer(bufferSize);
memcpy(buffer.data(), &header_, headerSize);
memcpy(buffer.data() + headerSize, buckets_.data(), bucketSize);
memcpy(
buffer.data() + headerSize + bucketSize,
dynamicData_.data(),
dynamicData_.size());
return MapBuffer(std::move(buffer));
}
} // namespace facebook::react

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/debug/react_native_assert.h>
#include <vector>
#include "MapBuffer.h"
namespace facebook::react {
// Default reserved size for buckets_ vector
constexpr uint32_t INITIAL_BUCKETS_SIZE = 10;
/**
* MapBufferBuilder is a builder class for MapBuffer
*/
class MapBufferBuilder {
public:
MapBufferBuilder(uint32_t initialSize = INITIAL_BUCKETS_SIZE);
static MapBuffer EMPTY();
void putInt(MapBuffer::Key key, int32_t value);
void putLong(MapBuffer::Key key, int64_t value);
void putBool(MapBuffer::Key key, bool value);
void putDouble(MapBuffer::Key key, double value);
void putString(MapBuffer::Key key, const std::string &value);
void putMapBuffer(MapBuffer::Key key, const MapBuffer &map);
void putMapBufferList(MapBuffer::Key key, const std::vector<MapBuffer> &mapBufferList);
MapBuffer build();
private:
MapBuffer::Header header_;
std::vector<MapBuffer::Bucket> buckets_{};
std::vector<uint8_t> dynamicData_{};
uint16_t lastKey_{0};
bool needsSort_{false};
void storeKeyValue(MapBuffer::Key key, MapBuffer::DataType type, const uint8_t *value, uint32_t valueSize);
};
} // namespace facebook::react

View File

@@ -0,0 +1,221 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <limits>
#include <memory>
#include <vector>
#include <gtest/gtest.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
using namespace facebook::react;
TEST(MapBufferTest, testSimpleIntMap) {
auto builder = MapBufferBuilder();
builder.putInt(0, 1234);
builder.putInt(1, 4321);
auto map = builder.build();
EXPECT_EQ(map.count(), 2);
EXPECT_EQ(map.getInt(0), 1234);
EXPECT_EQ(map.getInt(1), 4321);
}
TEST(MapBufferTest, testSimpleLongMap) {
auto builder = MapBufferBuilder();
int64_t minInt64 = std::numeric_limits<int64_t>::min();
int64_t maxInt64 = std::numeric_limits<int64_t>::max();
builder.putLong(0, minInt64);
builder.putLong(1, maxInt64);
builder.putLong(2, 1125899906842623LL);
builder.putLong(3, -1125899906842623LL);
auto map = builder.build();
EXPECT_EQ(map.count(), 4);
EXPECT_EQ(map.getLong(0), minInt64);
EXPECT_EQ(map.getLong(1), maxInt64);
EXPECT_EQ(map.getLong(2), 1125899906842623LL);
EXPECT_EQ(map.getLong(3), -1125899906842623LL);
}
TEST(MapBufferTest, testMapBufferExtension) {
// 26 = 2 buckets: 2*10 + 6 sizeof(header)
int initialSize = 26;
auto buffer = MapBufferBuilder(initialSize);
buffer.putInt(0, 1234);
buffer.putInt(1, 4321);
buffer.putInt(2, 2121);
buffer.putInt(3, 1212);
auto map = buffer.build();
EXPECT_EQ(map.count(), 4);
EXPECT_EQ(map.getInt(0), 1234);
EXPECT_EQ(map.getInt(1), 4321);
EXPECT_EQ(map.getInt(2), 2121);
EXPECT_EQ(map.getInt(3), 1212);
}
TEST(MapBufferTest, testBoolEntries) {
auto buffer = MapBufferBuilder();
buffer.putBool(0, true);
buffer.putBool(1, false);
auto map = buffer.build();
EXPECT_EQ(map.count(), 2);
EXPECT_EQ(map.getBool(0), true);
EXPECT_EQ(map.getBool(1), false);
}
TEST(MapBufferTest, testDoubleEntries) {
auto buffer = MapBufferBuilder();
buffer.putDouble(0, 123.4);
buffer.putDouble(1, 432.1);
auto map = buffer.build();
EXPECT_EQ(map.count(), 2);
EXPECT_EQ(map.getDouble(0), 123.4);
EXPECT_EQ(map.getDouble(1), 432.1);
}
TEST(MapBufferTest, testStringEmpty) {
auto builder = MapBufferBuilder();
builder.putString(0, "");
auto map = builder.build();
EXPECT_EQ(map.getString(0), "");
}
TEST(MapBufferTest, testStringEntries) {
auto builder = MapBufferBuilder();
builder.putString(0, "This is a test");
auto map = builder.build();
EXPECT_EQ(map.getString(0), "This is a test");
}
TEST(MapBufferTest, testUTFStringEntry) {
auto builder = MapBufferBuilder();
builder.putString(0, "Let's count: 的, 一, 是");
auto map = builder.build();
EXPECT_EQ(map.getString(0), "Let's count: 的, 一, 是");
}
TEST(MapBufferTest, testEmojiStringEntry) {
auto builder = MapBufferBuilder();
builder.putString(
0, "Let's count: 1⃣, 2⃣, 3⃣, 🤦🏿‍♀️");
auto map = builder.build();
EXPECT_EQ(
map.getString(0),
"Let's count: 1⃣, 2⃣, 3⃣, 🤦🏿‍♀️");
}
TEST(MapBufferTest, testUTFStringEntries) {
auto builder = MapBufferBuilder();
builder.putString(0, "Let's count: 的, 一, 是");
builder.putString(1, "This is a test");
auto map = builder.build();
EXPECT_EQ(map.getString(0), "Let's count: 的, 一, 是");
EXPECT_EQ(map.getString(1), "This is a test");
}
TEST(MapBufferTest, testEmptyMap) {
auto builder = MapBufferBuilder();
auto map = builder.build();
EXPECT_EQ(map.count(), 0);
}
TEST(MapBufferTest, testEmptyMapConstant) {
auto map = MapBufferBuilder::EMPTY();
EXPECT_EQ(map.count(), 0);
}
TEST(MapBufferTest, testMapEntries) {
auto builder = MapBufferBuilder();
builder.putString(0, "This is a test");
builder.putInt(1, 1234);
auto map = builder.build();
EXPECT_EQ(map.count(), 2);
EXPECT_EQ(map.getString(0), "This is a test");
EXPECT_EQ(map.getInt(1), 1234);
auto builder2 = MapBufferBuilder();
builder2.putInt(0, 4321);
builder2.putMapBuffer(1, map);
auto map2 = builder2.build();
EXPECT_EQ(map2.count(), 2);
EXPECT_EQ(map2.getInt(0), 4321);
MapBuffer readMap2 = map2.getMapBuffer(1);
EXPECT_EQ(readMap2.count(), 2);
EXPECT_EQ(readMap2.getString(0), "This is a test");
EXPECT_EQ(readMap2.getInt(1), 1234);
}
TEST(MapBufferTest, testMapListEntries) {
std::vector<MapBuffer> mapBufferList;
auto builder = MapBufferBuilder();
builder.putString(0, "This is a test");
builder.putInt(1, 1234);
mapBufferList.push_back(builder.build());
auto builder2 = MapBufferBuilder();
builder2.putInt(2, 4321);
builder2.putDouble(3, 908.1);
mapBufferList.push_back(builder2.build());
auto builder3 = MapBufferBuilder();
builder3.putMapBufferList(5, mapBufferList);
auto map = builder3.build();
std::vector<MapBuffer> mapBufferList2 = map.getMapBufferList(5);
EXPECT_EQ(mapBufferList2.size(), 2);
EXPECT_EQ(mapBufferList2[0].getString(0), "This is a test");
EXPECT_EQ(mapBufferList2[0].getInt(1), 1234);
EXPECT_EQ(mapBufferList2[1].getDouble(3), 908.1);
}
TEST(MapBufferTest, testMapRandomAccess) {
auto builder = MapBufferBuilder();
builder.putInt(1234, 4321);
builder.putString(0, "This is a test");
builder.putDouble(8, 908.1);
builder.putString(65535, "Let's count: 的, 一, 是");
auto map = builder.build();
EXPECT_EQ(map.count(), 4);
EXPECT_EQ(map.getString(0), "This is a test");
EXPECT_EQ(map.getDouble(8), 908.1);
EXPECT_EQ(map.getInt(1234), 4321);
EXPECT_EQ(map.getString(65535), "Let's count: 的, 一, 是");
}