From 47280de85e62f33f0b97bc1ed7c83bc6ca0dc7d4 Mon Sep 17 00:00:00 2001 From: Joshua Gross Date: Wed, 15 Jun 2022 23:37:34 -0700 Subject: [PATCH] New Props parsing infrastructure for perf improvements: visitor pattern vs random-map-access pattern (ViewProps, minus YogaLayoutableShadowNode) Summary: Perf numbers for this stack are given in terms of before-stack and after-stack, but the changes are split up for ease of review, and also to show that this migration CAN happen per-component and is 100% opt-in. Most existing C++ components do not /need/ to change at all. # Problem Statement During certain renders (select critical scenarios in specific products), UIManagerBinding::createNode time takes over 50% of JS thread CPU time. This could be higher or lower depending on the specific product and interaction, but overall createNode takes a lot of CPU time. The question is: can we improve this? What is the minimal overhead needed? The vast, vast majority of time is taken up by prop parsing (specifically, converting JS values across the JSI into concrete values on the C++ props structs). Other methods like appendChild, etc, do not take up a significant amount of time; so we conclude that createNode is special, and the JSI itself, or calling into C++, is not the problem. Props parsing is the perf problem. Can we improve it? (Spoiler: yes) # How does props parsing work today? Today, props parsing works as follows: 1. The ConcreteComponentDescriptor will construct a RawPropsParser (one per component /type/, per application: so one for View, one for Image, one for Text... etc) 2. Once per component type per application, ConcreteComponentDescriptor will call "prepare" on the RawPropsParser with an empty, default-constructed ConcreteProps struct. This ConcreteProps struct will cause RawProps.at(field) for every single field. 3. Based on the RawProps::at calls in part 2, RawPropsParser constructs a Map from props string names (width, height, position, etc) to a position within a "value index" array. 4. The above is what happens before any actual props are parsed; and the RawPropsParser is now ready to parse actual Props. 5. When props are actually being parsed from a JSI dictionary, we now have two phases: 1. The RawPropsParser `preparse`s the RawProps, by iterating over the JSI map and filling in two additional data structures: a linear list of RawValues, and a mapping from the ValueIndex array (`keyIndexToValueIndex_`; see step 3) to a value's position in the values list (`value_` in RawPropsParser/RawProps); 2. The ConcretePropT constructor is called, which is the same as in step 2/3, which calls `fieldValue = rawProps.at("fieldName")` repeatedly. 3. For each `at` call, the RawProps will look up a prop name in the Map constructed in step 3, and either return an empty value, or map the key name to the `keyIndexToValueIndex_` array, which maps to a value in `values_`, which is then returned and further parsed. So, a few things that become clear with the current architecture: 1. Complexity is a property of the number of /possible/ props that /can/ be parsed, not what is actually used in product code. This violates the "only pay for what you use" principal. If you have ``, the ViewProps constructor will request ~170 properties, not 1! 2. There's a lot of pre-parsing which isn't free 3. The levels of indirection aren't free, and make cache misses more likely and pipelining is more challenging 4. The levels of indirection also require more memory - minor, but not free # How can we improve it? The goal is to improve props parsing with minimal or zero impact on backwards-compability. We should be able to migrate over components when it's clear there's a performance issue, without requiring everything gets migrated over at once. This both (1) helps us prove out the new architecture, (2) derisks the project, (3) gives us time, internally and externally, to perfect the APIs and gradually migrate everything over before deleting the old infrastructure code entirely. Thus, the goal is to do something that introduces a zero-cost abstraction. This isn't entirely possible in practice, and in fact this method slightly regresses components that do not use the new architecture /at all/, while dramatically improving migrated components and causing the impact of the /old/ architecture to be minimal. # Solution 1. We still keep the existing system in place entirely. 2. After Props are constructed (see ConcreteComponentDescriptor changes) we iterate over all the /values/ set from JS, and call PropsT::setProp. Incidentally, this allows us to easily reuse the same prop for multiple values for "free", which was expensive in the old system. 3. It's worth noting that this makes a Props struct "less immutable" than it was before, and essentially now we have a "builder pattern" for Props. (If we really wanted to, we could still require a single constructor for Props, and then actually use an intermediate PropsBuilder to accumulate values - but I don't think this overhead would be worth for the conceptual "immutability" win, and instead a "Construct/Set/Seal" model works fine, and we still have all the same guarantees of immutability after the parsing phase) # Implementation Details # How to properly construct a single Prop value Minor detail: parsing a single prop is a 3-step process. We imagine two scenarios: (1) Creating a new ShadowNode/Props A from nothing/void, so the previous Props value is just the default constructor. (2) Cloning a ShadowNode A->B and therefore Props A must be copied to Props B before parsing. We will denote this as a clone from A->B, where A may or may not be a previous node or a default-constructed Props node; and imagine in particular that we're setting the "opacity" value for PropsB. We must first (1) copy a value over from the previous version of the Props struct, so B.opacity = A.opacity; (2) Determine if opacity has been set from JS. If so, and there is a value, B.opacity = parse(JSValue). (3) If JS has passed in a value for the prop, BUT the value is `null`, it means that JS is resetting or deleting the prop, so we must set it BACK to the default. In this case we set PropsB.opacity = DefaultConstructedProps.opacity. We must take care in general to ensure that the correct behavior is achieved here, which should help to explain some of the code below. ## String comparisons vs hash comparisons In the previous system, a RawPropsKey is three `const char*` strings, concatenated together repeatedly /at runtime/. In practice, the ONLY reason we have the prefix, name, suffix Key structure is for the templated prop parsing in ViewProps and YogaStyableProps - that's it. It's not used anywhere else. Further, the key {"margin", "Left", "Width"} is identical to the key {"marginLeftWidth", null, null} and we don't do anything fancy with matching prefixes before comparing the whole string, or similar. Before comparison, keys are concatenated into a single string and then we use `strcmp`. The performance of this isn't terrible, but it's nonzero overhead. I think we can do better and it's sufficient to compare hashed string values; even better, we can construct most of these /at compile time/ using constexpr, and using `switch` statements guarantee no hash collisions within a single Props struct (it's possible there's a collision between Props.cpp and ViewProps.cpp, for example, since they're different switch statements). We may eventually want to be more robust against has collisions; I personally don't find the risk to be too great, hash collisions with these keys are exceedingly unlikely (or maybe I just like to live dangerously). Thus, at runtime, each setProp requires computing a single hash for the value coming from JS, and then int comparisons with a bunch of pre-compiled values. If we want to be really paranoid, we could be robust to hash collisions by doing `switch COMPILED_HASH("opacity"): if (strcmp(strFromJs, "opacity") == 0)`. I'm happy to do this if there's enough concern. ## Macros Yuck! I'm using lots of C preprocessor macros. In general I found this way, way easier in reducing code and (essentially) doing codegen for me vs templated code for the switch cases and hashing prop names at compile-time. Maybe there's a better way. Changelog: [Added][Fabric] New API for efficient props construction Reviewed By: javache Differential Revision: D37050215 fbshipit-source-id: d2dcd351a93b9715cfeb5197eb0d6f9194ec6eb9 --- .../components/view/AccessibilityProps.cpp | 306 ++++++----- .../components/view/AccessibilityProps.h | 8 + .../renderer/components/view/ViewProps.cpp | 474 ++++++++++++------ .../renderer/components/view/ViewProps.h | 6 + .../components/view/propsConversions.h | 2 + .../core/ConcreteComponentDescriptor.h | 16 +- .../react/renderer/core/ConcreteShadowNode.h | 5 +- ReactCommon/react/renderer/core/Props.cpp | 31 +- ReactCommon/react/renderer/core/Props.h | 19 + ReactCommon/react/renderer/core/PropsMacros.h | 90 ++++ ReactCommon/react/renderer/core/RawProps.cpp | 7 + ReactCommon/react/renderer/core/RawProps.h | 9 + .../react/renderer/core/RawPropsParser.cpp | 63 +++ .../react/renderer/core/RawPropsParser.h | 9 + .../react/renderer/core/RawPropsPrimitives.h | 2 + ReactCommon/react/renderer/core/RawValue.h | 2 + .../react/renderer/core/propsConversions.h | 29 ++ 17 files changed, 791 insertions(+), 287 deletions(-) create mode 100644 ReactCommon/react/renderer/core/PropsMacros.h diff --git a/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp b/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp index 123b1410b4b..01c76f6385e 100644 --- a/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp +++ b/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp @@ -15,134 +15,196 @@ namespace facebook { namespace react { +bool AccessibilityProps::enablePropIteratorSetter = false; + AccessibilityProps::AccessibilityProps( const PropsParserContext &context, AccessibilityProps const &sourceProps, RawProps const &rawProps) - : accessible(convertRawProp( - context, - rawProps, - "accessible", - sourceProps.accessible, - false)), - accessibilityState(convertRawProp( - context, - rawProps, - "accessibilityState", - sourceProps.accessibilityState, - {})), - accessibilityLabel(convertRawProp( - context, - rawProps, - "accessibilityLabel", - sourceProps.accessibilityLabel, - "")), - accessibilityLabelledBy(convertRawProp( - context, - rawProps, - "accessibilityLabelledBy", - sourceProps.accessibilityLabelledBy, - {})), - accessibilityLiveRegion(convertRawProp( - context, - rawProps, - "accessibilityLiveRegion", - sourceProps.accessibilityLiveRegion, - AccessibilityLiveRegion::None)), - accessibilityHint(convertRawProp( - context, - rawProps, - "accessibilityHint", - sourceProps.accessibilityHint, - "")), - accessibilityLanguage(convertRawProp( - context, - rawProps, - "accessibilityLanguage", - sourceProps.accessibilityLanguage, - "")), - accessibilityValue(convertRawProp( - context, - rawProps, - "accessibilityValue", - sourceProps.accessibilityValue, - {})), - accessibilityActions(convertRawProp( - context, - rawProps, - "accessibilityActions", - sourceProps.accessibilityActions, - {})), - accessibilityViewIsModal(convertRawProp( - context, - rawProps, - "accessibilityViewIsModal", - sourceProps.accessibilityViewIsModal, - false)), - accessibilityElementsHidden(convertRawProp( - context, - rawProps, - "accessibilityElementsHidden", - sourceProps.accessibilityElementsHidden, - false)), - accessibilityIgnoresInvertColors(convertRawProp( - context, - rawProps, - "accessibilityIgnoresInvertColors", - sourceProps.accessibilityIgnoresInvertColors, - false)), - onAccessibilityTap(convertRawProp( - context, - rawProps, - "onAccessibilityTap", - sourceProps.onAccessibilityTap, - {})), - onAccessibilityMagicTap(convertRawProp( - context, - rawProps, - "onAccessibilityMagicTap", - sourceProps.onAccessibilityMagicTap, - {})), - onAccessibilityEscape(convertRawProp( - context, - rawProps, - "onAccessibilityEscape", - sourceProps.onAccessibilityEscape, - {})), - onAccessibilityAction(convertRawProp( - context, - rawProps, - "onAccessibilityAction", - sourceProps.onAccessibilityAction, - {})), - importantForAccessibility(convertRawProp( - context, - rawProps, - "importantForAccessibility", - sourceProps.importantForAccessibility, - ImportantForAccessibility::Auto)), + : accessible( + enablePropIteratorSetter ? sourceProps.accessible + : convertRawProp( + context, + rawProps, + "accessible", + sourceProps.accessible, + false)), + accessibilityState( + enablePropIteratorSetter ? sourceProps.accessibilityState + : convertRawProp( + context, + rawProps, + "accessibilityState", + sourceProps.accessibilityState, + {})), + accessibilityLabel( + enablePropIteratorSetter ? sourceProps.accessibilityLabel + : convertRawProp( + context, + rawProps, + "accessibilityLabel", + sourceProps.accessibilityLabel, + "")), + accessibilityLabelledBy( + enablePropIteratorSetter ? sourceProps.accessibilityLabelledBy + : convertRawProp( + context, + rawProps, + "accessibilityLabelledBy", + sourceProps.accessibilityLabelledBy, + {})), + accessibilityLiveRegion( + enablePropIteratorSetter ? sourceProps.accessibilityLiveRegion + : convertRawProp( + context, + rawProps, + "accessibilityLiveRegion", + sourceProps.accessibilityLiveRegion, + AccessibilityLiveRegion::None)), + accessibilityHint( + enablePropIteratorSetter ? sourceProps.accessibilityHint + : convertRawProp( + context, + rawProps, + "accessibilityHint", + sourceProps.accessibilityHint, + "")), + accessibilityLanguage( + enablePropIteratorSetter ? sourceProps.accessibilityLanguage + : convertRawProp( + context, + rawProps, + "accessibilityLanguage", + sourceProps.accessibilityLanguage, + "")), + accessibilityValue( + enablePropIteratorSetter ? sourceProps.accessibilityValue + : convertRawProp( + context, + rawProps, + "accessibilityValue", + sourceProps.accessibilityValue, + {})), + accessibilityActions( + enablePropIteratorSetter ? sourceProps.accessibilityActions + : convertRawProp( + context, + rawProps, + "accessibilityActions", + sourceProps.accessibilityActions, + {})), + accessibilityViewIsModal( + enablePropIteratorSetter ? sourceProps.accessibilityViewIsModal + : convertRawProp( + context, + rawProps, + "accessibilityViewIsModal", + sourceProps.accessibilityViewIsModal, + false)), + accessibilityElementsHidden( + enablePropIteratorSetter + ? sourceProps.accessibilityElementsHidden + : convertRawProp( + context, + rawProps, + "accessibilityElementsHidden", + sourceProps.accessibilityElementsHidden, + false)), + accessibilityIgnoresInvertColors( + enablePropIteratorSetter + ? sourceProps.accessibilityIgnoresInvertColors + : convertRawProp( + context, + rawProps, + "accessibilityIgnoresInvertColors", + sourceProps.accessibilityIgnoresInvertColors, + false)), + onAccessibilityTap( + enablePropIteratorSetter ? sourceProps.onAccessibilityTap + : convertRawProp( + context, + rawProps, + "onAccessibilityTap", + sourceProps.onAccessibilityTap, + {})), + onAccessibilityMagicTap( + enablePropIteratorSetter ? sourceProps.onAccessibilityMagicTap + : convertRawProp( + context, + rawProps, + "onAccessibilityMagicTap", + sourceProps.onAccessibilityMagicTap, + {})), + onAccessibilityEscape( + enablePropIteratorSetter ? sourceProps.onAccessibilityEscape + : convertRawProp( + context, + rawProps, + "onAccessibilityEscape", + sourceProps.onAccessibilityEscape, + {})), + onAccessibilityAction( + enablePropIteratorSetter ? sourceProps.onAccessibilityAction + : convertRawProp( + context, + rawProps, + "onAccessibilityAction", + sourceProps.onAccessibilityAction, + {})), + importantForAccessibility( + enablePropIteratorSetter ? sourceProps.importantForAccessibility + : convertRawProp( + context, + rawProps, + "importantForAccessibility", + sourceProps.importantForAccessibility, + ImportantForAccessibility::Auto)), testId( - convertRawProp(context, rawProps, "testID", sourceProps.testId, "")) { - // It is a (severe!) perf deoptimization to request props out-of-order. - // Thus, since we need to request the same prop twice here - // (accessibilityRole) we "must" do them subsequently here to prevent - // a regression. It is reasonable to ask if the `at` function can be improved; - // it probably can, but this is a fairly rare edge-case that (1) is easy-ish - // to work around here, and (2) would require very careful work to address - // this case and not regress the more common cases. - const auto *rawPropValue = rawProps.at("accessibilityRole", nullptr, nullptr); - AccessibilityTraits traits; - std::string roleString; - if (rawPropValue == nullptr || !rawPropValue->hasValue()) { - traits = AccessibilityTraits::None; - roleString = ""; - } else { - fromRawValue(context, *rawPropValue, traits); - fromRawValue(context, *rawPropValue, roleString); - } + enablePropIteratorSetter ? sourceProps.testId + : convertRawProp( + context, + rawProps, + "testID", + sourceProps.testId, + "")) {} - accessibilityTraits = traits; - accessibilityRole = roleString; +void AccessibilityProps::setProp( + const PropsParserContext &context, + RawPropsPropNameHash hash, + const char *propName, + RawValue const &value) { + switch (hash) { + RAW_SET_PROP_SWITCH_CASE_BASIC(accessible, false); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityState, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLabel, std::string{""}); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLabelledBy, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityHint, std::string{""}); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLanguage, std::string{""}); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityValue, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityActions, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityViewIsModal, false); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityElementsHidden, false); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityIgnoresInvertColors, false); + RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityTap, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityMagicTap, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityEscape, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityAction, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC( + importantForAccessibility, ImportantForAccessibility::Auto); + RAW_SET_PROP_SWITCH_CASE(testId, "testID", std::string{""}); + case CONSTEXPR_RAW_PROPS_KEY_HASH("accessibilityRole"): { + AccessibilityTraits traits = AccessibilityTraits::None; + std::string roleString = ""; + if (value.hasValue()) { + fromRawValue(context, value, traits); + fromRawValue(context, value, roleString); + } + + accessibilityTraits = traits; + accessibilityRole = roleString; + return; + } + } } #pragma mark - DebugStringConvertible diff --git a/ReactCommon/react/renderer/components/view/AccessibilityProps.h b/ReactCommon/react/renderer/components/view/AccessibilityProps.h index 050b0f1161c..e9e6aa7b3f0 100644 --- a/ReactCommon/react/renderer/components/view/AccessibilityProps.h +++ b/ReactCommon/react/renderer/components/view/AccessibilityProps.h @@ -24,6 +24,14 @@ class AccessibilityProps { AccessibilityProps const &sourceProps, RawProps const &rawProps); + void setProp( + const PropsParserContext &context, + RawPropsPropNameHash hash, + const char *propName, + RawValue const &value); + + static bool enablePropIteratorSetter; + #pragma mark - Props bool accessible{false}; diff --git a/ReactCommon/react/renderer/components/view/ViewProps.cpp b/ReactCommon/react/renderer/components/view/ViewProps.cpp index e09c9a0abcf..f95fa3f0003 100644 --- a/ReactCommon/react/renderer/components/view/ViewProps.cpp +++ b/ReactCommon/react/renderer/components/view/ViewProps.cpp @@ -25,167 +25,331 @@ ViewProps::ViewProps( bool shouldSetRawProps) : YogaStylableProps(context, sourceProps, rawProps, shouldSetRawProps), AccessibilityProps(context, sourceProps, rawProps), - opacity(convertRawProp( - context, - rawProps, - "opacity", - sourceProps.opacity, - (Float)1.0)), - foregroundColor(convertRawProp( - context, - rawProps, - "foregroundColor", - sourceProps.foregroundColor, - {})), - backgroundColor(convertRawProp( - context, - rawProps, - "backgroundColor", - sourceProps.backgroundColor, - {})), - borderRadii(convertRawProp( - context, - rawProps, - "border", - "Radius", - sourceProps.borderRadii, - {})), - borderColors(convertRawProp( - context, - rawProps, - "border", - "Color", - sourceProps.borderColors, - {})), - borderStyles(convertRawProp( - context, - rawProps, - "border", - "Style", - sourceProps.borderStyles, - {})), - shadowColor(convertRawProp( - context, - rawProps, - "shadowColor", - sourceProps.shadowColor, - {})), - shadowOffset(convertRawProp( - context, - rawProps, - "shadowOffset", - sourceProps.shadowOffset, - {})), - shadowOpacity(convertRawProp( - context, - rawProps, - "shadowOpacity", - sourceProps.shadowOpacity, - {})), - shadowRadius(convertRawProp( - context, - rawProps, - "shadowRadius", - sourceProps.shadowRadius, - {})), - transform(convertRawProp( - context, - rawProps, - "transform", - sourceProps.transform, - {})), - backfaceVisibility(convertRawProp( - context, - rawProps, - "backfaceVisibility", - sourceProps.backfaceVisibility, - {})), - shouldRasterize(convertRawProp( - context, - rawProps, - "shouldRasterize", - sourceProps.shouldRasterize, - {})), + opacity( + Props::enablePropIteratorSetter ? sourceProps.opacity + : convertRawProp( + context, + rawProps, + "opacity", + sourceProps.opacity, + (Float)1.0)), + foregroundColor( + Props::enablePropIteratorSetter ? sourceProps.foregroundColor + : convertRawProp( + context, + rawProps, + "foregroundColor", + sourceProps.foregroundColor, + {})), + backgroundColor( + Props::enablePropIteratorSetter ? sourceProps.backgroundColor + : convertRawProp( + context, + rawProps, + "backgroundColor", + sourceProps.backgroundColor, + {})), + borderRadii( + Props::enablePropIteratorSetter ? sourceProps.borderRadii + : convertRawProp( + context, + rawProps, + "border", + "Radius", + sourceProps.borderRadii, + {})), + borderColors( + Props::enablePropIteratorSetter ? sourceProps.borderColors + : convertRawProp( + context, + rawProps, + "border", + "Color", + sourceProps.borderColors, + {})), + borderStyles( + Props::enablePropIteratorSetter ? sourceProps.borderStyles + : convertRawProp( + context, + rawProps, + "border", + "Style", + sourceProps.borderStyles, + {})), + shadowColor( + Props::enablePropIteratorSetter ? sourceProps.shadowColor + : convertRawProp( + context, + rawProps, + "shadowColor", + sourceProps.shadowColor, + {})), + shadowOffset( + Props::enablePropIteratorSetter ? sourceProps.shadowOffset + : convertRawProp( + context, + rawProps, + "shadowOffset", + sourceProps.shadowOffset, + {})), + shadowOpacity( + Props::enablePropIteratorSetter ? sourceProps.shadowOpacity + : convertRawProp( + context, + rawProps, + "shadowOpacity", + sourceProps.shadowOpacity, + {})), + shadowRadius( + Props::enablePropIteratorSetter ? sourceProps.shadowRadius + : convertRawProp( + context, + rawProps, + "shadowRadius", + sourceProps.shadowRadius, + {})), + transform( + Props::enablePropIteratorSetter ? sourceProps.transform + : convertRawProp( + context, + rawProps, + "transform", + sourceProps.transform, + {})), + backfaceVisibility( + Props::enablePropIteratorSetter ? sourceProps.backfaceVisibility + : convertRawProp( + context, + rawProps, + "backfaceVisibility", + sourceProps.backfaceVisibility, + {})), + shouldRasterize( + Props::enablePropIteratorSetter ? sourceProps.shouldRasterize + : convertRawProp( + context, + rawProps, + "shouldRasterize", + sourceProps.shouldRasterize, + {})), zIndex( - convertRawProp(context, rawProps, "zIndex", sourceProps.zIndex, {})), - pointerEvents(convertRawProp( - context, - rawProps, - "pointerEvents", - sourceProps.pointerEvents, - {})), - hitSlop(convertRawProp( - context, - rawProps, - "hitSlop", - sourceProps.hitSlop, - {})), - onLayout(convertRawProp( - context, - rawProps, - "onLayout", - sourceProps.onLayout, - {})), - events(convertRawProp(context, rawProps, sourceProps.events, {})), - collapsable(convertRawProp( - context, - rawProps, - "collapsable", - sourceProps.collapsable, - true)), - removeClippedSubviews(convertRawProp( - context, - rawProps, - "removeClippedSubviews", - sourceProps.removeClippedSubviews, - false)) + Props::enablePropIteratorSetter ? sourceProps.zIndex + : convertRawProp( + context, + rawProps, + "zIndex", + sourceProps.zIndex, + {})), + pointerEvents( + Props::enablePropIteratorSetter ? sourceProps.pointerEvents + : convertRawProp( + context, + rawProps, + "pointerEvents", + sourceProps.pointerEvents, + {})), + hitSlop( + Props::enablePropIteratorSetter ? sourceProps.hitSlop + : convertRawProp( + context, + rawProps, + "hitSlop", + sourceProps.hitSlop, + {})), + onLayout( + Props::enablePropIteratorSetter ? sourceProps.onLayout + : convertRawProp( + context, + rawProps, + "onLayout", + sourceProps.onLayout, + {})), + events( + Props::enablePropIteratorSetter + ? sourceProps.events + : convertRawProp(context, rawProps, sourceProps.events, {})), + collapsable( + Props::enablePropIteratorSetter ? sourceProps.collapsable + : convertRawProp( + context, + rawProps, + "collapsable", + sourceProps.collapsable, + true)), + removeClippedSubviews( + Props::enablePropIteratorSetter + ? sourceProps.removeClippedSubviews + : convertRawProp( + context, + rawProps, + "removeClippedSubviews", + sourceProps.removeClippedSubviews, + false)) #ifdef ANDROID , - elevation(convertRawProp( - context, - rawProps, - "elevation", - sourceProps.elevation, - {})), - nativeBackground(convertRawProp( - context, - rawProps, - "nativeBackgroundAndroid", - sourceProps.nativeBackground, - {})), - nativeForeground(convertRawProp( - context, - rawProps, - "nativeForegroundAndroid", - sourceProps.nativeForeground, - {})), - focusable(convertRawProp( - context, - rawProps, - "focusable", - sourceProps.focusable, - {})), - hasTVPreferredFocus(convertRawProp( - context, - rawProps, - "hasTVPreferredFocus", - sourceProps.hasTVPreferredFocus, - {})), - needsOffscreenAlphaCompositing(convertRawProp( - context, - rawProps, - "needsOffscreenAlphaCompositing", - sourceProps.needsOffscreenAlphaCompositing, - {})), - renderToHardwareTextureAndroid(convertRawProp( - context, - rawProps, - "renderToHardwareTextureAndroid", - sourceProps.renderToHardwareTextureAndroid, - {})) + elevation( + Props::enablePropIteratorSetter ? sourceProps.elevation + : convertRawProp( + context, + rawProps, + "elevation", + sourceProps.elevation, + {})), + nativeBackground( + Props::enablePropIteratorSetter ? sourceProps.nativeBackground + : convertRawProp( + context, + rawProps, + "nativeBackgroundAndroid", + sourceProps.nativeBackground, + {})), + nativeForeground( + Props::enablePropIteratorSetter ? sourceProps.nativeForeground + : convertRawProp( + context, + rawProps, + "nativeForegroundAndroid", + sourceProps.nativeForeground, + {})), + focusable( + Props::enablePropIteratorSetter ? sourceProps.focusable + : convertRawProp( + context, + rawProps, + "focusable", + sourceProps.focusable, + {})), + hasTVPreferredFocus( + Props::enablePropIteratorSetter ? sourceProps.hasTVPreferredFocus + : convertRawProp( + context, + rawProps, + "hasTVPreferredFocus", + sourceProps.hasTVPreferredFocus, + {})), + needsOffscreenAlphaCompositing( + Props::enablePropIteratorSetter + ? sourceProps.needsOffscreenAlphaCompositing + : convertRawProp( + context, + rawProps, + "needsOffscreenAlphaCompositing", + sourceProps.needsOffscreenAlphaCompositing, + {})), + renderToHardwareTextureAndroid( + Props::enablePropIteratorSetter + ? sourceProps.renderToHardwareTextureAndroid + : convertRawProp( + context, + rawProps, + "renderToHardwareTextureAndroid", + sourceProps.renderToHardwareTextureAndroid, + {})) + #endif {}; +#define VIEW_EVENT_CASE(eventType, eventString) \ + case CONSTEXPR_RAW_PROPS_KEY_HASH(eventString): { \ + ViewEvents defaultViewEvents{}; \ + events[eventType] = ({ \ + bool res = defaultViewEvents[eventType]; \ + if (value.hasValue()) { \ + fromRawValue(context, value, res); \ + } \ + res; \ + }); \ + return; \ + } + +void ViewProps::setProp( + const PropsParserContext &context, + RawPropsPropNameHash hash, + const char *propName, + RawValue const &value) { + // All Props structs setProp methods must always, unconditionally, + // call all super::setProp methods, since multiple structs may + // reuse the same values. + YogaStylableProps::setProp(context, hash, propName, value); + AccessibilityProps::setProp(context, hash, propName, value); + + switch (hash) { + RAW_SET_PROP_SWITCH_CASE_BASIC(opacity, (Float)1.0); + RAW_SET_PROP_SWITCH_CASE_BASIC(foregroundColor, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(backgroundColor, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(shadowColor, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(shadowOffset, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(shadowOpacity, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(shadowRadius, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(transform, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(backfaceVisibility, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(shouldRasterize, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(zIndex, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(pointerEvents, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(hitSlop, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(onLayout, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(collapsable, true); + RAW_SET_PROP_SWITCH_CASE_BASIC(removeClippedSubviews, false); + // events field + VIEW_EVENT_CASE(ViewEvents::Offset::PointerEnter, "onPointerEnter"); + VIEW_EVENT_CASE(ViewEvents::Offset::PointerMove, "onPointerMove"); + VIEW_EVENT_CASE(ViewEvents::Offset::PointerLeave, "onPointerLeave"); + VIEW_EVENT_CASE(ViewEvents::Offset::PointerEnter2, "onPointerEnter2"); + VIEW_EVENT_CASE( + ViewEvents::Offset::PointerEnter2Capture, "onPointerEnter2Capture"); + VIEW_EVENT_CASE(ViewEvents::Offset::PointerMove2, "onPointerMove2"); + VIEW_EVENT_CASE( + ViewEvents::Offset::PointerMove2Capture, "onPointerMove2Capture"); + VIEW_EVENT_CASE(ViewEvents::Offset::PointerLeave2, "onPointerLeave2"); + VIEW_EVENT_CASE( + ViewEvents::Offset::PointerLeave2Capture, "onPointerLeave2Capture"); + VIEW_EVENT_CASE(ViewEvents::Offset::PointerOver, "onPointerOver"); + VIEW_EVENT_CASE(ViewEvents::Offset::PointerOut, "onPointerOut"); + VIEW_EVENT_CASE( + ViewEvents::Offset::MoveShouldSetResponder, "onMoveShouldSetResponder"); + VIEW_EVENT_CASE( + ViewEvents::Offset::MoveShouldSetResponderCapture, + "onMoveShouldSetResponderCapture"); + VIEW_EVENT_CASE( + ViewEvents::Offset::StartShouldSetResponder, + "onStartShouldSetResponder"); + VIEW_EVENT_CASE( + ViewEvents::Offset::StartShouldSetResponderCapture, + "onStartShouldSetResponderCapture"); + VIEW_EVENT_CASE(ViewEvents::Offset::ResponderGrant, "onResponderGrant"); + VIEW_EVENT_CASE(ViewEvents::Offset::ResponderReject, "onResponderReject"); + VIEW_EVENT_CASE(ViewEvents::Offset::ResponderStart, "onResponderStart"); + VIEW_EVENT_CASE(ViewEvents::Offset::ResponderEnd, "onResponderEnd"); + VIEW_EVENT_CASE(ViewEvents::Offset::ResponderRelease, "onResponderRelease"); + VIEW_EVENT_CASE(ViewEvents::Offset::ResponderMove, "ResponderMove"); + VIEW_EVENT_CASE( + ViewEvents::Offset::ResponderTerminate, "onResponderTerminate"); + VIEW_EVENT_CASE( + ViewEvents::Offset::ResponderTerminationRequest, + "onResponderTerminationRequest"); + VIEW_EVENT_CASE( + ViewEvents::Offset::ShouldBlockNativeResponder, + "onShouldBlockNativeResponder"); + VIEW_EVENT_CASE(ViewEvents::Offset::TouchStart, "onTouchStart"); + VIEW_EVENT_CASE(ViewEvents::Offset::TouchMove, "onTouchMove"); + VIEW_EVENT_CASE(ViewEvents::Offset::TouchEnd, "onTouchEnd"); + VIEW_EVENT_CASE(ViewEvents::Offset::TouchCancel, "onTouchCancel"); +#ifdef ANDROID + RAW_SET_PROP_SWITCH_CASE_BASIC(elevation, {}); + RAW_SET_PROP_SWITCH_CASE(nativeBackground, "nativeBackgroundAndroid", {}); + RAW_SET_PROP_SWITCH_CASE(nativeForeground, "nativeForegroundAndroid", {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(focusable, false); + RAW_SET_PROP_SWITCH_CASE_BASIC(hasTVPreferredFocus, false); + RAW_SET_PROP_SWITCH_CASE_BASIC(needsOffscreenAlphaCompositing, false); + RAW_SET_PROP_SWITCH_CASE_BASIC(renderToHardwareTextureAndroid, false); +#endif + // BorderRadii + SET_CASCADED_RECTANGLE_CORNERS(borderRadii, "border", "Radius", value); + SET_CASCADED_RECTANGLE_EDGES(borderColors, "border", "Color", value); + SET_CASCADED_RECTANGLE_EDGES(borderStyles, "border", "Style", value); + } +} + #pragma mark - Convenience Methods static BorderRadii ensureNoOverlap(BorderRadii const &radii, Size const &size) { diff --git a/ReactCommon/react/renderer/components/view/ViewProps.h b/ReactCommon/react/renderer/components/view/ViewProps.h index be31a70f787..21a425dc73f 100644 --- a/ReactCommon/react/renderer/components/view/ViewProps.h +++ b/ReactCommon/react/renderer/components/view/ViewProps.h @@ -35,6 +35,12 @@ class ViewProps : public YogaStylableProps, public AccessibilityProps { RawProps const &rawProps, bool shouldSetRawProps = true); + void setProp( + const PropsParserContext &context, + RawPropsPropNameHash hash, + const char *propName, + RawValue const &value); + #pragma mark - Props // Color diff --git a/ReactCommon/react/renderer/components/view/propsConversions.h b/ReactCommon/react/renderer/components/view/propsConversions.h index 413c29936ba..eb56b9b9701 100644 --- a/ReactCommon/react/renderer/components/view/propsConversions.h +++ b/ReactCommon/react/renderer/components/view/propsConversions.h @@ -301,6 +301,7 @@ static inline YGStyle convertRawProp( return yogaStyle; } +// This can be deleted when non-iterator ViewProp parsing is deleted template static inline CascadedRectangleCorners convertRawProp( const PropsParserContext &context, @@ -465,6 +466,7 @@ static inline CascadedRectangleEdges convertRawProp( return result; } +// This can be deleted when non-iterator ViewProp parsing is deleted static inline ViewEvents convertRawProp( const PropsParserContext &context, RawProps const &rawProps, diff --git a/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h b/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h index e2d515543d1..b49db6571e4 100644 --- a/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h +++ b/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h @@ -109,7 +109,21 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { rawProps.parse(rawPropsParser_, context); - return ShadowNodeT::Props(context, rawProps, props); + // Call old-style constructor + auto shadowNodeProps = ShadowNodeT::Props(context, rawProps, props); + + // Use the new-style iterator + // Note that we just check if `Props` has this flag set, no matter + // the type of ShadowNode; it acts as the single global flag. + if (Props::enablePropIteratorSetter) { + rawProps.iterateOverValues([&](RawPropsPropNameHash hash, + const char *propName, + RawValue const &fn) { + shadowNodeProps.get()->setProp(context, hash, propName, fn); + }); + } + + return shadowNodeProps; }; SharedProps interpolateProps( diff --git a/ReactCommon/react/renderer/core/ConcreteShadowNode.h b/ReactCommon/react/renderer/core/ConcreteShadowNode.h index 934de8d84ba..bb5b022b8cb 100644 --- a/ReactCommon/react/renderer/core/ConcreteShadowNode.h +++ b/ReactCommon/react/renderer/core/ConcreteShadowNode.h @@ -47,6 +47,7 @@ class ConcreteShadowNode : public BaseShadowNodeT { using ConcreteProps = PropsT; using SharedConcreteProps = std::shared_ptr; + using UnsharedConcreteProps = std::shared_ptr; using ConcreteEventEmitter = EventEmitterT; using SharedConcreteEventEmitter = std::shared_ptr; using SharedConcreteShadowNode = std::shared_ptr; @@ -69,11 +70,11 @@ class ConcreteShadowNode : public BaseShadowNodeT { return BaseShadowNodeT::BaseTraits(); } - static SharedConcreteProps Props( + static UnsharedConcreteProps Props( const PropsParserContext &context, RawProps const &rawProps, SharedProps const &baseProps = nullptr) { - return std::make_shared( + return std::make_shared( context, baseProps ? static_cast(*baseProps) : PropsT(), rawProps); diff --git a/ReactCommon/react/renderer/core/Props.cpp b/ReactCommon/react/renderer/core/Props.cpp index d1b5ff7e151..ad1c01c5c28 100644 --- a/ReactCommon/react/renderer/core/Props.cpp +++ b/ReactCommon/react/renderer/core/Props.cpp @@ -13,17 +13,21 @@ namespace facebook { namespace react { +bool Props::enablePropIteratorSetter = false; + Props::Props( const PropsParserContext &context, const Props &sourceProps, const RawProps &rawProps, const bool shouldSetRawProps) - : nativeId(convertRawProp( - context, - rawProps, - "nativeID", - sourceProps.nativeId, - {})), + : nativeId( + enablePropIteratorSetter ? sourceProps.nativeId + : convertRawProp( + context, + rawProps, + "nativeID", + sourceProps.nativeId, + {})), revision(sourceProps.revision + 1) #ifdef ANDROID , @@ -31,7 +35,20 @@ Props::Props( shouldSetRawProps ? (folly::dynamic)rawProps : /* null */ folly::dynamic()) #endif - {}; +{ +} + +void Props::setProp( + const PropsParserContext &context, + RawPropsPropNameHash hash, + const char *propName, + RawValue const &value) { + switch (hash) { + case CONSTEXPR_RAW_PROPS_KEY_HASH("nativeID"): + fromRawValue(context, value, nativeId, {}); + return; + } +} } // namespace react } // namespace facebook diff --git a/ReactCommon/react/renderer/core/Props.h b/ReactCommon/react/renderer/core/Props.h index 987c4228a7f..045de81dc17 100644 --- a/ReactCommon/react/renderer/core/Props.h +++ b/ReactCommon/react/renderer/core/Props.h @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -37,6 +38,24 @@ class Props : public virtual Sealable, public virtual DebugStringConvertible { bool shouldSetRawProps = true); virtual ~Props() = default; + static bool enablePropIteratorSetter; + + /** + * Set a prop value via iteration (see enableIterator above). + * If setProp is defined for a particular props struct, it /must/ + * be called every time setProp is called on the hierarchy. + * For example, ViewProps overrides setProp and so ViewProps must + * explicitly call Props::setProp every time ViewProps::setProp is + * called. This is because a single prop from JS can be reused + * multiple times for different values in the hierarchy. For example, if + * ViewProps uses "propX", Props may also use "propX". + */ + void setProp( + const PropsParserContext &context, + RawPropsPropNameHash hash, + const char *propName, + RawValue const &value); + std::string nativeId; /* diff --git a/ReactCommon/react/renderer/core/PropsMacros.h b/ReactCommon/react/renderer/core/PropsMacros.h new file mode 100644 index 00000000000..63625c66b17 --- /dev/null +++ b/ReactCommon/react/renderer/core/PropsMacros.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +// Get hash at compile-time. sizeof(str) - 1 == strlen +// Auto-formatting makes this more ugly than it has to be, but it works. +// The pragma is to ignore warnings about variable shadowing of `len` and/or +// `hash`, which are technically shadowing but it doesn't matter since they're +// constexpr'd. +#define CONSTEXPR_RAW_PROPS_KEY_HASH(s) \ + ({ \ + _Pragma("clang diagnostic push") _Pragma( \ + "clang diagnostic ignored \"-Wshadow\"") constexpr RawPropsPropNameLength \ + len = sizeof(s) - 1; \ + constexpr RawPropsPropNameHash hash = folly::hash::fnv32_buf(s, len); \ + hash; \ + _Pragma("clang diagnostic pop") \ + }) + +#define RAW_PROPS_KEY_HASH(s) folly::hash::fnv32_buf(s, std::strlen(s)) + +// Convenience for building setProps switch statements. +// This injects `fromRawValue` into source; each file that uses +// this macro must import the proper, respective headers required. +#define RAW_SET_PROP_SWITCH_CASE(field, jsPropName, defaultValue) \ + case CONSTEXPR_RAW_PROPS_KEY_HASH(jsPropName): \ + fromRawValue(context, value, field, defaultValue); \ + return; + +// Convenience for building setProps switch statements where the field name is +// the same as the string identifier +#define RAW_SET_PROP_SWITCH_CASE_BASIC(field, defaultValue) \ + RAW_SET_PROP_SWITCH_CASE(field, #field, defaultValue) + +#define CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, field, fieldNameString, value) \ + case CONSTEXPR_RAW_PROPS_KEY_HASH(fieldNameString): { \ + if (!value.hasValue()) { \ + decltype(struct) defaultValues{}; \ + struct.field = defaultValues.field; \ + return; \ + } \ + fromRawValue(context, value, struct.field); \ + return; \ + } + +#define SET_CASCADED_RECTANGLE_CORNERS(struct, prefix, suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, topLeft, prefix "TopLeft" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, topRight, prefix "TopRight" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, bottomLeft, prefix "BottomLeft" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, bottomRight, prefix "BottomRight" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, topStart, prefix "TopStart" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, topEnd, prefix "TopEnd" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, bottomStart, prefix "BottomStart" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, bottomEnd, prefix "BottomEnd" suffix, rawValue) + +#define SET_CASCADED_RECTANGLE_EDGES(struct, prefix, suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, left, prefix "Left" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, right, prefix "Right" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, top, prefix "Top" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, bottom, prefix "Bottom" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, start, prefix "Start" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, end, prefix "End" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, horizontal, prefix "Horizontal" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, vertical, prefix "Vertical" suffix, rawValue) \ + CASE_STATEMENT_SET_FIELD_VALUE_INDEXED( \ + struct, all, prefix "" suffix, rawValue) diff --git a/ReactCommon/react/renderer/core/RawProps.cpp b/ReactCommon/react/renderer/core/RawProps.cpp index 45d63d48e2a..454e2bed304 100644 --- a/ReactCommon/react/renderer/core/RawProps.cpp +++ b/ReactCommon/react/renderer/core/RawProps.cpp @@ -93,5 +93,12 @@ const RawValue *RawProps::at( return parser_->at(*this, RawPropsKey{prefix, name, suffix}); } +void RawProps::iterateOverValues( + std::function< + void(RawPropsPropNameHash, const char *, RawValue const &)> const &fn) + const { + return parser_->iterateOverValues(*this, fn); +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/react/renderer/core/RawProps.h b/ReactCommon/react/renderer/core/RawProps.h index 3b28256f4d4..4e86e289cc1 100644 --- a/ReactCommon/react/renderer/core/RawProps.h +++ b/ReactCommon/react/renderer/core/RawProps.h @@ -97,6 +97,15 @@ class RawProps final { const RawValue *at(char const *name, char const *prefix, char const *suffix) const noexcept; + /** + * Iterator functions: for when you want to iterate over values in-order + * instead of using `at` to access values randomly. + */ + void iterateOverValues( + std::function< + void(RawPropsPropNameHash, const char *, RawValue const &)> const &fn) + const; + private: friend class RawPropsParser; diff --git a/ReactCommon/react/renderer/core/RawPropsParser.cpp b/ReactCommon/react/renderer/core/RawPropsParser.cpp index 359b9f2f35d..882a4a935a5 100644 --- a/ReactCommon/react/renderer/core/RawPropsParser.cpp +++ b/ReactCommon/react/renderer/core/RawPropsParser.cpp @@ -8,6 +8,7 @@ #include "RawPropsParser.h" #include +#include #include #include @@ -99,6 +100,13 @@ void RawPropsParser::preparse(RawProps const &rawProps) const noexcept { // Resetting the cursor, the next increment will give `0`. rawProps.keyIndexCursor_ = static_cast(keyCount - 1); + // If the Props constructor doesn't use ::at at all, we might be + // able to skip this entirely (in those cases, the Props struct probably + // uses setProp instead). + if (keyCount == 0) { + return; + } + switch (rawProps.mode_) { case RawProps::Mode::Empty: return; @@ -123,6 +131,7 @@ void RawPropsParser::preparse(RawProps const &rawProps) const noexcept { auto keyIndex = nameToIndex_.at( name.data(), static_cast(name.size())); + if (keyIndex == kRawPropsValueIndexEmpty) { continue; } @@ -145,6 +154,7 @@ void RawPropsParser::preparse(RawProps const &rawProps) const noexcept { auto keyIndex = nameToIndex_.at( name.data(), static_cast(name.size())); + if (keyIndex == kRawPropsValueIndexEmpty) { continue; } @@ -158,5 +168,58 @@ void RawPropsParser::preparse(RawProps const &rawProps) const noexcept { } } +/** + * To be used by RawProps only. Value iterator functions. + */ +void RawPropsParser::iterateOverValues( + RawProps const &rawProps, + std::function< + void(RawPropsPropNameHash, const char *, RawValue const &)> const + &visit) const { + switch (rawProps.mode_) { + case RawProps::Mode::Empty: + return; + + case RawProps::Mode::JSI: { + auto &runtime = *rawProps.runtime_; + if (!rawProps.value_.isObject()) { + LOG(ERROR) << "Preparse props: rawProps value is not object"; + } + react_native_assert(rawProps.value_.isObject()); + auto object = rawProps.value_.asObject(runtime); + + auto names = object.getPropertyNames(runtime); + auto count = names.size(runtime); + + for (size_t i = 0; i < count; i++) { + auto nameValue = names.getValueAtIndex(runtime, i).getString(runtime); + auto value = object.getProperty(runtime, nameValue); + + auto name = nameValue.utf8(runtime); + + auto nameHash = RAW_PROPS_KEY_HASH(name.c_str()); + auto rawValue = RawValue(jsi::dynamicFromValue(runtime, value)); + + visit(nameHash, name.c_str(), rawValue); + } + + break; + } + + case RawProps::Mode::Dynamic: { + auto const &dynamic = rawProps.dynamic_; + + for (auto const &pair : dynamic.items()) { + auto name = pair.first.getString(); + + auto nameHash = RAW_PROPS_KEY_HASH(name.c_str()); + auto rawValue = RawValue{pair.second}; + visit(nameHash, name.c_str(), rawValue); + } + break; + } + } +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/react/renderer/core/RawPropsParser.h b/ReactCommon/react/renderer/core/RawPropsParser.h index f64dc9a5af2..c112f6247cb 100644 --- a/ReactCommon/react/renderer/core/RawPropsParser.h +++ b/ReactCommon/react/renderer/core/RawPropsParser.h @@ -76,6 +76,15 @@ class RawPropsParser final { RawValue const *at(RawProps const &rawProps, RawPropsKey const &key) const noexcept; + /** + * To be used by RawProps only. Value iterator functions. + */ + void iterateOverValues( + RawProps const &rawProps, + std::function< + void(RawPropsPropNameHash, const char *, RawValue const &)> const &fn) + const; + mutable butter::small_vector keys_{}; mutable RawPropsKeyMap nameToIndex_{}; diff --git a/ReactCommon/react/renderer/core/RawPropsPrimitives.h b/ReactCommon/react/renderer/core/RawPropsPrimitives.h index bc5f2c9a8fd..355496cc27f 100644 --- a/ReactCommon/react/renderer/core/RawPropsPrimitives.h +++ b/ReactCommon/react/renderer/core/RawPropsPrimitives.h @@ -7,6 +7,7 @@ #pragma once +#include #include namespace facebook { @@ -20,6 +21,7 @@ static_assert( sizeof(RawPropsValueIndex) == 1, "RawPropsValueIndex must be one byte size."); using RawPropsPropNameLength = uint8_t; +using RawPropsPropNameHash = uint32_t; /* * The number which is *usually* bigger than a number of explicitly specified diff --git a/ReactCommon/react/renderer/core/RawValue.h b/ReactCommon/react/renderer/core/RawValue.h index b5a3da48314..d541f11a16e 100644 --- a/ReactCommon/react/renderer/core/RawValue.h +++ b/ReactCommon/react/renderer/core/RawValue.h @@ -12,6 +12,8 @@ #include #include +#include + #include namespace facebook { diff --git a/ReactCommon/react/renderer/core/propsConversions.h b/ReactCommon/react/renderer/core/propsConversions.h index 0095a3989f4..adbc0786073 100644 --- a/ReactCommon/react/renderer/core/propsConversions.h +++ b/ReactCommon/react/renderer/core/propsConversions.h @@ -20,6 +20,35 @@ namespace facebook { namespace react { +/** + * Use this only when a prop update has definitely been sent from JS; + * essentially, cases where rawValue is virtually guaranteed to not be a + * nullptr. + */ +template +void fromRawValue( + const PropsParserContext &context, + RawValue const &rawValue, + T &result, + T defaultValue) { + if (!rawValue.hasValue()) { + result = std::move(defaultValue); + return; + } + + fromRawValue(context, rawValue, result); +} + +template +void fromRawValue( + const PropsParserContext &context, + RawValue const &rawValue, + std::optional &result) { + T res{}; + fromRawValue(context, rawValue, res); + result = std::optional(res); +} + template void fromRawValue( const PropsParserContext &context,