forked from facebook/react-native
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: This diff contains the code from the 35 diff stack - D27210587 This diff implement and integrates Mapbuffer into Fabric text measure system changelog: [internal] internal Reviewed By: JoshuaGross Differential Revision: D27241836 fbshipit-source-id: f40a780df0723f27da440f709a8676cfcca63953
- Loading branch information
1 parent
a15a46c
commit 91b3f5d
Showing
71 changed files
with
2,616 additions
and
202 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/BUCK
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
load("//tools/build_defs/oss:rn_defs.bzl", "FBJNI_TARGET", "react_native_dep", "react_native_target", "rn_android_library") | ||
|
||
rn_android_library( | ||
name = "mapbuffer", | ||
srcs = glob([ | ||
"*.java", | ||
]), | ||
autoglob = False, | ||
is_androidx = True, | ||
labels = ["supermodule:xplat/default/public.react_native.infra"], | ||
provided_deps = [], | ||
required_for_source_only_abi = True, | ||
visibility = [ | ||
"PUBLIC", | ||
], | ||
deps = [ | ||
FBJNI_TARGET, | ||
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"), | ||
react_native_target("java/com/facebook/react/common/mapbuffer/jni:jni"), | ||
react_native_dep("libraries/fbjni:java"), | ||
react_native_dep("third-party/android/androidx:annotation"), | ||
react_native_dep("third-party/java/infer-annotations:infer-annotations"), | ||
react_native_target("java/com/facebook/react/common:common"), | ||
], | ||
exported_deps = [], | ||
) |
348 changes: 348 additions & 0 deletions
348
ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,348 @@ | ||
/* | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
package com.facebook.react.common.mapbuffer; | ||
|
||
import androidx.annotation.Nullable; | ||
import com.facebook.jni.HybridData; | ||
import com.facebook.proguard.annotations.DoNotStrip; | ||
import com.facebook.soloader.SoLoader; | ||
import java.nio.ByteBuffer; | ||
import java.nio.ByteOrder; | ||
import java.util.Iterator; | ||
|
||
/** | ||
* TODO T83483191: add documentation. | ||
* | ||
* <p>NOTE: {@link ReadableMapBuffer} is NOT thread safe. | ||
*/ | ||
public class ReadableMapBuffer implements Iterable<ReadableMapBuffer.MapBufferEntry> { | ||
|
||
static { | ||
SoLoader.loadLibrary("mapbufferjni"); | ||
} | ||
|
||
// Value used to verify if the data is serialized with LittleEndian order. | ||
private static final int ALIGNMENT = 0xFE; | ||
|
||
// 6 bytes = 2 (alignment) + 2 (count) + 2 (size) | ||
private static final int HEADER_SIZE = 6; | ||
|
||
// key size = 2 bytes | ||
private static final int KEY_SIZE = 2; | ||
|
||
// 10 bytes = 2 bytes key + 8 bytes value | ||
private static final int BUCKET_SIZE = 10; | ||
|
||
private static final int INT_SIZE = 4; | ||
|
||
// TODO T83483191: consider moving short to INTs, we are doing extra cast operations just because | ||
// of short java operates with int | ||
private static final int SHORT_SIZE = 2; | ||
|
||
private static final short SHORT_ONE = (short) 1; | ||
|
||
@Nullable ByteBuffer mBuffer = null; | ||
|
||
// Size of the Serialized Data | ||
@SuppressWarnings("unused") | ||
private short mSizeOfData = 0; | ||
|
||
// Amount of items serialized on the ByteBuffer | ||
@SuppressWarnings("unused") | ||
private short mCount = 0; | ||
|
||
private ReadableMapBuffer(HybridData hybridData) { | ||
mHybridData = hybridData; | ||
} | ||
|
||
private ReadableMapBuffer(ByteBuffer buffer) { | ||
mBuffer = buffer; | ||
readHeader(); | ||
} | ||
|
||
private native ByteBuffer importByteBufferAllocateDirect(); | ||
|
||
private native ByteBuffer importByteBuffer(); | ||
|
||
@SuppressWarnings("unused") | ||
@DoNotStrip | ||
@Nullable | ||
private HybridData mHybridData; | ||
|
||
@Override | ||
protected void finalize() throws Throwable { | ||
super.finalize(); | ||
if (mHybridData != null) { | ||
mHybridData.resetNative(); | ||
} | ||
} | ||
|
||
private int getKeyOffsetForBucketIndex(int bucketIndex) { | ||
return HEADER_SIZE + BUCKET_SIZE * bucketIndex; | ||
} | ||
|
||
private int getValueOffsetForKey(short key) { | ||
importByteBufferAndReadHeader(); | ||
int bucketIndex = getBucketIndexForKey(key); | ||
if (bucketIndex == -1) { | ||
// TODO T83483191: Add tests | ||
throw new IllegalArgumentException("Unable to find key: " + key); | ||
} | ||
assertKeyExists(key, bucketIndex); | ||
return getKeyOffsetForBucketIndex(bucketIndex) + KEY_SIZE; | ||
} | ||
|
||
// returns the relative offset of the first byte of dynamic data | ||
private int getOffsetForDynamicData() { | ||
// TODO T83483191: check if there's dynamic data? | ||
return getKeyOffsetForBucketIndex(mCount); | ||
} | ||
|
||
/** | ||
* @param key Key to search for | ||
* @return the "bucket index" for a key or -1 if not found. It uses a binary search algorithm | ||
* (log(n)) | ||
*/ | ||
private int getBucketIndexForKey(short key) { | ||
short lo = 0; | ||
short hi = (short) (getCount() - SHORT_ONE); | ||
while (lo <= hi) { | ||
final short mid = (short) ((lo + hi) >>> SHORT_ONE); | ||
final short midVal = readKey(getKeyOffsetForBucketIndex(mid)); | ||
if (midVal < key) { | ||
lo = (short) (mid + SHORT_ONE); | ||
} else if (midVal > key) { | ||
hi = (short) (mid - SHORT_ONE); | ||
} else { | ||
return mid; | ||
} | ||
} | ||
return -1; | ||
} | ||
|
||
private short readKey(int position) { | ||
return mBuffer.getShort(position); | ||
} | ||
|
||
private double readDoubleValue(int bufferPosition) { | ||
return mBuffer.getDouble(bufferPosition); | ||
} | ||
|
||
private int readIntValue(int bufferPosition) { | ||
return mBuffer.getInt(bufferPosition); | ||
} | ||
|
||
private boolean readBooleanValue(int bufferPosition) { | ||
return readIntValue(bufferPosition) == 1; | ||
} | ||
|
||
private String readStringValue(int bufferPosition) { | ||
int offset = getOffsetForDynamicData() + mBuffer.getInt(bufferPosition); | ||
|
||
int sizeOfString = mBuffer.getInt(offset); | ||
byte[] result = new byte[sizeOfString]; | ||
|
||
int stringOffset = offset + INT_SIZE; | ||
|
||
mBuffer.position(stringOffset); | ||
mBuffer.get(result, 0, sizeOfString); | ||
|
||
return new String(result); | ||
} | ||
|
||
private ReadableMapBuffer readMapBufferValue(int position) { | ||
int offset = getOffsetForDynamicData() + mBuffer.getInt(position); | ||
|
||
int sizeMapBuffer = mBuffer.getShort(offset); | ||
byte[] buffer = new byte[sizeMapBuffer]; | ||
|
||
int bufferOffset = offset + SHORT_SIZE; | ||
|
||
mBuffer.position(bufferOffset); | ||
mBuffer.get(buffer, 0, sizeMapBuffer); | ||
|
||
return new ReadableMapBuffer(ByteBuffer.wrap(buffer)); | ||
} | ||
|
||
private void readHeader() { | ||
// byte order | ||
short storedAlignment = mBuffer.getShort(); | ||
if (storedAlignment != ALIGNMENT) { | ||
mBuffer.order(ByteOrder.LITTLE_ENDIAN); | ||
} | ||
// count | ||
mCount = mBuffer.getShort(); | ||
// size | ||
mSizeOfData = mBuffer.getShort(); | ||
} | ||
|
||
/** | ||
* Binary search of the key inside the mapBuffer (log(n)). | ||
* | ||
* @param key Key to search for | ||
* @return true if and only if the Key received as a parameter is stored in the MapBuffer. | ||
*/ | ||
public boolean hasKey(short key) { | ||
// TODO T83483191: Add tests | ||
return getBucketIndexForKey(key) != -1; | ||
} | ||
|
||
/** @return amount of elements stored into the MapBuffer */ | ||
public short getCount() { | ||
importByteBufferAndReadHeader(); | ||
return mCount; | ||
} | ||
|
||
/** | ||
* @param key {@link int} representing the key | ||
* @return return the int associated to the Key received as a parameter. | ||
*/ | ||
public int getInt(short key) { | ||
// TODO T83483191: extract common code of "get methods" | ||
return readIntValue(getValueOffsetForKey(key)); | ||
} | ||
|
||
/** | ||
* @param key {@link int} representing the key | ||
* @return return the double associated to the Key received as a parameter. | ||
*/ | ||
public double getDouble(short key) { | ||
return readDoubleValue(getValueOffsetForKey(key)); | ||
} | ||
|
||
/** | ||
* @param key {@link int} representing the key | ||
* @return return the int associated to the Key received as a parameter. | ||
*/ | ||
public String getString(short key) { | ||
return readStringValue(getValueOffsetForKey(key)); | ||
} | ||
|
||
public boolean getBoolean(short key) { | ||
return readBooleanValue(getValueOffsetForKey(key)); | ||
} | ||
|
||
/** | ||
* @param key {@link int} representing the key | ||
* @return return the int associated to the Key received as a parameter. | ||
*/ | ||
public ReadableMapBuffer getMapBuffer(short key) { | ||
return readMapBufferValue(getValueOffsetForKey(key)); | ||
} | ||
|
||
/** | ||
* Import ByteBuffer from C++, read the header and move the current cursor at the start of the | ||
* payload. | ||
*/ | ||
private ByteBuffer importByteBufferAndReadHeader() { | ||
if (mBuffer != null) { | ||
return mBuffer; | ||
} | ||
|
||
// mBuffer = importByteBufferAllocateDirect(); | ||
mBuffer = importByteBuffer(); | ||
|
||
readHeader(); | ||
return mBuffer; | ||
} | ||
|
||
private void assertKeyExists(short key, int bucketIndex) { | ||
short storedKey = mBuffer.getShort(getKeyOffsetForBucketIndex(bucketIndex)); | ||
if (storedKey != key) { | ||
throw new IllegalStateException( | ||
"Stored key doesn't match parameter - expected: " + key + " - found: " + storedKey); | ||
} | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
ByteBuffer byteBuffer = importByteBufferAndReadHeader(); | ||
byteBuffer.rewind(); | ||
return byteBuffer.hashCode(); | ||
} | ||
|
||
@Override | ||
public boolean equals(@Nullable Object obj) { | ||
if (!(obj instanceof ReadableMapBuffer)) { | ||
return false; | ||
} | ||
|
||
ReadableMapBuffer other = (ReadableMapBuffer) obj; | ||
ByteBuffer thisByteBuffer = importByteBufferAndReadHeader(); | ||
ByteBuffer otherByteBuffer = other.importByteBufferAndReadHeader(); | ||
if (thisByteBuffer == otherByteBuffer) { | ||
return true; | ||
} | ||
thisByteBuffer.rewind(); | ||
otherByteBuffer.rewind(); | ||
return thisByteBuffer.equals(otherByteBuffer); | ||
} | ||
|
||
/** @return an {@link Iterator<MapBufferEntry>} for the entries of this MapBuffer. */ | ||
@Override | ||
public Iterator<MapBufferEntry> iterator() { | ||
return new Iterator<MapBufferEntry>() { | ||
short current = 0; | ||
short last = (short) (getCount() - SHORT_ONE); | ||
|
||
@Override | ||
public boolean hasNext() { | ||
return current <= last; | ||
} | ||
|
||
@Override | ||
public MapBufferEntry next() { | ||
return new MapBufferEntry(getKeyOffsetForBucketIndex(current++)); | ||
} | ||
}; | ||
} | ||
|
||
/** This class represents an Entry of the {@link ReadableMapBuffer} class. */ | ||
public class MapBufferEntry { | ||
private final int mBucketOffset; | ||
|
||
private MapBufferEntry(int position) { | ||
mBucketOffset = position; | ||
} | ||
|
||
/** @return a {@link short} that represents the key of this {@link MapBufferEntry}. */ | ||
public short getKey() { | ||
return readKey(mBucketOffset); | ||
} | ||
|
||
/** @return the double value that is stored in this {@link MapBufferEntry}. */ | ||
public double getDouble(double defaultValue) { | ||
// TODO T83483191 Extend serialization of MapBuffer to add type checking | ||
// TODO T83483191 Extend serialization of MapBuffer to return null if there's no value | ||
// stored in this MapBufferEntry. | ||
return readDoubleValue(mBucketOffset + KEY_SIZE); | ||
} | ||
|
||
/** @return the int value that is stored in this {@link MapBufferEntry}. */ | ||
public int getInt(int defaultValue) { | ||
return readIntValue(mBucketOffset + KEY_SIZE); | ||
} | ||
|
||
/** @return the boolean value that is stored in this {@link MapBufferEntry}. */ | ||
public boolean getBoolean(boolean defaultValue) { | ||
return readBooleanValue(mBucketOffset + KEY_SIZE); | ||
} | ||
|
||
/** @return the String value that is stored in this {@link MapBufferEntry}. */ | ||
public @Nullable String getString() { | ||
return readStringValue(mBucketOffset + KEY_SIZE); | ||
} | ||
|
||
/** | ||
* @return the {@link ReadableMapBuffer} value that is stored in this {@link MapBufferEntry}. | ||
*/ | ||
public @Nullable ReadableMapBuffer getReadableMapBuffer() { | ||
return readMapBufferValue(mBucketOffset + KEY_SIZE); | ||
} | ||
} | ||
} |
Oops, something went wrong.