Skip to content

Commit

Permalink
Use mapbuffer for ReactViewGroup
Browse files Browse the repository at this point in the history
Summary:
Provides an alternative way of diffing props on Android side. Instead of passing dynamic JSON values from JS, the new approach diff C++ props and serializes difference into a `MapBuffer`. Current implementation does this only for `RCTView` component to test performance, correctness and memory impact (as MapBuffers are supposed to be more compact and performant).

This way of passing props allows for additional alignment between platforms, as C++ props are now source of truth for the view state, similar to iOS. At the same time, changing serialization method requires reimplementing `ViewManager` codegen (done manually for now) to account for `MapBuffer`s.

Changelog: [Added][Android] - Added an experimental prop serialization path based on MapBuffer

Reviewed By: mdvacca

Differential Revision: D33735245

fbshipit-source-id: 1515b5fe92f6557ae55abe215ce48277c83974a6
  • Loading branch information
Andrei Shikov authored and facebook-github-bot committed Feb 22, 2022
1 parent b1a7793 commit cbcdaae
Showing 14 changed files with 777 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -31,6 +31,10 @@ public static JavaOnlyMap of(Object... keysAndValues) {
return new JavaOnlyMap(keysAndValues);
}

public static JavaOnlyMap from(Map<String, Object> map) {
return new JavaOnlyMap(map);
}

