From 7cf27abd816f32d4ceb930bcd813652c950c5330 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Thu, 2 Jul 2026 05:53:04 -0700 Subject: [PATCH 1/3] Add IntBuffer and DoubleBuffer entry types to MapBuffer Summary: Adds two new MapBuffer entry types, `IntBuffer` and `DoubleBuffer`, for storing homogeneous arrays of ints and doubles compactly in the dynamic data section. Unlike `Map` / map lists, these carry no per-element key/type overhead: a batch of N values costs ~N*elementSize bytes plus a single 4-byte count prefix instead of N 12-byte buckets. The bucket value holds the offset of the array within the dynamic data section. Covers the full surface: the C++ reader (`MapBuffer::getIntBuffer` / `getDoubleBuffer`), the C++ builder (`MapBufferBuilder::putIntBuffer` / `putDoubleBuffer`), and the Kotlin reader API (`MapBuffer.getIntBuffer` / `getDoubleBuffer`, `Entry.intBufferValue` / `doubleBufferValue`). The `DataType` enum gains `IntBuffer = 6` and `DoubleBuffer = 7`, kept in sync across C++ and Kotlin. Changelog: [General][Added] - Add `IntBuffer` and `DoubleBuffer` entry types to MapBuffer for compact homogeneous int/double arrays Differential Revision: D109848476 --- .../ReactAndroid/api/ReactAndroid.api | 8 ++ .../react/common/mapbuffer/MapBuffer.kt | 40 ++++++++ .../common/mapbuffer/ReadableMapBuffer.kt | 38 ++++++++ .../common/mapbuffer/WritableMapBuffer.kt | 30 ++++++ .../common/mapbuffer/JWritableMapBuffer.cpp | 13 +++ .../react/renderer/mapbuffer/MapBuffer.cpp | 40 ++++++++ .../react/renderer/mapbuffer/MapBuffer.h | 16 +++- .../renderer/mapbuffer/MapBufferBuilder.cpp | 46 +++++++++ .../renderer/mapbuffer/MapBufferBuilder.h | 4 + .../mapbuffer/tests/MapBufferTest.cpp | 95 +++++++++++++++++++ .../api-snapshots/ReactAndroidDebugCxx.api | 6 ++ .../api-snapshots/ReactAndroidNewarchCxx.api | 6 ++ .../api-snapshots/ReactAndroidReleaseCxx.api | 6 ++ .../api-snapshots/ReactAppleDebugCxx.api | 6 ++ .../api-snapshots/ReactAppleNewarchCxx.api | 6 ++ .../api-snapshots/ReactAppleReleaseCxx.api | 6 ++ .../api-snapshots/ReactCommonDebugCxx.api | 6 ++ .../api-snapshots/ReactCommonNewarchCxx.api | 6 ++ .../api-snapshots/ReactCommonReleaseCxx.api | 6 ++ 19 files changed, 382 insertions(+), 2 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 699c267339a7..3eae29cea63a 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -1665,7 +1665,9 @@ public abstract interface class com/facebook/react/common/mapbuffer/MapBuffer : public abstract fun getBoolean (I)Z public abstract fun getCount ()I public abstract fun getDouble (I)D + public abstract fun getDoubleBuffer (I)[D public abstract fun getInt (I)I + public abstract fun getIntBuffer (I)[I public abstract fun getKeyOffset (I)I public abstract fun getLong (I)J public abstract fun getMapBuffer (I)Lcom/facebook/react/common/mapbuffer/MapBuffer; @@ -1680,7 +1682,9 @@ public final class com/facebook/react/common/mapbuffer/MapBuffer$Companion { public final class com/facebook/react/common/mapbuffer/MapBuffer$DataType : java/lang/Enum { public static final field BOOL Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field DOUBLE Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; + public static final field DOUBLE_BUFFER Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field INT Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; + public static final field INT_BUFFER Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field LONG Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field MAP Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field STRING Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; @@ -1691,7 +1695,9 @@ public final class com/facebook/react/common/mapbuffer/MapBuffer$DataType : java public abstract interface class com/facebook/react/common/mapbuffer/MapBuffer$Entry { public abstract fun getBooleanValue ()Z + public abstract fun getDoubleBufferValue ()[D public abstract fun getDoubleValue ()D + public abstract fun getIntBufferValue ()[I public abstract fun getIntValue ()I public abstract fun getKey ()I public abstract fun getLongValue ()J @@ -1708,7 +1714,9 @@ public final class com/facebook/react/common/mapbuffer/ReadableMapBuffer : com/f public fun getBoolean (I)Z public fun getCount ()I public fun getDouble (I)D + public fun getDoubleBuffer (I)[D public fun getInt (I)I + public fun getIntBuffer (I)[I public fun getKeyOffset (I)I public fun getLong (I)J public synthetic fun getMapBuffer (I)Lcom/facebook/react/common/mapbuffer/MapBuffer; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt index 44af587bd9f1..17db9cd7a1d3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt @@ -46,6 +46,8 @@ public interface MapBuffer : Iterable { STRING, MAP, LONG, + INT_BUFFER, + DOUBLE_BUFFER, } /** @@ -161,6 +163,30 @@ public interface MapBuffer : Iterable { */ public fun getMapBufferList(key: Int): List + /** + * Provides parsed [IntArray] value if the entry for given key exists with [DataType.INT_BUFFER] + * type. This is a compact representation of a homogeneous list of ints with no per-element + * key/type overhead. + * + * @param key key to lookup the [IntArray] value for + * @return value associated with the requested key + * @throws IllegalArgumentException if the key doesn't exist + * @throws IllegalStateException if the data type doesn't match + */ + public fun getIntBuffer(key: Int): IntArray + + /** + * Provides parsed [DoubleArray] value if the entry for given key exists with + * [DataType.DOUBLE_BUFFER] type. This is a compact representation of a homogeneous list of + * doubles with no per-element key/type overhead. + * + * @param key key to lookup the [DoubleArray] value for + * @return value associated with the requested key + * @throws IllegalArgumentException if the key doesn't exist + * @throws IllegalStateException if the data type doesn't match + */ + public fun getDoubleBuffer(key: Int): DoubleArray + /** Iterable entry representing parsed MapBuffer values */ public interface Entry { /** @@ -213,5 +239,19 @@ public interface MapBuffer : Iterable { * @throws IllegalStateException if the data type doesn't match [DataType.MAP] */ public val mapBufferValue: MapBuffer + + /** + * Entry value represented as [IntArray] + * + * @throws IllegalStateException if the data type doesn't match [DataType.INT_BUFFER] + */ + public val intBufferValue: IntArray + + /** + * Entry value represented as [DoubleArray] + * + * @throws IllegalStateException if the data type doesn't match [DataType.DOUBLE_BUFFER] + */ + public val doubleBufferValue: DoubleArray } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt index c73ccb6da1dc..35ed29590051 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt @@ -152,6 +152,24 @@ private constructor( return readMapBufferList } + private fun readIntBufferValue(bufferPosition: Int): IntArray { + var offset = offsetForDynamicData + buffer.getInt(bufferPosition) + val count = buffer.getInt(offset) + offset += Int.SIZE_BYTES + val result = IntArray(count) + buffer.duplicate().order(buffer.order()).apply { position(offset) }.asIntBuffer().get(result) + return result + } + + private fun readDoubleBufferValue(bufferPosition: Int): DoubleArray { + var offset = offsetForDynamicData + buffer.getInt(bufferPosition) + val count = buffer.getInt(offset) + offset += Int.SIZE_BYTES + val result = DoubleArray(count) + buffer.duplicate().order(buffer.order()).apply { position(offset) }.asDoubleBuffer().get(result) + return result + } + private fun getKeyOffsetForBucketIndex(bucketIndex: Int): Int { return offsetToMapBuffer + HEADER_SIZE + BUCKET_SIZE * bucketIndex } @@ -193,6 +211,12 @@ private constructor( override fun getMapBufferList(key: Int): List = readMapBufferListValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.MAP)) + override fun getIntBuffer(key: Int): IntArray = + readIntBufferValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.INT_BUFFER)) + + override fun getDoubleBuffer(key: Int): DoubleArray = + readDoubleBufferValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.DOUBLE_BUFFER)) + override fun hashCode(): Int { buffer.rewind() return buffer.hashCode() @@ -229,6 +253,8 @@ private constructor( append('"') } MapBuffer.DataType.MAP -> append(entry.mapBufferValue.toString()) + MapBuffer.DataType.INT_BUFFER -> append(entry.intBufferValue.contentToString()) + MapBuffer.DataType.DOUBLE_BUFFER -> append(entry.doubleBufferValue.contentToString()) } } } @@ -311,6 +337,18 @@ private constructor( assertType(MapBuffer.DataType.MAP) return readMapBufferValue(bucketOffset + VALUE_OFFSET) } + + override val intBufferValue: IntArray + get() { + assertType(MapBuffer.DataType.INT_BUFFER) + return readIntBufferValue(bucketOffset + VALUE_OFFSET) + } + + override val doubleBufferValue: DoubleArray + get() { + assertType(MapBuffer.DataType.DOUBLE_BUFFER) + return readDoubleBufferValue(bucketOffset + VALUE_OFFSET) + } } public companion object { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt index d75e8a39a8a2..be6895b25fa9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt @@ -84,6 +84,24 @@ internal class WritableMapBuffer : MapBuffer { */ fun put(key: Int, value: MapBuffer): WritableMapBuffer = putInternal(key, value) + /** + * Adds an [IntArray] value for given key to the current MapBuffer. + * + * @param key entry key + * @param value entry value + * @throws IllegalArgumentException if key is out of [UShort] range + */ + fun put(key: Int, value: IntArray): WritableMapBuffer = putInternal(key, value) + + /** + * Adds a [DoubleArray] value for given key to the current MapBuffer. + * + * @param key entry key + * @param value entry value + * @throws IllegalArgumentException if key is out of [UShort] range + */ + fun put(key: Int, value: DoubleArray): WritableMapBuffer = putInternal(key, value) + private fun putInternal(key: Int, value: Any): WritableMapBuffer { require(key in KEY_RANGE) { "Only integers in [${UShort.MIN_VALUE};${UShort.MAX_VALUE}] range are allowed for keys." @@ -126,6 +144,10 @@ internal class WritableMapBuffer : MapBuffer { override fun getMapBufferList(key: Int): List = verifyValue(key, values.get(key)) + override fun getIntBuffer(key: Int): IntArray = verifyValue(key, values.get(key)) + + override fun getDoubleBuffer(key: Int): DoubleArray = verifyValue(key, values.get(key)) + /** Generalizes verification of the value types based on the requested type. */ private inline fun verifyValue(key: Int, value: Any?): T { require(value != null) { "Key not found: $key" } @@ -143,6 +165,8 @@ internal class WritableMapBuffer : MapBuffer { is Double -> DataType.DOUBLE is String -> DataType.STRING is MapBuffer -> DataType.MAP + is IntArray -> DataType.INT_BUFFER + is DoubleArray -> DataType.DOUBLE_BUFFER else -> throw IllegalStateException("Key $key has value of unknown type: ${value.javaClass}") } } @@ -176,6 +200,12 @@ internal class WritableMapBuffer : MapBuffer { override val mapBufferValue: MapBuffer get() = verifyValue(key, values.valueAt(index)) + + override val intBufferValue: IntArray + get() = verifyValue(key, values.valueAt(index)) + + override val doubleBufferValue: DoubleArray + get() = verifyValue(key, values.valueAt(index)) } /* diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/mapbuffer/react/common/mapbuffer/JWritableMapBuffer.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/mapbuffer/react/common/mapbuffer/JWritableMapBuffer.cpp index 3273c5202bcd..f243aeae4b82 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/mapbuffer/react/common/mapbuffer/JWritableMapBuffer.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/mapbuffer/react/common/mapbuffer/JWritableMapBuffer.cpp @@ -35,6 +35,8 @@ MapBuffer JWritableMapBuffer::getMapBuffer() { static const auto stringClass = jni::JString::javaClassStatic(); static const auto readableMapClass = JReadableMapBuffer::javaClassStatic(); static const auto writableMapClass = JWritableMapBuffer::javaClassStatic(); + static const auto intArrayClass = jni::JArrayInt::javaClassStatic(); + static const auto doubleArrayClass = jni::JArrayDouble::javaClassStatic(); if (value->isInstanceOf(booleanClass)) { auto element = jni::static_ref_cast(value); @@ -56,6 +58,17 @@ MapBuffer JWritableMapBuffer::getMapBuffer() { auto element = jni::static_ref_cast(value); builder.putMapBuffer(key, element->getMapBuffer()); + } else if (value->isInstanceOf(intArrayClass)) { + auto array = jni::static_ref_cast(value); + auto pinned = array->pin(); + builder.putIntBuffer( + key, + std::vector(pinned.get(), pinned.get() + pinned.size())); + } else if (value->isInstanceOf(doubleArrayClass)) { + auto array = jni::static_ref_cast(value); + auto pinned = array->pin(); + builder.putDoubleBuffer( + key, std::vector(pinned.get(), pinned.get() + pinned.size())); } } diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp index 401a6670d03b..e7c0a97ffe5c 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp @@ -163,6 +163,46 @@ std::vector MapBuffer::getMapBufferList(MapBuffer::Key key) const { return mapBufferList; } +std::vector MapBuffer::getIntBuffer(MapBuffer::Key key) const { + auto bucketIndex = getKeyBucket(key); + react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer"); + if (bucketIndex == -1) { + return {}; + } + + int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex); + int32_t count = *reinterpret_cast(bytes_.data() + offset); + + std::vector result(count); + if (count > 0) { + memcpy( + result.data(), + bytes_.data() + offset + sizeof(int32_t), + static_cast(count) * sizeof(int32_t)); + } + return result; +} + +std::vector MapBuffer::getDoubleBuffer(MapBuffer::Key key) const { + auto bucketIndex = getKeyBucket(key); + react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer"); + if (bucketIndex == -1) { + return {}; + } + + int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex); + int32_t count = *reinterpret_cast(bytes_.data() + offset); + + std::vector result(count); + if (count > 0) { + memcpy( + result.data(), + bytes_.data() + offset + sizeof(int32_t), + static_cast(count) * sizeof(double)); + } + return result; +} + size_t MapBuffer::size() const { return bytes_.size(); } diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h index 1ae3595368c9..2d920af86e8e 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h @@ -94,8 +94,9 @@ class MapBuffer { /** * 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. + * Keep in sync with the `DataType` enum in `MapBuffer.kt` + * (packages/react-native/ReactAndroid/.../common/mapbuffer/MapBuffer.kt), + * which is ordinal-indexed on the JVM side, so the order must match exactly. */ enum DataType : uint16_t { Boolean = 0, @@ -104,6 +105,13 @@ class MapBuffer { String = 3, Map = 4, Long = 5, + // Homogeneous, length-prefixed arrays stored contiguously in the dynamic + // data section. Unlike Map, they carry no per-element key/type overhead, so + // a batch of N values costs ~N*elementSize bytes plus a single 4-byte count + // prefix instead of N*12-byte buckets. The bucket value is the offset of the + // array within the dynamic data section. + IntBuffer = 6, + DoubleBuffer = 7, }; explicit MapBuffer(std::vector data); @@ -131,6 +139,10 @@ class MapBuffer { std::vector getMapBufferList(MapBuffer::Key key) const; + std::vector getIntBuffer(MapBuffer::Key key) const; + + std::vector getDoubleBuffer(MapBuffer::Key key) const; + size_t size() const; const uint8_t *data() const; diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp index 244f1065b524..c0e77bdbb5d1 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp @@ -161,6 +161,52 @@ void MapBufferBuilder::putMapBufferList( INT_SIZE); } +void MapBufferBuilder::putIntBuffer( + MapBuffer::Key key, + const std::vector& value) { + // Wire format: [element count (int32)] + [count * int32]. The count is the + // number of elements, not bytes; see MapBuffer::getIntBuffer. + auto count = static_cast(value.size()); + auto payloadSize = static_cast(value.size() * sizeof(int32_t)); + + auto offset = static_cast(dynamicData_.size()); + dynamicData_.resize(offset + INT_SIZE + payloadSize, 0); + memcpy(dynamicData_.data() + offset, &count, INT_SIZE); + if (payloadSize > 0) { + memcpy(dynamicData_.data() + offset + INT_SIZE, value.data(), payloadSize); + } + + storeKeyValue( + key, + MapBuffer::DataType::IntBuffer, + reinterpret_cast(&offset), + INT_SIZE); +} + +void MapBufferBuilder::putDoubleBuffer( + MapBuffer::Key key, + const std::vector& value) { + // Wire format: [element count (int32)] + [count * double]. Doubles are copied + // byte-for-byte; the reader uses memcpy, so the payload needs no special + // alignment for correctness. A consumer that wants a zero-copy typed view on + // the JVM (ByteBuffer::asDoubleBuffer) must ensure 8-byte alignment itself. + auto count = static_cast(value.size()); + auto payloadSize = static_cast(value.size() * sizeof(double)); + + auto offset = static_cast(dynamicData_.size()); + dynamicData_.resize(offset + INT_SIZE + payloadSize, 0); + memcpy(dynamicData_.data() + offset, &count, INT_SIZE); + if (payloadSize > 0) { + memcpy(dynamicData_.data() + offset + INT_SIZE, value.data(), payloadSize); + } + + storeKeyValue( + key, + MapBuffer::DataType::DoubleBuffer, + reinterpret_cast(&offset), + INT_SIZE); +} + static inline bool compareBuckets( const MapBuffer::Bucket& a, const MapBuffer::Bucket& b) { diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h index af054da55789..2963c77a9b36 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h @@ -39,6 +39,10 @@ class MapBufferBuilder { void putMapBufferList(MapBuffer::Key key, const std::vector &mapBufferList); + void putIntBuffer(MapBuffer::Key key, const std::vector &value); + + void putDoubleBuffer(MapBuffer::Key key, const std::vector &value); + MapBuffer build(); private: diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp b/packages/react-native/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp index 3cc169563fca..ce4fd6237c03 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp @@ -205,6 +205,101 @@ TEST(MapBufferTest, testMapListEntries) { EXPECT_EQ(mapBufferList2[1].getDouble(3), 908.1); } +TEST(MapBufferTest, testEmptyMapBufferList) { + auto builder = MapBufferBuilder(); + + builder.putMapBufferList(0, {}); + auto map = builder.build(); + + EXPECT_EQ(map.getMapBufferList(0).size(), 0); +} + +// Place the list behind another dynamic-data entry so its offset is non-zero, +// exercising `getDynamicDataOffset() + getIntAtBucket(...)` against a non-zero +// base rather than the zero-offset path testMapListEntries covers. +TEST(MapBufferTest, testMapListEntriesAtNonZeroOffset) { + std::vector mapBufferList; + auto inner = MapBufferBuilder(); + inner.putString(0, "inner"); + inner.putInt(1, 42); + mapBufferList.push_back(inner.build()); + + auto builder = MapBufferBuilder(); + builder.putString(0, "prefix"); + builder.putMapBufferList(1, mapBufferList); + auto map = builder.build(); + + EXPECT_EQ(map.getString(0), "prefix"); + std::vector readList = map.getMapBufferList(1); + EXPECT_EQ(readList.size(), 1); + EXPECT_EQ(readList[0].getString(0), "inner"); + EXPECT_EQ(readList[0].getInt(1), 42); +} + +TEST(MapBufferTest, testIntBufferEntries) { + auto builder = MapBufferBuilder(); + + std::vector values{ + 1, + -2, + 3, + std::numeric_limits::min(), + std::numeric_limits::max()}; + builder.putIntBuffer(0, values); + auto map = builder.build(); + + EXPECT_EQ(map.count(), 1); + EXPECT_EQ(map.getIntBuffer(0), values); +} + +TEST(MapBufferTest, testEmptyIntBuffer) { + auto builder = MapBufferBuilder(); + + builder.putIntBuffer(0, {}); + auto map = builder.build(); + + EXPECT_EQ(map.getIntBuffer(0).size(), 0); +} + +TEST(MapBufferTest, testDoubleBufferEntries) { + auto builder = MapBufferBuilder(); + + std::vector values{0.0, -1.5, 3.14159, 1e300, -1e-300}; + builder.putDoubleBuffer(0, values); + auto map = builder.build(); + + EXPECT_EQ(map.count(), 1); + EXPECT_EQ(map.getDoubleBuffer(0), values); +} + +TEST(MapBufferTest, testEmptyDoubleBuffer) { + auto builder = MapBufferBuilder(); + + builder.putDoubleBuffer(0, {}); + auto map = builder.build(); + + EXPECT_EQ(map.getDoubleBuffer(0).size(), 0); +} + +// Mirrors the batched-animated-props use case: a pair of typed streams plus +// some scalar metadata, with keys inserted out of order to exercise both the +// dynamic-data section and the bucket sort path. +TEST(MapBufferTest, testIntAndDoubleBuffersAlongsideScalars) { + std::vector intStream{1, 100, 1, 2, 4, 15, 4}; + std::vector doubleStream{0.5, 12.0, 0.25}; + + auto builder = MapBufferBuilder(); + builder.putDoubleBuffer(2, doubleStream); + builder.putInt(0, 7); + builder.putIntBuffer(1, intStream); + auto map = builder.build(); + + EXPECT_EQ(map.count(), 3); + EXPECT_EQ(map.getInt(0), 7); + EXPECT_EQ(map.getIntBuffer(1), intStream); + EXPECT_EQ(map.getDoubleBuffer(2), doubleStream); +} + TEST(MapBufferTest, testMapRandomAccess) { auto builder = MapBufferBuilder(); builder.putInt(1234, 4321); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index d484218abb9d..a3c11b134bd6 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -3249,7 +3249,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -3257,7 +3259,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -3282,7 +3286,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index ce3256b5f8be..ffe4f90614c7 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -3158,7 +3158,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -3166,7 +3168,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -3191,7 +3195,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 04c7b0691b18..82b7241990ea 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -3246,7 +3246,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -3254,7 +3256,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -3279,7 +3283,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 6e8bf9f249d1..8efdf4143c47 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -5484,7 +5484,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -5492,7 +5494,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -5517,7 +5521,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 8c903a172ef6..6c7799c8dc30 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -5408,7 +5408,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -5416,7 +5418,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -5441,7 +5445,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 6f48057c356d..276c824bec11 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -5481,7 +5481,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -5489,7 +5491,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -5514,7 +5518,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 66d75beaa700..676107ead62c 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -2170,7 +2170,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -2178,7 +2180,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -2203,7 +2207,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index cd680589a109..69b7eb0ca5f9 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -2106,7 +2106,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -2114,7 +2116,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -2139,7 +2143,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index b6573d00004b..5d77f4412fff 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -2167,7 +2167,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -2175,7 +2177,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -2200,7 +2204,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); From 1a212f42ab82bdcfafdeb8f8be8be9cac5f54c87 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Thu, 2 Jul 2026 05:53:05 -0700 Subject: [PATCH 2/3] Add MapBufferList entry type to MapBuffer Summary: Introduces a dedicated `MapBufferList` `DataType` for an ordered array of nested MapBuffers, instead of overloading the `Map` type for lists. This makes a list of MapBuffers self-describing and distinguishable from a single nested `Map` (they were byte-distinct in payload but previously shared the `Map` type tag). Updates the C++ builder (`putMapBufferList`), the Kotlin `MapBuffer` interface, `ReadableMapBuffer`, and `WritableMapBuffer`, and adds cross-language JNI round-trip coverage in the serialization instrumentation test. Changelog: [Android][Added] - Add a dedicated `MapBufferList` type to `MapBuffer` for ordered lists of nested `MapBuffer`s Differential Revision: D109848477 --- .../react-native/ReactAndroid/api/ReactAndroid.api | 2 ++ .../com/facebook/react/common/mapbuffer/MapBuffer.kt | 12 ++++++++++-- .../react/common/mapbuffer/ReadableMapBuffer.kt | 9 ++++++++- .../react/common/mapbuffer/WritableMapBuffer.kt | 3 +++ .../ReactCommon/react/renderer/mapbuffer/MapBuffer.h | 4 ++++ .../react/renderer/mapbuffer/MapBufferBuilder.cpp | 5 +++-- .../cxx-api/api-snapshots/ReactAndroidDebugCxx.api | 1 + .../cxx-api/api-snapshots/ReactAndroidNewarchCxx.api | 1 + .../cxx-api/api-snapshots/ReactAndroidReleaseCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api | 1 + .../cxx-api/api-snapshots/ReactAppleNewarchCxx.api | 1 + .../cxx-api/api-snapshots/ReactAppleReleaseCxx.api | 1 + .../cxx-api/api-snapshots/ReactCommonDebugCxx.api | 1 + .../cxx-api/api-snapshots/ReactCommonNewarchCxx.api | 1 + .../cxx-api/api-snapshots/ReactCommonReleaseCxx.api | 1 + 15 files changed, 39 insertions(+), 5 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 3eae29cea63a..c4e2927d1568 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -1687,6 +1687,7 @@ public final class com/facebook/react/common/mapbuffer/MapBuffer$DataType : java public static final field INT_BUFFER Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field LONG Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field MAP Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; + public static final field MAP_BUFFER_LIST Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field STRING Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; @@ -1701,6 +1702,7 @@ public abstract interface class com/facebook/react/common/mapbuffer/MapBuffer$En public abstract fun getIntValue ()I public abstract fun getKey ()I public abstract fun getLongValue ()J + public abstract fun getMapBufferListValue ()Ljava/util/List; public abstract fun getMapBufferValue ()Lcom/facebook/react/common/mapbuffer/MapBuffer; public abstract fun getStringValue ()Ljava/lang/String; public abstract fun getType ()Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt index 17db9cd7a1d3..feba0dc97d6a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt @@ -48,6 +48,7 @@ public interface MapBuffer : Iterable { LONG, INT_BUFFER, DOUBLE_BUFFER, + MAP_BUFFER_LIST, } /** @@ -153,8 +154,8 @@ public interface MapBuffer : Iterable { public fun getMapBuffer(key: Int): MapBuffer /** - * Provides parsed [List] value if the entry for given key exists with [DataType.MAP] - * type + * Provides parsed [List] value if the entry for given key exists with + * [DataType.MAP_BUFFER_LIST] type * * @param key key to lookup [List] value for * @return value associated with the requested key @@ -253,5 +254,12 @@ public interface MapBuffer : Iterable { * @throws IllegalStateException if the data type doesn't match [DataType.DOUBLE_BUFFER] */ public val doubleBufferValue: DoubleArray + + /** + * Entry value represented as [List] + * + * @throws IllegalStateException if the data type doesn't match [DataType.MAP_BUFFER_LIST] + */ + public val mapBufferListValue: List } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt index 35ed29590051..875873bfb0cc 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt @@ -209,7 +209,7 @@ private constructor( readMapBufferValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.MAP)) override fun getMapBufferList(key: Int): List = - readMapBufferListValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.MAP)) + readMapBufferListValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.MAP_BUFFER_LIST)) override fun getIntBuffer(key: Int): IntArray = readIntBufferValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.INT_BUFFER)) @@ -255,6 +255,7 @@ private constructor( MapBuffer.DataType.MAP -> append(entry.mapBufferValue.toString()) MapBuffer.DataType.INT_BUFFER -> append(entry.intBufferValue.contentToString()) MapBuffer.DataType.DOUBLE_BUFFER -> append(entry.doubleBufferValue.contentToString()) + MapBuffer.DataType.MAP_BUFFER_LIST -> append(entry.mapBufferListValue.toString()) } } } @@ -349,6 +350,12 @@ private constructor( assertType(MapBuffer.DataType.DOUBLE_BUFFER) return readDoubleBufferValue(bucketOffset + VALUE_OFFSET) } + + override val mapBufferListValue: List + get() { + assertType(MapBuffer.DataType.MAP_BUFFER_LIST) + return readMapBufferListValue(bucketOffset + VALUE_OFFSET) + } } public companion object { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt index be6895b25fa9..dea593ca6e7b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt @@ -206,6 +206,9 @@ internal class WritableMapBuffer : MapBuffer { override val doubleBufferValue: DoubleArray get() = verifyValue(key, values.valueAt(index)) + + override val mapBufferListValue: List + get() = verifyValue(key, values.valueAt(index)) } /* diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h index 2d920af86e8e..ae89b674b6a3 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h @@ -112,6 +112,10 @@ class MapBuffer { // array within the dynamic data section. IntBuffer = 6, DoubleBuffer = 7, + // A homogeneous, ordered array of nested MapBuffers. Distinct from `Map` so + // that a list of MapBuffers is self-describing (a single Map and a list are + // byte-distinct in payload but previously shared the `Map` type tag). + MapBufferList = 8, }; explicit MapBuffer(std::vector data); diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp index c0e77bdbb5d1..8ee37c1c01e9 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp @@ -153,10 +153,11 @@ void MapBufferBuilder::putMapBufferList( mapBufferSize); } - // Store Key and pointer to the string + // Store Key and pointer to the list. Uses the dedicated MapBufferList type so + // the entry is self-describing and distinguishable from a single Map. storeKeyValue( key, - MapBuffer::DataType::Map, + MapBuffer::DataType::MapBufferList, reinterpret_cast(&offset), INT_SIZE); } diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index a3c11b134bd6..8022b30807e7 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -3264,6 +3264,7 @@ enum facebook::react::MapBuffer::DataType : uint16_t { IntBuffer, Long, Map, + MapBufferList, String, } diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index ffe4f90614c7..c241745f95f5 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -3173,6 +3173,7 @@ enum facebook::react::MapBuffer::DataType : uint16_t { IntBuffer, Long, Map, + MapBufferList, String, } diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 82b7241990ea..73beff563c82 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -3261,6 +3261,7 @@ enum facebook::react::MapBuffer::DataType : uint16_t { IntBuffer, Long, Map, + MapBufferList, String, } diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 8efdf4143c47..0522de96cd20 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -5499,6 +5499,7 @@ enum facebook::react::MapBuffer::DataType : uint16_t { IntBuffer, Long, Map, + MapBufferList, String, } diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 6c7799c8dc30..e961ff1cf987 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -5423,6 +5423,7 @@ enum facebook::react::MapBuffer::DataType : uint16_t { IntBuffer, Long, Map, + MapBufferList, String, } diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 276c824bec11..f5fd13c275cc 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -5496,6 +5496,7 @@ enum facebook::react::MapBuffer::DataType : uint16_t { IntBuffer, Long, Map, + MapBufferList, String, } diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 676107ead62c..5ec53a2f575b 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -2185,6 +2185,7 @@ enum facebook::react::MapBuffer::DataType : uint16_t { IntBuffer, Long, Map, + MapBufferList, String, } diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 69b7eb0ca5f9..2b67e1c5b4f2 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -2121,6 +2121,7 @@ enum facebook::react::MapBuffer::DataType : uint16_t { IntBuffer, Long, Map, + MapBufferList, String, } diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 5d77f4412fff..2f7f3c22e513 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -2182,6 +2182,7 @@ enum facebook::react::MapBuffer::DataType : uint16_t { IntBuffer, Long, Map, + MapBufferList, String, } From dd90c0792054885a32352f1e04b45bad29748c23 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Thu, 2 Jul 2026 07:28:14 -0700 Subject: [PATCH 3/3] Optimize MapBuffer representation (#57361) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/react/react-native/pull/57361 Broadens the former header-shrink change into a single representation-optimization commit for MapBuffer. It bundles three layout optimizations that were previously split: (1) the header is reduced to a single 2-byte `count` field; (2) multi-byte values are read via `memcpy` so unaligned access is well-defined on all platforms; (3) every dynamic-data entry (`String`, `Map`, `MapBufferList`, `IntBuffer`, `DoubleBuffer`) packs its `[offset][byteLength]` into the bucket's 8-byte value instead of writing an in-band length prefix into the dynamic data section. Net effect: 4 fewer bytes per dynamic entry, one fewer indirection on read (the length is already in the bucket), and every dynamic entry becomes self-delimiting from its bucket alone. No public API change — only the internal serialized representation. Changelog: [Internal] Reviewed By: lenaic, zeyap Differential Revision: D109848478 --- .../common/mapbuffer/ReadableMapBuffer.kt | 50 ++++--- .../react/renderer/mapbuffer/MapBuffer.cpp | 124 ++++++++++++------ .../react/renderer/mapbuffer/MapBuffer.h | 32 ++--- .../renderer/mapbuffer/MapBufferBuilder.cpp | 124 ++++++++---------- .../mapbuffer/tests/MapBufferTest.cpp | 2 +- .../api-snapshots/ReactAndroidDebugCxx.api | 3 - .../api-snapshots/ReactAndroidNewarchCxx.api | 3 - .../api-snapshots/ReactAndroidReleaseCxx.api | 3 - .../api-snapshots/ReactAppleDebugCxx.api | 3 - .../api-snapshots/ReactAppleNewarchCxx.api | 3 - .../api-snapshots/ReactAppleReleaseCxx.api | 3 - .../api-snapshots/ReactCommonDebugCxx.api | 3 - .../api-snapshots/ReactCommonNewarchCxx.api | 3 - .../api-snapshots/ReactCommonReleaseCxx.api | 3 - 14 files changed, 181 insertions(+), 178 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt index 875873bfb0cc..de9adb5d7faa 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt @@ -51,13 +51,11 @@ private constructor( ReadableMapBuffer(buffer.duplicate().apply { position(offset) }, offset) private fun readHeader() { - // byte order - val storedAlignment = buffer.short - if (storedAlignment.toInt() != ALIGNMENT) { - buffer.order(ByteOrder.LITTLE_ENDIAN) - } - // count - count = readUnsignedShort(buffer.position()).toInt() + // The C++ writer always serializes in little-endian byte order. ByteBuffer + // defaults to big-endian and duplicate() resets the order, so set it + // explicitly on every instance, including nested clones. + buffer.order(ByteOrder.LITTLE_ENDIAN) + count = readUnsignedShort(offsetToMapBuffer).toInt() } /** @@ -122,26 +120,27 @@ private constructor( return readIntValue(bufferPosition) == 1 } + // Dynamic-data entries store [offset][byteLength] in the bucket's 8-byte + // value: getInt(bufferPosition) is the offset, getInt(bufferPosition + 4) is + // the byte length. The dynamic data section itself carries no length prefix. private fun readStringValue(bufferPosition: Int): String { val offset = offsetForDynamicData + buffer.getInt(bufferPosition) - val sizeOfString = buffer.getInt(offset) + val sizeOfString = buffer.getInt(bufferPosition + Int.SIZE_BYTES) val result = ByteArray(sizeOfString) - val stringOffset = offset + Int.SIZE_BYTES - buffer.position(stringOffset) - buffer[result, 0, sizeOfString] + buffer.position(offset) + buffer.get(result, 0, sizeOfString) return String(result) } private fun readMapBufferValue(position: Int): ReadableMapBuffer { val offset = offsetForDynamicData + buffer.getInt(position) - return cloneWithOffset(offset + Int.SIZE_BYTES) + return cloneWithOffset(offset) } private fun readMapBufferListValue(position: Int): List { val readMapBufferList = arrayListOf() - var offset = offsetForDynamicData + buffer.getInt(position) - val sizeMapBufferList = buffer.getInt(offset) - offset += Int.SIZE_BYTES + val offset = offsetForDynamicData + buffer.getInt(position) + val sizeMapBufferList = buffer.getInt(position + Int.SIZE_BYTES) var curLen = 0 while (curLen < sizeMapBufferList) { val sizeMapBuffer = buffer.getInt(offset + curLen) @@ -153,18 +152,18 @@ private constructor( } private fun readIntBufferValue(bufferPosition: Int): IntArray { - var offset = offsetForDynamicData + buffer.getInt(bufferPosition) - val count = buffer.getInt(offset) - offset += Int.SIZE_BYTES + val offset = offsetForDynamicData + buffer.getInt(bufferPosition) + val byteLength = buffer.getInt(bufferPosition + Int.SIZE_BYTES) + val count = byteLength / Int.SIZE_BYTES val result = IntArray(count) buffer.duplicate().order(buffer.order()).apply { position(offset) }.asIntBuffer().get(result) return result } private fun readDoubleBufferValue(bufferPosition: Int): DoubleArray { - var offset = offsetForDynamicData + buffer.getInt(bufferPosition) - val count = buffer.getInt(offset) - offset += Int.SIZE_BYTES + val offset = offsetForDynamicData + buffer.getInt(bufferPosition) + val byteLength = buffer.getInt(bufferPosition + Int.SIZE_BYTES) + val count = byteLength / Double.SIZE_BYTES val result = DoubleArray(count) buffer.duplicate().order(buffer.order()).apply { position(offset) }.asDoubleBuffer().get(result) return result @@ -359,13 +358,10 @@ private constructor( } public companion object { - // Value used to verify if the data is serialized with LittleEndian order. - private const val ALIGNMENT = 0xFE - - // 8 bytes = 2 (alignment) + 2 (count) + 4 (size) - private const val HEADER_SIZE = 8 + // 2 bytes = 2 (count) + private const val HEADER_SIZE = 2 - // 10 bytes = 2 (key) + 2 (type) + 8 (value) + // 12 bytes = 2 (key) + 2 (type) + 8 (value) private const val BUCKET_SIZE = 12 // 2 bytes = 2 (key) diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp index e7c0a97ffe5c..e657cc5514fc 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp @@ -8,8 +8,43 @@ #include "MapBuffer.h" #include +#include +#include + namespace facebook::react { +namespace { +// Reads a value of type T from a (possibly unaligned) offset in the buffer. +// MapBuffer's packed layout places multi-byte values at offsets that are not +// naturally aligned for their type (e.g. an 8-byte value at a 2-byte boundary), +// so dereferencing a reinterpret_cast pointer there is undefined behavior and +// can fault on 32-bit ARM. memcpy compiles to a single unaligned load on +// arm64/x86 and to alignment-safe loads on armv7. +template +inline T readUnaligned(const uint8_t* data, int32_t offset) { + T value; + std::memcpy(&value, data + offset, sizeof(T)); + return value; +} + +// Debug-asserts on OOB (catches corrupt buffers early in dev) AND clamps in +// release so a corrupt bucket length can never drive an OOB memcpy read. +// react_native_assert is compiled out in release, so the runtime cost outside +// dev is the single min() call. +inline int32_t +clampToBufferBounds(int32_t offset, int32_t byteLength, size_t bufferSize) { + react_native_assert(offset >= 0 && byteLength >= 0); + react_native_assert( + static_cast(offset) + static_cast(byteLength) <= + bufferSize); + size_t maxLength = bufferSize > static_cast(offset) + ? bufferSize - static_cast(offset) + : 0; + return static_cast( + std::min(static_cast(std::max(byteLength, 0)), maxLength)); +} +} // namespace + static inline int32_t bucketOffset(int32_t index) { return sizeof(MapBuffer::Header) + sizeof(MapBuffer::Bucket) * index; } @@ -18,16 +53,20 @@ static inline int32_t valueOffset(int32_t bucketIndex) { return bucketOffset(bucketIndex) + offsetof(MapBuffer::Bucket, data); } +// Dynamic-data entries pack [offset (low 32 bits)][byteLength (high 32 bits)] +// into the bucket's 8-byte value, so the payload in the dynamic data section +// carries no in-band length prefix. This returns the position of the high +// 32 bits (the length). +static inline int32_t lengthOffset(int32_t bucketIndex) { + return valueOffset(bucketIndex) + static_cast(sizeof(int32_t)); +} + // TODO T83483191: Extend MapBuffer C++ implementation to support basic random // access MapBuffer::MapBuffer(std::vector data) : bytes_(std::move(data)) { - auto header = reinterpret_cast(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(); + if (bytes_.size() >= sizeof(Header)) { + auto header = reinterpret_cast(bytes_.data()); + count_ = header->count; } } @@ -37,8 +76,7 @@ int32_t MapBuffer::getKeyBucket(Key key) const { while (lo <= hi) { int32_t mid = (lo + hi) >> 1; - Key midVal = - *reinterpret_cast(bytes_.data() + bucketOffset(mid)); + Key midVal = readUnaligned(bytes_.data(), bucketOffset(mid)); if (midVal < key) { lo = mid + 1; @@ -53,8 +91,7 @@ int32_t MapBuffer::getKeyBucket(Key key) const { } inline int32_t MapBuffer::getIntAtBucket(int32_t bucketIndex) const { - return *reinterpret_cast( - bytes_.data() + valueOffset(bucketIndex)); + return readUnaligned(bytes_.data(), valueOffset(bucketIndex)); } int32_t MapBuffer::getInt(Key key) const { @@ -74,8 +111,7 @@ int64_t MapBuffer::getLong(Key key) const { return 0; } - return *reinterpret_cast( - bytes_.data() + valueOffset(bucketIndex)); + return readUnaligned(bytes_.data(), valueOffset(bucketIndex)); } bool MapBuffer::getBool(Key key) const { @@ -89,8 +125,7 @@ double MapBuffer::getDouble(Key key) const { return 0; } - return *reinterpret_cast( - bytes_.data() + valueOffset(bucketIndex)); + return readUnaligned(bytes_.data(), valueOffset(bucketIndex)); } int32_t MapBuffer::getDynamicDataOffset() const { @@ -107,9 +142,10 @@ std::string MapBuffer::getString(Key key) const { } int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex); - int32_t stringLength = - *reinterpret_cast(bytes_.data() + offset); - const uint8_t* stringPtr = bytes_.data() + offset + sizeof(int); + auto stringLength = + readUnaligned(bytes_.data(), lengthOffset(bucketIndex)); + stringLength = clampToBufferBounds(offset, stringLength, bytes_.size()); + const uint8_t* stringPtr = bytes_.data() + offset; return {stringPtr, stringPtr + stringLength}; } @@ -122,17 +158,13 @@ MapBuffer MapBuffer::getMapBuffer(Key key) const { } int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex); - int32_t mapBufferLength = - *reinterpret_cast(bytes_.data() + offset); - size_t maxLength = bytes_.size() - offset - sizeof(int32_t); - if (mapBufferLength > maxLength) { - mapBufferLength = maxLength; - } + auto mapBufferLength = + readUnaligned(bytes_.data(), lengthOffset(bucketIndex)); + mapBufferLength = clampToBufferBounds(offset, mapBufferLength, bytes_.size()); std::vector value(mapBufferLength); - memcpy( - value.data(), bytes_.data() + offset + sizeof(int32_t), mapBufferLength); + memcpy(value.data(), bytes_.data() + offset, mapBufferLength); return MapBuffer(std::move(value)); } @@ -146,19 +178,27 @@ std::vector MapBuffer::getMapBufferList(MapBuffer::Key key) const { std::vector mapBufferList; int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex); - int32_t mapBufferListLength = - *reinterpret_cast(bytes_.data() + offset); - offset = offset + sizeof(uint32_t); + auto mapBufferListLength = + readUnaligned(bytes_.data(), lengthOffset(bucketIndex)); + mapBufferListLength = + clampToBufferBounds(offset, mapBufferListLength, bytes_.size()); int32_t curLen = 0; while (curLen < mapBufferListLength) { - int32_t mapBufferLength = - *reinterpret_cast(bytes_.data() + offset + curLen); - curLen = curLen + sizeof(uint32_t); + if (curLen + sizeof(int32_t) > mapBufferListLength) { + break; + } + + auto mapBufferLength = + readUnaligned(bytes_.data(), offset + curLen); + curLen += sizeof(int32_t); + + mapBufferLength = + clampToBufferBounds(offset + curLen, mapBufferLength, bytes_.size()); std::vector value(mapBufferLength); memcpy(value.data(), bytes_.data() + offset + curLen, mapBufferLength); mapBufferList.emplace_back(std::move(value)); - curLen = curLen + mapBufferLength; + curLen += mapBufferLength; } return mapBufferList; } @@ -171,13 +211,18 @@ std::vector MapBuffer::getIntBuffer(MapBuffer::Key key) const { } int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex); - int32_t count = *reinterpret_cast(bytes_.data() + offset); + auto byteLength = + readUnaligned(bytes_.data(), lengthOffset(bucketIndex)); + byteLength = clampToBufferBounds(offset, byteLength, bytes_.size()); + int32_t count = byteLength / static_cast(sizeof(int32_t)); std::vector result(count); if (count > 0) { + // Copy only whole elements: a clamped byteLength may not be a multiple of + // sizeof(int32_t), and result holds exactly count elements. memcpy( result.data(), - bytes_.data() + offset + sizeof(int32_t), + bytes_.data() + offset, static_cast(count) * sizeof(int32_t)); } return result; @@ -191,13 +236,18 @@ std::vector MapBuffer::getDoubleBuffer(MapBuffer::Key key) const { } int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex); - int32_t count = *reinterpret_cast(bytes_.data() + offset); + auto byteLength = + readUnaligned(bytes_.data(), lengthOffset(bucketIndex)); + byteLength = clampToBufferBounds(offset, byteLength, bytes_.size()); + int32_t count = byteLength / static_cast(sizeof(double)); std::vector result(count); if (count > 0) { + // Copy only whole elements: a clamped byteLength may not be a multiple of + // sizeof(double), and result holds exactly count elements. memcpy( result.data(), - bytes_.data() + offset + sizeof(int32_t), + bytes_.data() + offset, static_cast(count) * sizeof(double)); } return result; diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h index ae89b674b6a3..07b64bdfba99 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h @@ -40,11 +40,11 @@ class JReadableMapBuffer; * * 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 │ - * └───────────┴────────────┴────────────────────────┘ + * ┌──────Header──────┐ + * │ 2 bytes │ + * ├────Item count────┤ + * │ 2 bytes │ + * └──────────────────┘ * ┌────────────────────────────────────────────────────────────────────────────────────────┐ * │ Buckets (one per item in the map) │ * │ │ @@ -69,14 +69,8 @@ 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) @@ -89,7 +83,7 @@ class MapBuffer { }; #pragma pack(pop) - static_assert(sizeof(Header) == 8, "MapBuffer header size is incorrect."); + static_assert(sizeof(Header) == 2, "MapBuffer header size is incorrect."); static_assert(sizeof(Bucket) == 12, "MapBuffer bucket size is incorrect."); /** @@ -105,15 +99,17 @@ class MapBuffer { String = 3, Map = 4, Long = 5, - // Homogeneous, length-prefixed arrays stored contiguously in the dynamic + // Homogeneous arrays of raw elements stored contiguously in the dynamic // data section. Unlike Map, they carry no per-element key/type overhead, so - // a batch of N values costs ~N*elementSize bytes plus a single 4-byte count - // prefix instead of N*12-byte buckets. The bucket value is the offset of the - // array within the dynamic data section. + // a batch of N values costs ~N*elementSize bytes instead of N*12-byte + // buckets. The bucket value packs [offset][byteLength]; the element count is + // recovered as byteLength / elementSize. IntBuffer = 6, DoubleBuffer = 7, - // A homogeneous, ordered array of nested MapBuffers. Distinct from `Map` so - // that a list of MapBuffers is self-describing (a single Map and a list are + // A homogeneous, ordered array of nested MapBuffers. The bucket value packs + // [offset][byteLength] for the whole list region; within it each child stays + // framed as [int32 childSize][child bytes]. Distinct from `Map` so that a + // list of MapBuffers is self-describing (a single Map and a list are // byte-distinct in payload but previously shared the `Map` type tag). MapBufferList = 8, }; diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp index 8ee37c1c01e9..1944a01f261f 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp @@ -15,6 +15,14 @@ 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); +// Dynamic-data entries store their location in the bucket's 8-byte value as +// [offset (low 32 bits)][byteLength (high 32 bits)], so the payload in the +// dynamic data section needs no in-band length prefix. +static inline uint64_t packOffsetAndLength(int32_t offset, int32_t length) { + return static_cast(static_cast(offset)) | + (static_cast(static_cast(length)) << 32); +} + MapBuffer MapBufferBuilder::EMPTY() { return MapBufferBuilder(0).build(); } @@ -22,7 +30,6 @@ MapBuffer MapBufferBuilder::EMPTY() { MapBufferBuilder::MapBufferBuilder(uint32_t initialSize) { buckets_.reserve(initialSize); header_.count = 0; - header_.bufferSize = 0; } void MapBufferBuilder::storeKeyValue( @@ -84,128 +91,111 @@ void MapBufferBuilder::putLong(MapBuffer::Key key, int64_t value) { } void MapBufferBuilder::putString(MapBuffer::Key key, const std::string& value) { - // The wire format encodes lengths and offsets as int32_t (see - // MapBuffer::getString). Without an explicit narrowing cast, `auto` deduces - // size_t (8 bytes on 64-bit) and `memcpy(&x, ..., INT_SIZE)` then copies only - // the first 4 bytes of an 8-byte value: silent truncation on little-endian, - // wrong (high) bytes on big-endian. auto strSize = static_cast(value.size()); - const char* strData = value.data(); - - // format [length of string (int)] + [Array of Characters in the string] auto offset = static_cast(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 + // The bucket stores [offset][byteLength]; the dynamic section holds only the + // raw string bytes. + dynamicData_.resize(offset + strSize, 0); + if (strSize > 0) { + memcpy(dynamicData_.data() + offset, value.data(), strSize); + } + + uint64_t data = packOffsetAndLength(offset, strSize); storeKeyValue( key, MapBuffer::DataType::String, - reinterpret_cast(&offset), - INT_SIZE); + reinterpret_cast(&data), + sizeof(data)); } void MapBufferBuilder::putMapBuffer(MapBuffer::Key key, const MapBuffer& map) { - // Wire format encodes lengths and offsets as int32_t (see - // MapBuffer::getMapBuffer). Cast explicitly so memcpy(&x, ..., INT_SIZE) - // copies the full value, not the first 4 bytes of an 8-byte size_t. auto mapBufferSize = static_cast(map.size()); - auto offset = static_cast(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); + // The bucket stores [offset][byteLength]; the dynamic section holds only the + // serialized child MapBuffer bytes. + dynamicData_.resize(offset + mapBufferSize, 0); + memcpy(dynamicData_.data() + offset, map.data(), mapBufferSize); - // Store Key and pointer to the string + uint64_t data = packOffsetAndLength(offset, mapBufferSize); storeKeyValue( key, MapBuffer::DataType::Map, - reinterpret_cast(&offset), - INT_SIZE); + reinterpret_cast(&data), + sizeof(data)); } void MapBufferBuilder::putMapBufferList( MapBuffer::Key key, const std::vector& mapBufferList) { auto offset = static_cast(dynamicData_.size()); - int32_t dataSize = 0; - for (const MapBuffer& mapBuffer : mapBufferList) { - dataSize = dataSize + INT_SIZE + static_cast(mapBuffer.size()); - } - - dynamicData_.resize(offset + INT_SIZE, 0); - memcpy(dynamicData_.data() + offset, &dataSize, INT_SIZE); + // The bucket stores [offset][byteLength] for the whole list region; within it + // each child stays framed as [int32 childSize][child bytes] so the children + // remain individually delimited. for (const MapBuffer& mapBuffer : mapBufferList) { auto mapBufferSize = static_cast(mapBuffer.size()); - auto dynamicDataSize = static_cast(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_ + auto pos = static_cast(dynamicData_.size()); + dynamicData_.resize(pos + INT_SIZE + mapBufferSize, 0); + memcpy(dynamicData_.data() + pos, &mapBufferSize, INT_SIZE); memcpy( - dynamicData_.data() + dynamicDataSize + INT_SIZE, - mapBuffer.data(), - mapBufferSize); + dynamicData_.data() + pos + INT_SIZE, mapBuffer.data(), mapBufferSize); } - // Store Key and pointer to the list. Uses the dedicated MapBufferList type so - // the entry is self-describing and distinguishable from a single Map. + auto totalSize = static_cast(dynamicData_.size()) - offset; + uint64_t data = packOffsetAndLength(offset, totalSize); + // Uses the dedicated MapBufferList type so the entry is self-describing and + // distinguishable from a single Map. storeKeyValue( key, MapBuffer::DataType::MapBufferList, - reinterpret_cast(&offset), - INT_SIZE); + reinterpret_cast(&data), + sizeof(data)); } void MapBufferBuilder::putIntBuffer( MapBuffer::Key key, const std::vector& value) { - // Wire format: [element count (int32)] + [count * int32]. The count is the - // number of elements, not bytes; see MapBuffer::getIntBuffer. - auto count = static_cast(value.size()); + // The bucket stores [offset][byteLength]; the dynamic section holds the raw + // int32 elements. Element count is recovered as byteLength / sizeof(int32_t). auto payloadSize = static_cast(value.size() * sizeof(int32_t)); - auto offset = static_cast(dynamicData_.size()); - dynamicData_.resize(offset + INT_SIZE + payloadSize, 0); - memcpy(dynamicData_.data() + offset, &count, INT_SIZE); + dynamicData_.resize(offset + payloadSize, 0); if (payloadSize > 0) { - memcpy(dynamicData_.data() + offset + INT_SIZE, value.data(), payloadSize); + memcpy(dynamicData_.data() + offset, value.data(), payloadSize); } + uint64_t data = packOffsetAndLength(offset, payloadSize); storeKeyValue( key, MapBuffer::DataType::IntBuffer, - reinterpret_cast(&offset), - INT_SIZE); + reinterpret_cast(&data), + sizeof(data)); } void MapBufferBuilder::putDoubleBuffer( MapBuffer::Key key, const std::vector& value) { - // Wire format: [element count (int32)] + [count * double]. Doubles are copied - // byte-for-byte; the reader uses memcpy, so the payload needs no special - // alignment for correctness. A consumer that wants a zero-copy typed view on - // the JVM (ByteBuffer::asDoubleBuffer) must ensure 8-byte alignment itself. - auto count = static_cast(value.size()); + // The bucket stores [offset][byteLength]; the dynamic section holds the raw + // double elements. Element count is recovered as byteLength / sizeof(double). + // Doubles are copied byte-for-byte; the reader uses memcpy, so the payload + // needs no special alignment for correctness. A consumer that wants a + // zero-copy typed view on the JVM (ByteBuffer::asDoubleBuffer) must ensure + // 8-byte alignment itself. auto payloadSize = static_cast(value.size() * sizeof(double)); - auto offset = static_cast(dynamicData_.size()); - dynamicData_.resize(offset + INT_SIZE + payloadSize, 0); - memcpy(dynamicData_.data() + offset, &count, INT_SIZE); + dynamicData_.resize(offset + payloadSize, 0); if (payloadSize > 0) { - memcpy(dynamicData_.data() + offset + INT_SIZE, value.data(), payloadSize); + memcpy(dynamicData_.data() + offset, value.data(), payloadSize); } + uint64_t data = packOffsetAndLength(offset, payloadSize); storeKeyValue( key, MapBuffer::DataType::DoubleBuffer, - reinterpret_cast(&offset), - INT_SIZE); + reinterpret_cast(&data), + sizeof(data)); } static inline bool compareBuckets( @@ -220,8 +210,6 @@ MapBuffer MapBufferBuilder::build() { auto headerSize = sizeof(MapBuffer::Header); auto bufferSize = headerSize + bucketSize + dynamicData_.size(); - header_.bufferSize = static_cast(bufferSize); - if (needsSort_) { std::sort(buckets_.begin(), buckets_.end(), compareBuckets); } diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp b/packages/react-native/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp index ce4fd6237c03..05c70d203f59 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp @@ -49,7 +49,7 @@ TEST(MapBufferTest, testSimpleLongMap) { } TEST(MapBufferTest, testMapBufferExtension) { - // 26 = 2 buckets: 2*10 + 6 sizeof(header) + // initialSize is a reserve hint for the number of buckets int initialSize = 26; auto buffer = MapBufferBuilder(initialSize); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 8022b30807e7..ef70f42b27ef 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -3247,7 +3247,6 @@ class facebook::react::MapBuffer { public int32_t getInt(facebook::react::MapBuffer::Key key) const; public int64_t getLong(facebook::react::MapBuffer::Key key) const; public size_t size() const; - public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; @@ -3276,9 +3275,7 @@ struct facebook::react::MapBuffer::Bucket { } struct facebook::react::MapBuffer::Header { - public uint16_t alignment; public uint16_t count; - public uint32_t bufferSize; } class facebook::react::MapBufferBuilder { diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index c241745f95f5..a7b27cd3aa8a 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -3156,7 +3156,6 @@ class facebook::react::MapBuffer { public int32_t getInt(facebook::react::MapBuffer::Key key) const; public int64_t getLong(facebook::react::MapBuffer::Key key) const; public size_t size() const; - public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; @@ -3185,9 +3184,7 @@ struct facebook::react::MapBuffer::Bucket { } struct facebook::react::MapBuffer::Header { - public uint16_t alignment; public uint16_t count; - public uint32_t bufferSize; } class facebook::react::MapBufferBuilder { diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 73beff563c82..37597a3834e0 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -3244,7 +3244,6 @@ class facebook::react::MapBuffer { public int32_t getInt(facebook::react::MapBuffer::Key key) const; public int64_t getLong(facebook::react::MapBuffer::Key key) const; public size_t size() const; - public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; @@ -3273,9 +3272,7 @@ struct facebook::react::MapBuffer::Bucket { } struct facebook::react::MapBuffer::Header { - public uint16_t alignment; public uint16_t count; - public uint32_t bufferSize; } class facebook::react::MapBufferBuilder { diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 0522de96cd20..947870b8c66e 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -5482,7 +5482,6 @@ class facebook::react::MapBuffer { public int32_t getInt(facebook::react::MapBuffer::Key key) const; public int64_t getLong(facebook::react::MapBuffer::Key key) const; public size_t size() const; - public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; @@ -5511,9 +5510,7 @@ struct facebook::react::MapBuffer::Bucket { } struct facebook::react::MapBuffer::Header { - public uint16_t alignment; public uint16_t count; - public uint32_t bufferSize; } class facebook::react::MapBufferBuilder { diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index e961ff1cf987..d60d97b5ac7f 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -5406,7 +5406,6 @@ class facebook::react::MapBuffer { public int32_t getInt(facebook::react::MapBuffer::Key key) const; public int64_t getLong(facebook::react::MapBuffer::Key key) const; public size_t size() const; - public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; @@ -5435,9 +5434,7 @@ struct facebook::react::MapBuffer::Bucket { } struct facebook::react::MapBuffer::Header { - public uint16_t alignment; public uint16_t count; - public uint32_t bufferSize; } class facebook::react::MapBufferBuilder { diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index f5fd13c275cc..325e0e4dd4cc 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -5479,7 +5479,6 @@ class facebook::react::MapBuffer { public int32_t getInt(facebook::react::MapBuffer::Key key) const; public int64_t getLong(facebook::react::MapBuffer::Key key) const; public size_t size() const; - public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; @@ -5508,9 +5507,7 @@ struct facebook::react::MapBuffer::Bucket { } struct facebook::react::MapBuffer::Header { - public uint16_t alignment; public uint16_t count; - public uint32_t bufferSize; } class facebook::react::MapBufferBuilder { diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 5ec53a2f575b..144dbea1f8ef 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -2168,7 +2168,6 @@ class facebook::react::MapBuffer { public int32_t getInt(facebook::react::MapBuffer::Key key) const; public int64_t getLong(facebook::react::MapBuffer::Key key) const; public size_t size() const; - public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; @@ -2197,9 +2196,7 @@ struct facebook::react::MapBuffer::Bucket { } struct facebook::react::MapBuffer::Header { - public uint16_t alignment; public uint16_t count; - public uint32_t bufferSize; } class facebook::react::MapBufferBuilder { diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 2b67e1c5b4f2..c4253f9a715e 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -2104,7 +2104,6 @@ class facebook::react::MapBuffer { public int32_t getInt(facebook::react::MapBuffer::Key key) const; public int64_t getLong(facebook::react::MapBuffer::Key key) const; public size_t size() const; - public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; @@ -2133,9 +2132,7 @@ struct facebook::react::MapBuffer::Bucket { } struct facebook::react::MapBuffer::Header { - public uint16_t alignment; public uint16_t count; - public uint32_t bufferSize; } class facebook::react::MapBufferBuilder { diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 2f7f3c22e513..1cab650e5a96 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -2165,7 +2165,6 @@ class facebook::react::MapBuffer { public int32_t getInt(facebook::react::MapBuffer::Key key) const; public int64_t getLong(facebook::react::MapBuffer::Key key) const; public size_t size() const; - public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; @@ -2194,9 +2193,7 @@ struct facebook::react::MapBuffer::Bucket { } struct facebook::react::MapBuffer::Header { - public uint16_t alignment; public uint16_t count; - public uint32_t bufferSize; } class facebook::react::MapBufferBuilder {