Skip to content

Commit d49129e

Browse files
javachefacebook-github-bot
authored andcommitted
Add IntBuffer and DoubleBuffer entry types to MapBuffer (#57359)
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
1 parent 6d3ebea commit d49129e

18 files changed

Lines changed: 314 additions & 2 deletions

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,7 +1665,9 @@ public abstract interface class com/facebook/react/common/mapbuffer/MapBuffer :
16651665
public abstract fun getBoolean (I)Z
16661666
public abstract fun getCount ()I
16671667
public abstract fun getDouble (I)D
1668+
public abstract fun getDoubleBuffer (I)[D
16681669
public abstract fun getInt (I)I
1670+
public abstract fun getIntBuffer (I)[I
16691671
public abstract fun getKeyOffset (I)I
16701672
public abstract fun getLong (I)J
16711673
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 {
16801682
public final class com/facebook/react/common/mapbuffer/MapBuffer$DataType : java/lang/Enum {
16811683
public static final field BOOL Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType;
16821684
public static final field DOUBLE Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType;
1685+
public static final field DOUBLE_BUFFER Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType;
16831686
public static final field INT Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType;
1687+
public static final field INT_BUFFER Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType;
16841688
public static final field LONG Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType;
16851689
public static final field MAP Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType;
16861690
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
16911695

16921696
public abstract interface class com/facebook/react/common/mapbuffer/MapBuffer$Entry {
16931697
public abstract fun getBooleanValue ()Z
1698+
public abstract fun getDoubleBufferValue ()[D
16941699
public abstract fun getDoubleValue ()D
1700+
public abstract fun getIntBufferValue ()[I
16951701
public abstract fun getIntValue ()I
16961702
public abstract fun getKey ()I
16971703
public abstract fun getLongValue ()J
@@ -1708,7 +1714,9 @@ public final class com/facebook/react/common/mapbuffer/ReadableMapBuffer : com/f
17081714
public fun getBoolean (I)Z
17091715
public fun getCount ()I
17101716
public fun getDouble (I)D
1717+
public fun getDoubleBuffer (I)[D
17111718
public fun getInt (I)I
1719+
public fun getIntBuffer (I)[I
17121720
public fun getKeyOffset (I)I
17131721
public fun getLong (I)J
17141722
public synthetic fun getMapBuffer (I)Lcom/facebook/react/common/mapbuffer/MapBuffer;

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public interface MapBuffer : Iterable<MapBuffer.Entry> {
4646
STRING,
4747
MAP,
4848
LONG,
49+
INT_BUFFER,
50+
DOUBLE_BUFFER,
4951
}
5052

5153
/**
@@ -161,6 +163,30 @@ public interface MapBuffer : Iterable<MapBuffer.Entry> {
161163
*/
162164
public fun getMapBufferList(key: Int): List<MapBuffer>
163165

166+
/**
167+
* Provides parsed [IntArray] value if the entry for given key exists with [DataType.INT_BUFFER]
168+
* type. This is a compact representation of a homogeneous list of ints with no per-element
169+
* key/type overhead.
170+
*
171+
* @param key key to lookup the [IntArray] value for
172+
* @return value associated with the requested key
173+
* @throws IllegalArgumentException if the key doesn't exist
174+
* @throws IllegalStateException if the data type doesn't match
175+
*/
176+
public fun getIntBuffer(key: Int): IntArray
177+
178+
/**
179+
* Provides parsed [DoubleArray] value if the entry for given key exists with
180+
* [DataType.DOUBLE_BUFFER] type. This is a compact representation of a homogeneous list of
181+
* doubles with no per-element key/type overhead.
182+
*
183+
* @param key key to lookup the [DoubleArray] value for
184+
* @return value associated with the requested key
185+
* @throws IllegalArgumentException if the key doesn't exist
186+
* @throws IllegalStateException if the data type doesn't match
187+
*/
188+
public fun getDoubleBuffer(key: Int): DoubleArray
189+
164190
/** Iterable entry representing parsed MapBuffer values */
165191
public interface Entry {
166192
/**
@@ -213,5 +239,19 @@ public interface MapBuffer : Iterable<MapBuffer.Entry> {
213239
* @throws IllegalStateException if the data type doesn't match [DataType.MAP]
214240
*/
215241
public val mapBufferValue: MapBuffer
242+
243+
/**
244+
* Entry value represented as [IntArray]
245+
*
246+
* @throws IllegalStateException if the data type doesn't match [DataType.INT_BUFFER]
247+
*/
248+
public val intBufferValue: IntArray
249+
250+
/**
251+
* Entry value represented as [DoubleArray]
252+
*
253+
* @throws IllegalStateException if the data type doesn't match [DataType.DOUBLE_BUFFER]
254+
*/
255+
public val doubleBufferValue: DoubleArray
216256
}
217257
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,20 @@ private constructor(
152152
return readMapBufferList
153153
}
154154

155+
private fun readIntBufferValue(bufferPosition: Int): IntArray {
156+
var offset = offsetForDynamicData + buffer.getInt(bufferPosition)
157+
val count = buffer.getInt(offset)
158+
offset += Int.SIZE_BYTES
159+
return IntArray(count) { i -> buffer.getInt(offset + i * Int.SIZE_BYTES) }
160+
}
161+
162+
private fun readDoubleBufferValue(bufferPosition: Int): DoubleArray {
163+
var offset = offsetForDynamicData + buffer.getInt(bufferPosition)
164+
val count = buffer.getInt(offset)
165+
offset += Int.SIZE_BYTES
166+
return DoubleArray(count) { i -> buffer.getDouble(offset + i * Double.SIZE_BYTES) }
167+
}
168+
155169
private fun getKeyOffsetForBucketIndex(bucketIndex: Int): Int {
156170
return offsetToMapBuffer + HEADER_SIZE + BUCKET_SIZE * bucketIndex
157171
}
@@ -193,6 +207,12 @@ private constructor(
193207
override fun getMapBufferList(key: Int): List<ReadableMapBuffer> =
194208
readMapBufferListValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.MAP))
195209

210+
override fun getIntBuffer(key: Int): IntArray =
211+
readIntBufferValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.INT_BUFFER))
212+
213+
override fun getDoubleBuffer(key: Int): DoubleArray =
214+
readDoubleBufferValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.DOUBLE_BUFFER))
215+
196216
override fun hashCode(): Int {
197217
buffer.rewind()
198218
return buffer.hashCode()
@@ -229,6 +249,8 @@ private constructor(
229249
append('"')
230250
}
231251
MapBuffer.DataType.MAP -> append(entry.mapBufferValue.toString())
252+
MapBuffer.DataType.INT_BUFFER -> append(entry.intBufferValue.contentToString())
253+
MapBuffer.DataType.DOUBLE_BUFFER -> append(entry.doubleBufferValue.contentToString())
232254
}
233255
}
234256
}
@@ -311,6 +333,18 @@ private constructor(
311333
assertType(MapBuffer.DataType.MAP)
312334
return readMapBufferValue(bucketOffset + VALUE_OFFSET)
313335
}
336+
337+
override val intBufferValue: IntArray
338+
get() {
339+
assertType(MapBuffer.DataType.INT_BUFFER)
340+
return readIntBufferValue(bucketOffset + VALUE_OFFSET)
341+
}
342+
343+
override val doubleBufferValue: DoubleArray
344+
get() {
345+
assertType(MapBuffer.DataType.DOUBLE_BUFFER)
346+
return readDoubleBufferValue(bucketOffset + VALUE_OFFSET)
347+
}
314348
}
315349

316350
public companion object {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ internal class WritableMapBuffer : MapBuffer {
126126

127127
override fun getMapBufferList(key: Int): List<MapBuffer> = verifyValue(key, values.get(key))
128128

129+
override fun getIntBuffer(key: Int): IntArray = verifyValue(key, values.get(key))
130+
131+
override fun getDoubleBuffer(key: Int): DoubleArray = verifyValue(key, values.get(key))
132+
129133
/** Generalizes verification of the value types based on the requested type. */
130134
private inline fun <reified T> verifyValue(key: Int, value: Any?): T {
131135
require(value != null) { "Key not found: $key" }
@@ -176,6 +180,12 @@ internal class WritableMapBuffer : MapBuffer {
176180

177181
override val mapBufferValue: MapBuffer
178182
get() = verifyValue(key, values.valueAt(index))
183+
184+
override val intBufferValue: IntArray
185+
get() = verifyValue(key, values.valueAt(index))
186+
187+
override val doubleBufferValue: DoubleArray
188+
get() = verifyValue(key, values.valueAt(index))
179189
}
180190

181191
/*

packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,46 @@ std::vector<MapBuffer> MapBuffer::getMapBufferList(MapBuffer::Key key) const {
163163
return mapBufferList;
164164
}
165165

166+
std::vector<int32_t> MapBuffer::getIntBuffer(MapBuffer::Key key) const {
167+
auto bucketIndex = getKeyBucket(key);
168+
react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer");
169+
if (bucketIndex == -1) {
170+
return {};
171+
}
172+
173+
int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex);
174+
int32_t count = *reinterpret_cast<const int32_t*>(bytes_.data() + offset);
175+
176+
std::vector<int32_t> result(count);
177+
if (count > 0) {
178+
memcpy(
179+
result.data(),
180+
bytes_.data() + offset + sizeof(int32_t),
181+
static_cast<size_t>(count) * sizeof(int32_t));
182+
}
183+
return result;
184+
}
185+
186+
std::vector<double> MapBuffer::getDoubleBuffer(MapBuffer::Key key) const {
187+
auto bucketIndex = getKeyBucket(key);
188+
react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer");
189+
if (bucketIndex == -1) {
190+
return {};
191+
}
192+
193+
int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex);
194+
int32_t count = *reinterpret_cast<const int32_t*>(bytes_.data() + offset);
195+
196+
std::vector<double> result(count);
197+
if (count > 0) {
198+
memcpy(
199+
result.data(),
200+
bytes_.data() + offset + sizeof(int32_t),
201+
static_cast<size_t>(count) * sizeof(double));
202+
}
203+
return result;
204+
}
205+
166206
size_t MapBuffer::size() const {
167207
return bytes_.size();
168208
}

packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,9 @@ class MapBuffer {
9494

9595
/**
9696
* Data types available for serialization in MapBuffer
97-
* Keep in sync with `DataType` enum in `JReadableMapBuffer.java`, which
98-
* expects the same values after reading them through JNI.
97+
* Keep in sync with the `DataType` enum in `MapBuffer.kt`
98+
* (packages/react-native/ReactAndroid/.../common/mapbuffer/MapBuffer.kt),
99+
* which is ordinal-indexed on the JVM side, so the order must match exactly.
99100
*/
100101
enum DataType : uint16_t {
101102
Boolean = 0,
@@ -104,6 +105,13 @@ class MapBuffer {
104105
String = 3,
105106
Map = 4,
106107
Long = 5,
108+
// Homogeneous, length-prefixed arrays stored contiguously in the dynamic
109+
// data section. Unlike Map, they carry no per-element key/type overhead, so
110+
// a batch of N values costs ~N*elementSize bytes plus a single 4-byte count
111+
// prefix instead of N*12-byte buckets. The bucket value is the offset of the
112+
// array within the dynamic data section.
113+
IntBuffer = 6,
114+
DoubleBuffer = 7,
107115
};
108116

109117
explicit MapBuffer(std::vector<uint8_t> data);
@@ -131,6 +139,10 @@ class MapBuffer {
131139

132140
std::vector<MapBuffer> getMapBufferList(MapBuffer::Key key) const;
133141

142+
std::vector<int32_t> getIntBuffer(MapBuffer::Key key) const;
143+
144+
std::vector<double> getDoubleBuffer(MapBuffer::Key key) const;
145+
134146
size_t size() const;
135147

136148
const uint8_t *data() const;

packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,52 @@ void MapBufferBuilder::putMapBufferList(
161161
INT_SIZE);
162162
}
163163

164+
void MapBufferBuilder::putIntBuffer(
165+
MapBuffer::Key key,
166+
const std::vector<int32_t>& value) {
167+
// Wire format: [element count (int32)] + [count * int32]. The count is the
168+
// number of elements, not bytes; see MapBuffer::getIntBuffer.
169+
auto count = static_cast<int32_t>(value.size());
170+
auto payloadSize = static_cast<int32_t>(value.size() * sizeof(int32_t));
171+
172+
auto offset = static_cast<int32_t>(dynamicData_.size());
173+
dynamicData_.resize(offset + INT_SIZE + payloadSize, 0);
174+
memcpy(dynamicData_.data() + offset, &count, INT_SIZE);
175+
if (payloadSize > 0) {
176+
memcpy(dynamicData_.data() + offset + INT_SIZE, value.data(), payloadSize);
177+
}
178+
179+
storeKeyValue(
180+
key,
181+
MapBuffer::DataType::IntBuffer,
182+
reinterpret_cast<const uint8_t*>(&offset),
183+
INT_SIZE);
184+
}
185+
186+
void MapBufferBuilder::putDoubleBuffer(
187+
MapBuffer::Key key,
188+
const std::vector<double>& value) {
189+
// Wire format: [element count (int32)] + [count * double]. Doubles are copied
190+
// byte-for-byte; the reader uses memcpy, so the payload needs no special
191+
// alignment for correctness. A consumer that wants a zero-copy typed view on
192+
// the JVM (ByteBuffer::asDoubleBuffer) must ensure 8-byte alignment itself.
193+
auto count = static_cast<int32_t>(value.size());
194+
auto payloadSize = static_cast<int32_t>(value.size() * sizeof(double));
195+
196+
auto offset = static_cast<int32_t>(dynamicData_.size());
197+
dynamicData_.resize(offset + INT_SIZE + payloadSize, 0);
198+
memcpy(dynamicData_.data() + offset, &count, INT_SIZE);
199+
if (payloadSize > 0) {
200+
memcpy(dynamicData_.data() + offset + INT_SIZE, value.data(), payloadSize);
201+
}
202+
203+
storeKeyValue(
204+
key,
205+
MapBuffer::DataType::DoubleBuffer,
206+
reinterpret_cast<const uint8_t*>(&offset),
207+
INT_SIZE);
208+
}
209+
164210
static inline bool compareBuckets(
165211
const MapBuffer::Bucket& a,
166212
const MapBuffer::Bucket& b) {

packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class MapBufferBuilder {
3939

4040
void putMapBufferList(MapBuffer::Key key, const std::vector<MapBuffer> &mapBufferList);
4141

42+
void putIntBuffer(MapBuffer::Key key, const std::vector<int32_t> &value);
43+
44+
void putDoubleBuffer(MapBuffer::Key key, const std::vector<double> &value);
45+
4246
MapBuffer build();
4347

4448
private:

0 commit comments

Comments
 (0)