public static JavaOnlyMap deepClone(ReadableMap map) {
JavaOnlyMap res = new JavaOnlyMap();
ReadableMapKeySetIterator iter = map.keySetIterator();
Original file line number Diff line number Diff line change
@@ -301,6 +301,36 @@ public boolean equals(@Nullable Object obj) {
return thisByteBuffer.equals(otherByteBuffer);
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder("{");
for (MapBufferEntry entry : this) {
int key = entry.getKey();
builder.append(key);
builder.append('=');
switch (entry.getType()) {
case BOOL:
builder.append(entry.getBoolean());
break;
case INT:
builder.append(entry.getInt());
break;
case DOUBLE:
builder.append(entry.getDouble());
break;
case STRING:
builder.append(entry.getString());
break;
case MAP:
builder.append(entry.getReadableMapBuffer().toString());
break;
}
builder.append(',');
}
builder.append('}');
return builder.toString();
}

/** @return an {@link Iterator<MapBufferEntry>} for the entries of this MapBuffer. */
@Override
public Iterator<MapBufferEntry> iterator() {
@@ -372,15 +402,15 @@ public boolean getBoolean() {
}

/** @return the String value that is stored in this {@link MapBufferEntry}. */
public @Nullable String getString() {
public String getString() {
assertType(DataType.STRING);
return readStringValue(mBucketOffset + VALUE_OFFSET);
}

/**
* @return the {@link ReadableMapBuffer} value that is stored in this {@link MapBufferEntry}.
*/
public @Nullable ReadableMapBuffer getReadableMapBuffer() {
public ReadableMapBuffer getReadableMapBuffer() {
assertType(DataType.MAP);
return readMapBufferValue(mBucketOffset + VALUE_OFFSET);
}
Original file line number Diff line number Diff line change
@@ -704,7 +704,7 @@ private void preallocateView(
int rootTag,
int reactTag,
final String componentName,
@Nullable ReadableMap props,
@Nullable Object props,
@Nullable Object stateWrapper,
@Nullable Object eventEmitterWrapper,
boolean isLayoutable) {
Original file line number Diff line number Diff line change
@@ -28,8 +28,11 @@ CppMountItem CppMountItem::RemoveMountItem(
int index) {
return {CppMountItem::Type::Remove, parentView, shadowView, {}, index};
}
CppMountItem CppMountItem::UpdatePropsMountItem(ShadowView const &shadowView) {
return {CppMountItem::Type::UpdateProps, {}, {}, shadowView, -1};
CppMountItem CppMountItem::UpdatePropsMountItem(
ShadowView const &oldShadowView,
ShadowView const &newShadowView) {
return {
CppMountItem::Type::UpdateProps, {}, oldShadowView, newShadowView, -1};
}
CppMountItem CppMountItem::UpdateStateMountItem(ShadowView const &shadowView) {
return {CppMountItem::Type::UpdateState, {}, {}, shadowView, -1};
Original file line number Diff line number Diff line change
@@ -35,7 +35,9 @@ struct CppMountItem final {
ShadowView const &shadowView,
int index);

static CppMountItem UpdatePropsMountItem(ShadowView const &shadowView);
static CppMountItem UpdatePropsMountItem(
ShadowView const &oldShadowView,
ShadowView const &newShadowView);

static CppMountItem UpdateStateMountItem(ShadowView const &shadowView);

Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
#include "FabricMountingManager.h"
#include "EventEmitterWrapper.h"
#include "StateWrapperImpl.h"
#include "viewPropConversions.h"

#include <react/jni/ReadableNativeMap.h>
#include <react/renderer/components/scrollview/ScrollViewProps.h>
@@ -177,11 +178,6 @@ static inline void writeIntBufferTypePreamble(
}
}

inline local_ref<ReadableMap::javaobject> castReadableMap(
local_ref<ReadableNativeMap::javaobject> const &nativeMap) {
return make_local(reinterpret_cast<ReadableMap::javaobject>(nativeMap.get()));
}

inline local_ref<ReadableArray::javaobject> castReadableArray(
local_ref<ReadableNativeArray::javaobject> const &nativeArray) {
return make_local(
@@ -222,6 +218,22 @@ static inline float scale(Float value, Float pointScaleFactor) {
return result;
}

local_ref<jobject> FabricMountingManager::getProps(
ShadowView const &oldShadowView,
ShadowView const &newShadowView) {
if (useMapBufferForViewProps_ &&
newShadowView.traits.check(ShadowNodeTraits::Trait::View)) {
auto oldProps = oldShadowView.props != nullptr
? static_cast<ViewProps const &>(*oldShadowView.props)
: ViewProps{};
auto newProps = static_cast<ViewProps const &>(*newShadowView.props);
return ReadableMapBuffer::createWithContents(
viewPropsDiff(oldProps, newProps));
} else {
return ReadableNativeMap::newObjectCxxArgs(newShadowView.props->rawProps);
}
}

void FabricMountingManager::executeMount(
MountingCoordinator::Shared const &mountingCoordinator) {
std::lock_guard<std::recursive_mutex> lock(commitMutex_);
@@ -308,7 +320,8 @@ void FabricMountingManager::executeMount(
if (!isVirtual) {
if (oldChildShadowView.props != newChildShadowView.props) {
cppUpdatePropsMountItems.push_back(
CppMountItem::UpdatePropsMountItem(newChildShadowView));
CppMountItem::UpdatePropsMountItem(
oldChildShadowView, newChildShadowView));
}
if (oldChildShadowView.state != newChildShadowView.state) {
cppUpdateStateMountItems.push_back(
@@ -367,7 +380,7 @@ void FabricMountingManager::executeMount(
shouldRememberAllocatedViews_ ? allocationCheck : revisionCheck;
if (shouldCreateView) {
cppUpdatePropsMountItems.push_back(
CppMountItem::UpdatePropsMountItem(newChildShadowView));
CppMountItem::UpdatePropsMountItem({}, newChildShadowView));
}

// State
@@ -484,7 +497,6 @@ void FabricMountingManager::executeMount(

// Allocate the intBuffer and object array, now that we know exact sizes
// necessary
// TODO: don't allocate at all if size is zero
jintArray intBufferArray = env->NewIntArray(batchMountItemIntsSize);
local_ref<JArrayClass<jobject>> objBufferArray =
JArrayClass<jobject>::newArray(batchMountItemObjectsSize);
@@ -525,10 +537,8 @@ void FabricMountingManager::executeMount(
int isLayoutable =
mountItem.newChildShadowView.layoutMetrics != EmptyLayoutMetrics ? 1
: 0;

local_ref<ReadableMap::javaobject> props =
castReadableMap(ReadableNativeMap::newObjectCxxArgs(
mountItem.newChildShadowView.props->rawProps));
local_ref<JObject> props =
getProps(mountItem.oldChildShadowView, mountItem.newChildShadowView);

// Do not hold onto Java object from C
// We DO want to hold onto C object from Java, since we don't know the
@@ -584,11 +594,8 @@ void FabricMountingManager::executeMount(
temp[0] = mountItem.newChildShadowView.tag;
env->SetIntArrayRegion(intBufferArray, intBufferPosition, 1, temp);
intBufferPosition += 1;

auto newProps = mountItem.newChildShadowView.props->rawProps;
local_ref<ReadableMap::javaobject> newPropsReadableMap =
castReadableMap(ReadableNativeMap::newObjectCxxArgs(newProps));
(*objBufferArray)[objBufferPosition++] = newPropsReadableMap.get();
(*objBufferArray)[objBufferPosition++] =
getProps(mountItem.oldChildShadowView, mountItem.newChildShadowView);
}
}
if (!cppUpdateStateMountItems.empty()) {
@@ -795,15 +802,11 @@ void FabricMountingManager::preallocateShadowView(

bool isLayoutableShadowNode = shadowView.layoutMetrics != EmptyLayoutMetrics;

static auto preallocateView = jni::findClassStatic(UIManagerJavaDescriptor)
->getMethod<void(
jint,
jint,
jstring,
ReadableMap::javaobject,
jobject,
jobject,
jboolean)>("preallocateView");
static auto preallocateView =
jni::findClassStatic(UIManagerJavaDescriptor)
->getMethod<void(
jint, jint, jstring, jobject, jobject, jobject, jboolean)>(
"preallocateView");

// Do not hold onto Java object from C
// We DO want to hold onto C object from Java, since we don't know the
@@ -826,8 +829,8 @@ void FabricMountingManager::preallocateShadowView(
}
}

local_ref<ReadableMap::javaobject> props = castReadableMap(
ReadableNativeMap::newObjectCxxArgs(shadowView.props->rawProps));
local_ref<JObject> props = getProps({}, shadowView);

auto component = getPlatformComponentName(shadowView);

preallocateView(
@@ -942,6 +945,8 @@ FabricMountingManager::FabricMountingManager(
useOverflowInset_ = doesUseOverflowInset();
shouldRememberAllocatedViews_ = config->getBool(
"react_native_new_architecture:remember_views_on_mount_android");
useMapBufferForViewProps_ = config->getBool(
"react_native_new_architecture:use_mapbuffer_for_viewprops");
}

} // namespace react
Original file line number Diff line number Diff line change
@@ -76,6 +76,11 @@ class FabricMountingManager {
bool disableRevisionCheckForPreallocation_{false};
bool useOverflowInset_{false};
bool shouldRememberAllocatedViews_{false};
bool useMapBufferForViewProps_{false};

jni::local_ref<jobject> getProps(
ShadowView const &oldShadowView,
ShadowView const &newShadowView);
};

} // namespace react
Loading

0 comments on commit cbcdaae

Please sign in to comment.