Construct RawValue directly from jsi::Value (#48047)

Summary:
### Motivation:

We are looking for a way to access the "raw" jsi value in our fabric view components, so that we can pass complex types like `HostObjects` or `jsi::Object` with `NativeState` attached to our components directly.
Currently the props are converted from `jsi::Object` to `folly::dynamic`, which prevents us from accessing these values directly.

### Changes

This PR is a implementation of the proposal discussed here:

- https://github.com/facebook/react-native/pull/44966#issuecomment-2503915245

These changes extend `RawValue` so that it can be directly constructed from `RawValue(Runtime*, jsi::Value&)` (not just from `folly::dynamic`).

`RawValue`s are created by the `RawPropParser.cpp`. By default it will use the `RawValue(folly::dynamic)` constructor, but we added a feature flag called `useRawPropsJsiValue`, which will sue the JSI constructor.
This enables to test this feature at runtime incrementally.

This change might also be tested on a component basis, by setting a flag in the component descriptor to enable JSI prop parsing for the RawPropParser, as outlined in this PR:

- https://github.com/hannojg/react-native/pull/2

## Changelog:

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->

[General] [Added] - Add `useRawPropsJsiValue` feature flag to represent props internally as `jsi::Value`s instead of converting them to `folly::dynamic`

[General] [Added] - Added `RawValue(Runtime*, jsi::Value&)` constructor to make a `RawValue` from a `jsi::Value`.

Pull Request resolved: https://github.com/facebook/react-native/pull/48047

Test Plan: Enable the `useRawPropsJsiValue` feature flag and test the rn-tester app on android and iOS. Make sure you get no new errors / warnings in the native console.

Reviewed By: NickGerleman

Differential Revision: D66877093

Pulled By: javache

fbshipit-source-id: 7342e5f86d2492ad63a9ccf5508f04e7eb252def
This commit is contained in:
Hanno J. Gödecke
2024-12-11 08:38:12 -08:00
committed by Facebook GitHub Bot
parent 7d0338cb0b
commit 03d2186ace
24 changed files with 468 additions and 47 deletions
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<124d445f029ae3e23652f355cbd28765>>
* @generated SignedSource<<609b55df15ac72da5a3c14dde18a4814>>
*/
/**
@@ -286,6 +286,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun useOptimizedEventBatchingOnAndroid(): Boolean = accessor.useOptimizedEventBatchingOnAndroid()
/**
* Instead of using folly::dynamic as internal representation in RawProps and RawValue, use jsi::Value
*/
@JvmStatic
public fun useRawPropsJsiValue(): Boolean = accessor.useRawPropsJsiValue()
/**
* When enabled, cloning shadow nodes within react native will update the reference held by the current JS fiber tree.
*/
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<08711c50ae5a1ffabcc561bbfe05896e>>
* @generated SignedSource<<0be0090ba864cd4fe0bd2c22e419923c>>
*/
/**
@@ -63,6 +63,7 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useOptimisedViewPreallocationOnAndroidCache: Boolean? = null
private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null
private var useRawPropsJsiValueCache: Boolean? = null
private var useRuntimeShadowNodeReferenceUpdateCache: Boolean? = null
private var useTurboModuleInteropCache: Boolean? = null
private var useTurboModulesCache: Boolean? = null
@@ -454,6 +455,15 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
return cached
}
override fun useRawPropsJsiValue(): Boolean {
var cached = useRawPropsJsiValueCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.useRawPropsJsiValue()
useRawPropsJsiValueCache = cached
}
return cached
}
override fun useRuntimeShadowNodeReferenceUpdate(): Boolean {
var cached = useRuntimeShadowNodeReferenceUpdateCache
if (cached == null) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<1079b38c8b5c0c16e658fa1a4ff0b619>>
* @generated SignedSource<<b92feb26341cd20ea526620ffef11352>>
*/
/**
@@ -114,6 +114,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun useOptimizedEventBatchingOnAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun useRawPropsJsiValue(): Boolean
@DoNotStrip @JvmStatic public external fun useRuntimeShadowNodeReferenceUpdate(): Boolean
@DoNotStrip @JvmStatic public external fun useTurboModuleInterop(): Boolean
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<cb4ab9f84d7333655f4e436e551825b0>>
* @generated SignedSource<<04ac11c085b2880db40d44e431db42f2>>
*/
/**
@@ -109,6 +109,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun useOptimizedEventBatchingOnAndroid(): Boolean = false
override fun useRawPropsJsiValue(): Boolean = false
override fun useRuntimeShadowNodeReferenceUpdate(): Boolean = true
override fun useTurboModuleInterop(): Boolean = false
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<afbce922bb8e83bfd3f57179046c5049>>
* @generated SignedSource<<0f7a852c7c44b4074bf618252fecb2f6>>
*/
/**
@@ -67,6 +67,7 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useOptimisedViewPreallocationOnAndroidCache: Boolean? = null
private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null
private var useRawPropsJsiValueCache: Boolean? = null
private var useRuntimeShadowNodeReferenceUpdateCache: Boolean? = null
private var useTurboModuleInteropCache: Boolean? = null
private var useTurboModulesCache: Boolean? = null
@@ -501,6 +502,16 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun useRawPropsJsiValue(): Boolean {
var cached = useRawPropsJsiValueCache
if (cached == null) {
cached = currentProvider.useRawPropsJsiValue()
accessedFeatureFlags.add("useRawPropsJsiValue")
useRawPropsJsiValueCache = cached
}
return cached
}
override fun useRuntimeShadowNodeReferenceUpdate(): Boolean {
var cached = useRuntimeShadowNodeReferenceUpdateCache
if (cached == null) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<fa21a158fae72045c1b63590ba54d640>>
* @generated SignedSource<<190f00e954f343b076cfe5ef75ee4539>>
*/
/**
@@ -109,6 +109,8 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun useOptimizedEventBatchingOnAndroid(): Boolean
@DoNotStrip public fun useRawPropsJsiValue(): Boolean
@DoNotStrip public fun useRuntimeShadowNodeReferenceUpdate(): Boolean
@DoNotStrip public fun useTurboModuleInterop(): Boolean
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<0f603eaf15eb01cb23a2485d5a525275>>
* @generated SignedSource<<8a9673f2f81917b3466fb955d5641720>>
*/
/**
@@ -297,6 +297,12 @@ class ReactNativeFeatureFlagsProviderHolder
return method(javaProvider_);
}
bool useRawPropsJsiValue() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("useRawPropsJsiValue");
return method(javaProvider_);
}
bool useRuntimeShadowNodeReferenceUpdate() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("useRuntimeShadowNodeReferenceUpdate");
@@ -534,6 +540,11 @@ bool JReactNativeFeatureFlagsCxxInterop::useOptimizedEventBatchingOnAndroid(
return ReactNativeFeatureFlags::useOptimizedEventBatchingOnAndroid();
}
bool JReactNativeFeatureFlagsCxxInterop::useRawPropsJsiValue(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::useRawPropsJsiValue();
}
bool JReactNativeFeatureFlagsCxxInterop::useRuntimeShadowNodeReferenceUpdate(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::useRuntimeShadowNodeReferenceUpdate();
@@ -709,6 +720,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"useOptimizedEventBatchingOnAndroid",
JReactNativeFeatureFlagsCxxInterop::useOptimizedEventBatchingOnAndroid),
makeNativeMethod(
"useRawPropsJsiValue",
JReactNativeFeatureFlagsCxxInterop::useRawPropsJsiValue),
makeNativeMethod(
"useRuntimeShadowNodeReferenceUpdate",
JReactNativeFeatureFlagsCxxInterop::useRuntimeShadowNodeReferenceUpdate),
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<ca05791857a963b64d1b75dccd0d5033>>
* @generated SignedSource<<cf7e9685a1739386706934b8c249ed03>>
*/
/**
@@ -159,6 +159,9 @@ class JReactNativeFeatureFlagsCxxInterop
static bool useOptimizedEventBatchingOnAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool useRawPropsJsiValue(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool useRuntimeShadowNodeReferenceUpdate(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<88103b9e29549d8bc3971f254a3529b6>>
* @generated SignedSource<<debbf54b108cfb6727b6408d99e7d056>>
*/
/**
@@ -198,6 +198,10 @@ bool ReactNativeFeatureFlags::useOptimizedEventBatchingOnAndroid() {
return getAccessor().useOptimizedEventBatchingOnAndroid();
}
bool ReactNativeFeatureFlags::useRawPropsJsiValue() {
return getAccessor().useRawPropsJsiValue();
}
bool ReactNativeFeatureFlags::useRuntimeShadowNodeReferenceUpdate() {
return getAccessor().useRuntimeShadowNodeReferenceUpdate();
}
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<9657126869d2fb6edb9074d71c0989ac>>
* @generated SignedSource<<e4e20a183ab2272062286bd0503530ac>>
*/
/**
@@ -254,6 +254,11 @@ class ReactNativeFeatureFlags {
*/
RN_EXPORT static bool useOptimizedEventBatchingOnAndroid();
/**
* Instead of using folly::dynamic as internal representation in RawProps and RawValue, use jsi::Value
*/
RN_EXPORT static bool useRawPropsJsiValue();
/**
* When enabled, cloning shadow nodes within react native will update the reference held by the current JS fiber tree.
*/
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<01cf526d22b18b9f43f715a84db8422f>>
* @generated SignedSource<<1cd2586e0118ba4a828fd99fdda97187>>
*/
/**
@@ -803,6 +803,24 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() {
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() {
auto flagValue = useRawPropsJsiValue_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(43, "useRawPropsJsiValue");
flagValue = currentProvider_->useRawPropsJsiValue();
useRawPropsJsiValue_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::useRuntimeShadowNodeReferenceUpdate() {
auto flagValue = useRuntimeShadowNodeReferenceUpdate_.load();
@@ -812,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::useRuntimeShadowNodeReferenceUpdate() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(43, "useRuntimeShadowNodeReferenceUpdate");
markFlagAsAccessed(44, "useRuntimeShadowNodeReferenceUpdate");
flagValue = currentProvider_->useRuntimeShadowNodeReferenceUpdate();
useRuntimeShadowNodeReferenceUpdate_ = flagValue;
@@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(44, "useTurboModuleInterop");
markFlagAsAccessed(45, "useTurboModuleInterop");
flagValue = currentProvider_->useTurboModuleInterop();
useTurboModuleInterop_ = flagValue;
@@ -848,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(45, "useTurboModules");
markFlagAsAccessed(46, "useTurboModules");
flagValue = currentProvider_->useTurboModules();
useTurboModules_ = flagValue;
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<485a7e88ec81935a6c45b1ce5de0cbf2>>
* @generated SignedSource<<6be37ce8729f305b92161c7263366c7b>>
*/
/**
@@ -75,6 +75,7 @@ class ReactNativeFeatureFlagsAccessor {
bool useNativeViewConfigsInBridgelessMode();
bool useOptimisedViewPreallocationOnAndroid();
bool useOptimizedEventBatchingOnAndroid();
bool useRawPropsJsiValue();
bool useRuntimeShadowNodeReferenceUpdate();
bool useTurboModuleInterop();
bool useTurboModules();
@@ -89,7 +90,7 @@ class ReactNativeFeatureFlagsAccessor {
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
bool wasOverridden_;
std::array<std::atomic<const char*>, 46> accessedFeatureFlags_;
std::array<std::atomic<const char*>, 47> accessedFeatureFlags_;
std::atomic<std::optional<bool>> commonTestFlag_;
std::atomic<std::optional<bool>> completeReactInstanceCreationOnBgThreadOnAndroid_;
@@ -134,6 +135,7 @@ class ReactNativeFeatureFlagsAccessor {
std::atomic<std::optional<bool>> useNativeViewConfigsInBridgelessMode_;
std::atomic<std::optional<bool>> useOptimisedViewPreallocationOnAndroid_;
std::atomic<std::optional<bool>> useOptimizedEventBatchingOnAndroid_;
std::atomic<std::optional<bool>> useRawPropsJsiValue_;
std::atomic<std::optional<bool>> useRuntimeShadowNodeReferenceUpdate_;
std::atomic<std::optional<bool>> useTurboModuleInterop_;
std::atomic<std::optional<bool>> useTurboModules_;
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<3a027e8944a9d20b04f16f99e8097c65>>
* @generated SignedSource<<93d8b298331642e4dfc7aa0ebf0978c2>>
*/
/**
@@ -199,6 +199,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
return false;
}
bool useRawPropsJsiValue() override {
return false;
}
bool useRuntimeShadowNodeReferenceUpdate() override {
return true;
}
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<5b91c17d725f3a6a140e9fd4a9849707>>
* @generated SignedSource<<d4b9353fbaff57c4cb7431ba2b1c4074>>
*/
/**
@@ -432,6 +432,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef
return ReactNativeFeatureFlagsDefaults::useOptimizedEventBatchingOnAndroid();
}
bool useRawPropsJsiValue() override {
auto value = values_["useRawPropsJsiValue"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useRawPropsJsiValue();
}
bool useRuntimeShadowNodeReferenceUpdate() override {
auto value = values_["useRuntimeShadowNodeReferenceUpdate"];
if (!value.isNull()) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<d4f86766d5755f75b4900ef2db97ac71>>
* @generated SignedSource<<8e43cf80f518df24cf78180c83bdb2cc>>
*/
/**
@@ -68,6 +68,7 @@ class ReactNativeFeatureFlagsProvider {
virtual bool useNativeViewConfigsInBridgelessMode() = 0;
virtual bool useOptimisedViewPreallocationOnAndroid() = 0;
virtual bool useOptimizedEventBatchingOnAndroid() = 0;
virtual bool useRawPropsJsiValue() = 0;
virtual bool useRuntimeShadowNodeReferenceUpdate() = 0;
virtual bool useTurboModuleInterop() = 0;
virtual bool useTurboModules() = 0;
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<ee4c101a64f98180d70278390e5f2ca0>>
* @generated SignedSource<<2bc4bcbe92476dd7de97e804a1e20440>>
*/
/**
@@ -259,6 +259,11 @@ bool NativeReactNativeFeatureFlags::useOptimizedEventBatchingOnAndroid(
return ReactNativeFeatureFlags::useOptimizedEventBatchingOnAndroid();
}
bool NativeReactNativeFeatureFlags::useRawPropsJsiValue(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useRawPropsJsiValue();
}
bool NativeReactNativeFeatureFlags::useRuntimeShadowNodeReferenceUpdate(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useRuntimeShadowNodeReferenceUpdate();
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<7f993bf54033abc75c79e719489b8127>>
* @generated SignedSource<<21e01d73d525a42650c97107fb7ed7ae>>
*/
/**
@@ -123,6 +123,8 @@ class NativeReactNativeFeatureFlags
bool useOptimizedEventBatchingOnAndroid(jsi::Runtime& runtime);
bool useRawPropsJsiValue(jsi::Runtime& runtime);
bool useRuntimeShadowNodeReferenceUpdate(jsi::Runtime& runtime);
bool useTurboModuleInterop(jsi::Runtime& runtime);
@@ -8,6 +8,7 @@
#include "RawPropsParser.h"
#include <react/debug/react_native_assert.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/core/RawProps.h>
#include <glog/logging.h>
@@ -136,8 +137,13 @@ void RawPropsParser::preparse(const RawProps& rawProps) const noexcept {
rawProps.keyIndexToValueIndex_[keyIndex] = valueIndex;
auto value = object.getProperty(runtime, nameValue);
rawProps.values_.push_back(
RawValue(jsi::dynamicFromValue(runtime, value)));
RawValue rawValue;
if (ReactNativeFeatureFlags::useRawPropsJsiValue()) {
rawValue = RawValue(runtime, std::move(value));
} else {
rawValue = RawValue(jsi::dynamicFromValue(runtime, value));
}
rawProps.values_.push_back(std::move(rawValue));
valueIndex++;
}
@@ -8,6 +8,7 @@
#pragma once
#include <unordered_map>
#include <variant>
#include <folly/dynamic.h>
#include <jsi/JSIDynamic.h>
@@ -54,38 +55,52 @@ class RawValue {
/*
* Constructors.
*/
RawValue() noexcept : dynamic_(nullptr) {}
RawValue() noexcept : value_(folly::dynamic(nullptr)) {}
RawValue(RawValue&& other) noexcept : dynamic_(std::move(other.dynamic_)) {}
RawValue(RawValue&& other) noexcept : value_(std::move(other.value_)) {}
RawValue& operator=(RawValue&& other) noexcept {
if (this != &other) {
dynamic_ = std::move(other.dynamic_);
value_ = std::move(other.value_);
}
return *this;
}
explicit RawValue(const folly::dynamic& dynamic) noexcept
: dynamic_(dynamic) {}
explicit RawValue(jsi::Runtime& runtime, const jsi::Value& value) noexcept
: value_(std::make_pair(&runtime, jsi::Value(runtime, value))) {}
explicit RawValue(jsi::Runtime& runtime, jsi::Value&& value) noexcept
: value_(std::make_pair(&runtime, std::move(value))) {}
explicit RawValue(const folly::dynamic& dynamic) noexcept : value_(dynamic) {}
explicit RawValue(folly::dynamic&& dynamic) noexcept
: dynamic_(std::move(dynamic)) {}
: value_(std::move(dynamic)) {}
private:
friend class RawProps;
friend class RawPropsParser;
friend class UIManagerBinding;
/*
* Copy constructor and copy assignment operator would be private and only for
* internal use, but it's needed for user-code that does `auto val =
* (butter::map<std::string, RawValue>)rawVal;`
*/
RawValue(const RawValue& other) noexcept : dynamic_(other.dynamic_) {}
RawValue(const RawValue& other) {
if (std::holds_alternative<folly::dynamic>(other.value_)) {
auto& dynamic = std::get<folly::dynamic>(other.value_);
value_ = dynamic;
} else {
const auto& [runtime, value] = std::get<JsiValuePair>(other.value_);
value_ = std::make_pair(runtime, jsi::Value(*runtime, value));
}
}
RawValue& operator=(const RawValue& other) noexcept {
RawValue& operator=(const RawValue& other) {
if (this != &other) {
dynamic_ = other.dynamic_;
if (std::holds_alternative<folly::dynamic>(other.value_)) {
auto& dynamic = std::get<folly::dynamic>(other.value_);
value_ = dynamic;
} else {
const auto& [runtime, value] = std::get<JsiValuePair>(other.value_);
value_ = std::make_pair(runtime, jsi::Value(*runtime, value));
}
}
return *this;
}
@@ -96,30 +111,54 @@ class RawValue {
*/
template <typename T>
explicit operator T() const {
return castValue(dynamic_, (T*)nullptr);
if (std::holds_alternative<folly::dynamic>(value_)) {
auto& dynamic = std::get<folly::dynamic>(value_);
return castValue(dynamic, (T*)nullptr);
} else {
const auto& [runtime, value] = std::get<JsiValuePair>(value_);
return castValue(runtime, value, (T*)nullptr);
}
}
inline explicit operator folly::dynamic() const noexcept {
return dynamic_;
inline explicit operator folly::dynamic() const {
if (std::holds_alternative<folly::dynamic>(value_)) {
return std::get<folly::dynamic>(value_);
} else {
const auto& [runtime, value] = std::get<JsiValuePair>(value_);
return jsi::dynamicFromValue(*runtime, value);
}
}
/*
* Checks if the stored value has specified type.
*/
template <typename T>
bool hasType() const noexcept {
return checkValueType(dynamic_, (T*)nullptr);
bool hasType() const {
if (std::holds_alternative<folly::dynamic>(value_)) {
auto& dynamic = std::get<folly::dynamic>(value_);
return checkValueType(dynamic, (T*)nullptr);
} else {
const auto& [runtime, value] = std::get<JsiValuePair>(value_);
return checkValueType(runtime, value, (T*)nullptr);
}
}
/*
* Checks if the stored value is *not* `null`.
*/
bool hasValue() const noexcept {
return !dynamic_.isNull();
bool hasValue() const {
if (std::holds_alternative<folly::dynamic>(value_)) {
auto& dynamic = std::get<folly::dynamic>(value_);
return !dynamic.isNull();
} else {
const auto& [runtime, value] = std::get<JsiValuePair>(value_);
return !value.isNull() && !value.isUndefined();
}
}
private:
folly::dynamic dynamic_;
using JsiValuePair = std::pair<jsi::Runtime*, jsi::Value>;
std::variant<folly::dynamic, JsiValuePair> value_;
static bool checkValueType(
const folly::dynamic& /*dynamic*/,
@@ -127,42 +166,104 @@ class RawValue {
return true;
}
static bool checkValueType(
jsi::Runtime* /*runtime*/,
const jsi::Value& /*value*/,
RawValue* /*type*/) noexcept {
return true;
}
static bool checkValueType(
const folly::dynamic& dynamic,
bool* /*type*/) noexcept {
return dynamic.isBool();
}
static bool checkValueType(
jsi::Runtime* /*runtime*/,
const jsi::Value& value,
bool* /*type*/) noexcept {
return value.isBool();
}
static bool checkValueType(
const folly::dynamic& dynamic,
int* /*type*/) noexcept {
return dynamic.isNumber();
}
static bool checkValueType(
jsi::Runtime* /*runtime*/,
const jsi::Value& value,
int* /*type*/) noexcept {
return value.isNumber();
}
static bool checkValueType(
const folly::dynamic& dynamic,
int64_t* /*type*/) noexcept {
return dynamic.isNumber();
}
static bool checkValueType(
jsi::Runtime* /*runtime*/,
const jsi::Value& value,
int64_t* /*type*/) noexcept {
return value.isNumber();
}
static bool checkValueType(
const folly::dynamic& dynamic,
float* /*type*/) noexcept {
return dynamic.isNumber();
}
static bool checkValueType(
jsi::Runtime* /*runtime*/,
const jsi::Value& value,
float* /*type*/) noexcept {
return value.isNumber();
}
static bool checkValueType(
const folly::dynamic& dynamic,
double* /*type*/) noexcept {
return dynamic.isNumber();
}
static bool checkValueType(
jsi::Runtime* /*runtime*/,
const jsi::Value& value,
double* /*type*/) noexcept {
return value.isNumber();
}
static bool checkValueType(
const folly::dynamic& dynamic,
std::string* /*type*/) noexcept {
return dynamic.isString();
}
static bool checkValueType(
jsi::Runtime* /*runtime*/,
const jsi::Value& value,
std::string* /*type*/) noexcept {
return value.isString();
}
static bool checkValueType(
const folly::dynamic& /*dynamic*/,
JsiValuePair* /*type*/) noexcept {
return false;
}
static bool checkValueType(
jsi::Runtime* /*runtime*/,
const jsi::Value& /*value*/,
JsiValuePair* /*type*/) noexcept {
return true;
}
template <typename T>
static bool checkValueType(
const folly::dynamic& dynamic,
@@ -183,6 +284,36 @@ class RawValue {
return true;
}
template <typename T>
static bool checkValueType(
jsi::Runtime* runtime,
const jsi::Value& value,
std::vector<T>* /*type*/) noexcept {
if (!value.isObject()) {
return false;
}
jsi::Object asObject = value.getObject(*runtime);
if (!asObject.isArray(*runtime)) {
return false;
}
jsi::Array array = asObject.getArray(*runtime);
size_t size = array.size(*runtime);
for (size_t i = 0; i < size; i++) {
jsi::Value itemValue = array.getValueAtIndex(*runtime, i);
if (!checkValueType(runtime, itemValue, (T*)nullptr)) {
return false;
}
// Note: We test only one element.
break;
}
return true;
}
template <typename T>
static bool checkValueType(
const folly::dynamic& dynamic,
@@ -204,6 +335,34 @@ class RawValue {
return true;
}
template <typename T>
static bool checkValueType(
jsi::Runtime* runtime,
const jsi::Value& value,
std::unordered_map<std::string, T>* /*type*/) {
if (!value.isObject()) {
return false;
}
jsi::Object asObject = value.getObject(*runtime);
auto propertyNames = asObject.getPropertyNames(*runtime);
size_t size = propertyNames.size(*runtime);
for (size_t i = 0; i < size; i++) {
jsi::String propertyName =
propertyNames.getValueAtIndex(*runtime, i).getString(*runtime);
jsi::Value propertyValue = asObject.getProperty(*runtime, propertyName);
if (!checkValueType(runtime, propertyValue, (T*)nullptr)) {
return false;
}
// Note: We test only one element.
break;
}
return true;
}
// Casts
static RawValue castValue(
const folly::dynamic& dynamic,
@@ -211,32 +370,98 @@ class RawValue {
return RawValue(dynamic);
}
static RawValue castValue(
jsi::Runtime* runtime,
const jsi::Value& value,
RawValue* /*type*/) noexcept {
return RawValue(*runtime, value);
}
static bool castValue(const folly::dynamic& dynamic, bool* /*type*/) {
return dynamic.getBool();
}
static bool castValue(
jsi::Runtime* /*runtime*/,
const jsi::Value& value,
bool* /*type*/) {
return value.asBool();
}
static int castValue(const folly::dynamic& dynamic, int* /*type*/) {
return static_cast<int>(dynamic.asInt());
}
static int
castValue(jsi::Runtime* /*runtime*/, const jsi::Value& value, int* /*type*/) {
double number = value.asNumber();
return static_cast<int>(number);
}
static int64_t castValue(const folly::dynamic& dynamic, int64_t* /*type*/) {
return dynamic.asInt();
}
static int64_t castValue(
jsi::Runtime* /*runtime*/,
const jsi::Value& value,
int64_t* /*type*/) {
double number = value.asNumber();
return static_cast<int64_t>(number);
}
static float castValue(const folly::dynamic& dynamic, float* /*type*/) {
return static_cast<float>(dynamic.asDouble());
}
static float castValue(
jsi::Runtime* /*runtime*/,
const jsi::Value& value,
float* /*type*/) {
double number = value.asNumber();
return static_cast<float>(number);
}
static double castValue(const folly::dynamic& dynamic, double* /*type*/) {
return dynamic.asDouble();
}
static double castValue(
jsi::Runtime* /*runtime*/,
const jsi::Value& value,
double* /*type*/) {
return value.asNumber();
}
static std::string castValue(
const folly::dynamic& dynamic,
std::string* /*type*/) {
return dynamic.getString();
}
static std::string castValue(
jsi::Runtime* runtime,
const jsi::Value& value,
std::string* /*type*/) {
jsi::String stringValue = value.asString(*runtime);
return stringValue.utf8(*runtime);
}
static JsiValuePair castValue(
const folly::dynamic& /*dynamic*/,
JsiValuePair* /*type*/) {
react_native_assert(false);
throw std::runtime_error(
"Cannot cast dynamic to a jsi::Value type. Please use the 'useRawPropsJsiValue' feature flag to enable jsi::Value support for RawValues.");
}
static JsiValuePair castValue(
jsi::Runtime* runtime,
const jsi::Value& value,
JsiValuePair* /*type*/) {
return {runtime, jsi::Value(*runtime, value)};
}
template <typename T>
static std::vector<T> castValue(
const folly::dynamic& dynamic,
@@ -250,6 +475,26 @@ class RawValue {
return result;
}
template <typename T>
static std::vector<T> castValue(
jsi::Runtime* runtime,
const jsi::Value& value,
std::vector<T>* /*type*/) {
react_native_assert(value.isObject());
jsi::Object object = value.getObject(*runtime);
react_native_assert(object.isArray(*runtime));
jsi::Array array = object.getArray(*runtime);
size_t size = array.size(*runtime);
std::vector<T> result;
result.reserve(size);
for (size_t i = 0; i < size; i++) {
jsi::Value itemValue = array.getValueAtIndex(*runtime, i);
T item = castValue(runtime, itemValue, (T*)nullptr);
result.push_back(std::move(item));
}
return result;
}
template <typename T>
static std::vector<std::vector<T>> castValue(
const folly::dynamic& dynamic,
@@ -263,6 +508,27 @@ class RawValue {
return result;
}
template <typename T>
static std::vector<std::vector<T>> castValue(
jsi::Runtime* runtime,
const jsi::Value& value,
std::vector<std::vector<T>>* /*type*/) {
react_native_assert(value.isObject());
jsi::Object object = value.getObject(*runtime);
react_native_assert(object.isArray(*runtime));
jsi::Array array = std::move(object).getArray(*runtime);
size_t size = array.size(*runtime);
std::vector<std::vector<T>> result;
result.reserve(size);
for (size_t i = 0; i < size; i++) {
jsi::Value itemValue = array.getValueAtIndex(*runtime, i);
std::vector<T> item =
castValue(runtime, itemValue, (std::vector<T>*)nullptr);
result.push_back(std::move(item));
}
return result;
}
template <typename T>
static std::unordered_map<std::string, T> castValue(
const folly::dynamic& dynamic,
@@ -275,6 +541,33 @@ class RawValue {
}
return result;
}
template <typename T>
static std::unordered_map<std::string, T> castValue(
jsi::Runtime* runtime,
const jsi::Value& value,
std::unordered_map<std::string, T>* /*type*/) {
react_native_assert(value.isObject());
jsi::Object object = value.getObject(*runtime);
jsi::Array propertyNames = object.getPropertyNames(*runtime);
size_t size = propertyNames.size(*runtime);
std::unordered_map<std::string, T> result;
for (size_t i = 0; i < size; i++) {
jsi::Value propertyNameValue = propertyNames.getValueAtIndex(*runtime, i);
jsi::String propertyName = propertyNameValue.getString(*runtime);
jsi::Value propertyValue = object.getProperty(*runtime, propertyName);
if (propertyValue.isUndefined()) {
// Skip undefined values to mimic JSIDynamic::dynamicFromValue behavior.
// Null values are allowed in the map.
continue;
}
std::string propertyNameString = propertyName.utf8(*runtime);
T property = castValue(runtime, propertyValue, (T*)nullptr);
result.emplace(propertyNameString, std::move(property));
}
return result;
}
};
} // namespace facebook::react
@@ -70,4 +70,10 @@ Pod::Spec.new do |s|
s.dependency "DoubleConversion"
s.dependency "fast_float", "6.1.4"
s.dependency "fmt", "11.0.2"
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
s.dependency "hermes-engine"
else
s.dependency "React-jsc"
end
end
@@ -411,6 +411,15 @@ const definitions: FeatureFlagDefinitions = {
purpose: 'experimentation',
},
},
useRawPropsJsiValue: {
defaultValue: false,
metadata: {
dateAdded: '2024-12-02',
description:
'Instead of using folly::dynamic as internal representation in RawProps and RawValue, use jsi::Value',
purpose: 'experimentation',
},
},
useRuntimeShadowNodeReferenceUpdate: {
defaultValue: true,
metadata: {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<67e1f8c80caedcbf7b28a089a2744b9a>>
* @generated SignedSource<<52cce0be8ae96e530267bf0dbbcfbd2d>>
* @flow strict
*/
@@ -94,6 +94,7 @@ export type ReactNativeFeatureFlags = {
useNativeViewConfigsInBridgelessMode: Getter<boolean>,
useOptimisedViewPreallocationOnAndroid: Getter<boolean>,
useOptimizedEventBatchingOnAndroid: Getter<boolean>,
useRawPropsJsiValue: Getter<boolean>,
useRuntimeShadowNodeReferenceUpdate: Getter<boolean>,
useTurboModuleInterop: Getter<boolean>,
useTurboModules: Getter<boolean>,
@@ -360,6 +361,10 @@ export const useOptimisedViewPreallocationOnAndroid: Getter<boolean> = createNat
* Uses an optimized mechanism for event batching on Android that does not need to wait for a Choreographer frame callback.
*/
export const useOptimizedEventBatchingOnAndroid: Getter<boolean> = createNativeFlagGetter('useOptimizedEventBatchingOnAndroid', false);
/**
* Instead of using folly::dynamic as internal representation in RawProps and RawValue, use jsi::Value
*/
export const useRawPropsJsiValue: Getter<boolean> = createNativeFlagGetter('useRawPropsJsiValue', false);
/**
* When enabled, cloning shadow nodes within react native will update the reference held by the current JS fiber tree.
*/
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<fa9dcd18f2e50de520995ce1f26b111e>>
* @generated SignedSource<<01d174fa990b1b9cccd8bea4694fc1b4>>
* @flow strict
*/
@@ -67,6 +67,7 @@ export interface Spec extends TurboModule {
+useNativeViewConfigsInBridgelessMode?: () => boolean;
+useOptimisedViewPreallocationOnAndroid?: () => boolean;
+useOptimizedEventBatchingOnAndroid?: () => boolean;
+useRawPropsJsiValue?: () => boolean;
+useRuntimeShadowNodeReferenceUpdate?: () => boolean;
+useTurboModuleInterop?: () => boolean;
+useTurboModules?: () => boolean;
+1
View File
@@ -1237,6 +1237,7 @@ PODS:
- fast_float (= 6.1.4)
- fmt (= 11.0.2)
- glog
- hermes-engine
- RCT-Folly/Fabric (= 2024.11.18.00)
- React-jsi
- React-jsiexecutor