From 1d3e48a45174d4a462b87ab30610f35a9ad953e0 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Wed, 19 Aug 2026 14:07:36 +0200 Subject: [PATCH 1/8] feat: Move `Props` into Nitro Modules core via C++ template --- .../src/views/CppHybridViewComponent.ts | 114 +-------- .../views/kotlin/KotlinHybridViewManager.ts | 18 +- .../src/views/swift/SwiftHybridViewManager.ts | 18 +- .../NitroModules.podspec | 1 + .../cpp/views/HybridViewProps.hpp | 226 ++++++++++++++++++ .../android/NitroTest+autolinking.cmake | 2 - .../JHybridRecyclableTestViewStateUpdater.cpp | 12 +- .../c++/views/JHybridTestViewStateUpdater.cpp | 30 +-- .../HybridRecyclableTestViewComponent.mm | 12 +- .../ios/c++/views/HybridTestViewComponent.mm | 30 +-- .../HybridRecyclableTestViewComponent.cpp | 58 ----- .../HybridRecyclableTestViewComponent.hpp | 28 +-- .../c++/views/HybridTestViewComponent.cpp | 91 ------- .../c++/views/HybridTestViewComponent.hpp | 34 +-- 14 files changed, 306 insertions(+), 368 deletions(-) create mode 100644 packages/react-native-nitro-modules/cpp/views/HybridViewProps.hpp delete mode 100644 packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp delete mode 100644 packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp diff --git a/packages/nitrogen/src/views/CppHybridViewComponent.ts b/packages/nitrogen/src/views/CppHybridViewComponent.ts index b8414de82d..306fb45fc4 100644 --- a/packages/nitrogen/src/views/CppHybridViewComponent.ts +++ b/packages/nitrogen/src/views/CppHybridViewComponent.ts @@ -1,12 +1,7 @@ import type { SourceFile } from '../syntax/SourceFile.js' import type { HybridObjectSpec } from '../syntax/HybridObjectSpec.js' import { createIndentation, indent } from '../utils.js' -import { - createFileMetadataString, - escapeCppName, - isFunction, - isNotDuplicate, -} from '../syntax/helpers.js' +import { createFileMetadataString, isNotDuplicate } from '../syntax/helpers.js' import { getHybridObjectName } from '../syntax/getHybridObjectName.js' import { includeHeader } from '../syntax/c++/includeNitroHeader.js' import { createHostComponentJs } from './createHostComponentJs.js' @@ -72,10 +67,9 @@ export function createViewComponentShadowNodeFiles( const namespace = spec.config.getCxxNamespace('c++', 'views') const props = [...spec.properties, getHybridRefProperty(spec)] - const properties = props.map( - (p) => `CachedProp<${p.type.getCode('c++')}> ${escapeCppName(p.name)};` + const propSchemas = props.map( + (p) => `nitro::ViewProp<"${p.name}", ${p.type.getCode('c++')}>` ) - const cases = props.map((p) => `case hashString("${p.name}"): return true;`) const includes = props .flatMap((p) => p.getRequiredImports('c++').map((i) => includeHeader(i, true)) @@ -89,14 +83,8 @@ ${createFileMetadataString(`${component}.hpp`)} #pragma once -#include -#include -#include -#include -#include -#include #include -#include +#include #include #include @@ -109,24 +97,14 @@ namespace ${namespace} { /** * The name of the actual native View. */ - extern const char ${nameVariable}[]; + inline constexpr char ${nameVariable}[] = "${T}"; /** * Props for the "${spec.name}" View. */ - class ${propsClassName} final: public react::ViewProps { - public: - ${propsClassName}() = default; - ${propsClassName}(const react::PropsParserContext& context, - ${createIndentation(propsClassName.length)} const ${propsClassName}& sourceProps, - ${createIndentation(propsClassName.length)} const react::RawProps& rawProps); - - public: - ${indent(properties.join('\n'), ' ')} - - private: - static bool filterObjectKeys(const std::string& propName); - }; + using ${propsClassName} = nitro::HybridViewProps< + "${spec.name}", + ${indent(propSchemas.join(',\n'), ' ')}>; /** * State for the "${spec.name}" View. @@ -148,75 +126,6 @@ namespace ${namespace} { /* The actual view for "${spec.name}" needs to be implemented in platform-specific code. */ -} // namespace ${namespace} -`.trim() - - // .cpp code - const propInitializers = [ - 'react::ViewProps(context, sourceProps, rawProps, filterObjectKeys)', - ] - const propCopyInitializers = ['react::ViewProps()'] - for (const prop of props) { - const name = escapeCppName(prop.name) - const type = prop.type.getCode('c++') - - let valueConversion = `value` - if (isFunction(prop.type)) { - // Due to a React limitation, functions cannot be passed to native directly, - // because RN converts them to booleans (`true`). Nitro knows this and just - // wraps functions as objects - the original function is stored in `f`. - valueConversion = `value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f"))` - } - - propInitializers.push( - ` -${name}([&]() -> CachedProp<${type}> { - try { - const react::RawValue* rawValue = rawProps.at("${prop.name}", nullptr, nullptr); - if (rawValue == nullptr) return sourceProps.${name}; - const auto& [runtime, value] = (std::pair)*rawValue; - return CachedProp<${type}>::fromRawValue(*runtime, ${valueConversion}, sourceProps.${name}); - } catch (const std::exception& exc) { - throw std::runtime_error(std::string("${spec.name}.${prop.name}: ") + exc.what()); - } -}())`.trim() - ) - propCopyInitializers.push(`${name}(other.${name})`) - } - - const ctorIndent = createIndentation(propsClassName.length * 2) - const componentCode = ` -${createFileMetadataString(`${component}.cpp`)} - -#include "${component}.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ${namespace} { - - extern const char ${nameVariable}[] = "${T}"; - - ${propsClassName}::${propsClassName}(const react::PropsParserContext& context, - ${ctorIndent} const ${propsClassName}& sourceProps, - ${ctorIndent} const react::RawProps& rawProps): - ${indent(propInitializers.join(',\n'), ' ')} { } - - bool ${propsClassName}::filterObjectKeys(const std::string& propName) { - switch (hashString(propName)) { - ${indent(cases.join('\n'), ' ')} - default: return false; - } - } - } // namespace ${namespace} `.trim() @@ -228,13 +137,6 @@ namespace ${namespace} { platform: 'shared', subdirectory: ['views'], }, - { - name: `${component}.cpp`, - content: componentCode, - language: 'c++', - platform: 'shared', - subdirectory: ['views'], - }, ] const jsFiles = createHostComponentJs(spec) files.push(...(jsFiles as unknown as SourceFile[])) diff --git a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts index bbece349e0..72e8e442be 100644 --- a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts +++ b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts @@ -4,10 +4,7 @@ import { createViewComponentShadowNodeFiles, getViewComponentNames, } from '../CppHybridViewComponent.js' -import { - createFileMetadataString, - escapeCppName, -} from '../../syntax/helpers.js' +import { createFileMetadataString } from '../../syntax/helpers.js' import { getHybridObjectName } from '../../syntax/getHybridObjectName.js' import { addJNINativeRegistration } from '../../syntax/kotlin/JNINativeRegistrations.js' import { indent } from '../../utils.js' @@ -191,12 +188,11 @@ public: `.trim() const propsUpdaterCalls = spec.properties.map((p) => { - const name = escapeCppName(p.name) const setter = p.getSetterName('other') return ` -if (props->${name}.isDirty) { - hybridView->${setter}(props->${name}.value); - props->${name}.isDirty = false; +if (props->get<"${p.name}">().isDirty) { + hybridView->${setter}(props->get<"${p.name}">().value); + props->get<"${p.name}">().isDirty = false; } `.trim() }) @@ -238,13 +234,13 @@ void J${stateUpdaterName}::updateViewProps(jni::alias_ref /* class ${indent(propsUpdaterCalls.join('\n'), ' ')} // Update hybridRef if it changed - if (props->hybridRef.isDirty) { + if (props->get<"hybridRef">().isDirty) { // hybridRef changed - call it with new this - const auto& maybeFunc = props->hybridRef.value; + const auto& maybeFunc = props->get<"hybridRef">().value; if (maybeFunc.has_value()) { maybeFunc.value()(hybridView); } - props->hybridRef.isDirty = false; + props->get<"hybridRef">().isDirty = false; } } diff --git a/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts b/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts index c3228cd566..b6408fc45d 100644 --- a/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts +++ b/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts @@ -4,10 +4,7 @@ import { createViewComponentShadowNodeFiles, getViewComponentNames, } from '../CppHybridViewComponent.js' -import { - createFileMetadataString, - escapeCppName, -} from '../../syntax/helpers.js' +import { createFileMetadataString } from '../../syntax/helpers.js' import { getUmbrellaHeaderName } from '../../autolinking/ios/createSwiftUmbrellaHeader.js' import { getHybridObjectName } from '../../syntax/getHybridObjectName.js' import { @@ -36,18 +33,17 @@ export function createSwiftHybridViewManager( } const propAssignments = spec.properties.map((p) => { - const name = escapeCppName(p.name) const setter = p.getSetterName('swift') const bridge = new SwiftCxxBridgedType(p.type, false) const parse = bridge.parseFromCppToSwift( - `newViewProps.${name}.value`, + `newViewProps.get<"${p.name}">().value`, 'c++' ) return ` // ${p.jsSignature} -if (newViewProps.${name}.isDirty) { +if (newViewProps.get<"${p.name}">().isDirty) { swiftPart.${setter}(${indent(parse, ' ')}); - newViewProps.${name}.isDirty = false; + newViewProps.get<"${p.name}">().isDirty = false; } `.trim() }) @@ -148,13 +144,13 @@ using namespace ${namespace}::views; swiftPart.afterUpdate(); // 3. Update hybridRef if it changed - if (newViewProps.hybridRef.isDirty) { + if (newViewProps.get<"hybridRef">().isDirty) { // hybridRef changed - call it with new this - const auto& maybeFunc = newViewProps.hybridRef.value; + const auto& maybeFunc = newViewProps.get<"hybridRef">().value; if (maybeFunc.has_value()) { maybeFunc.value()(_hybridView); } - newViewProps.hybridRef.isDirty = false; + newViewProps.get<"hybridRef">().isDirty = false; } // 4. Continue in base class diff --git a/packages/react-native-nitro-modules/NitroModules.podspec b/packages/react-native-nitro-modules/NitroModules.podspec index eb4aa165d5..064a5de1b8 100644 --- a/packages/react-native-nitro-modules/NitroModules.podspec +++ b/packages/react-native-nitro-modules/NitroModules.podspec @@ -51,6 +51,7 @@ Pod::Spec.new do |s| "cpp/utils/NitroDefines.hpp", "cpp/utils/PropNameIDCache.hpp", "cpp/views/CachedProp.hpp", + "cpp/views/HybridViewProps.hpp", "cpp/views/ViewComponentDescriptor.hpp", "cpp/views/ViewPropsHolderState.hpp", # Public iOS-specific headers that will be exposed in modulemap (for Swift) diff --git a/packages/react-native-nitro-modules/cpp/views/HybridViewProps.hpp b/packages/react-native-nitro-modules/cpp/views/HybridViewProps.hpp new file mode 100644 index 0000000000..be9ff064ec --- /dev/null +++ b/packages/react-native-nitro-modules/cpp/views/HybridViewProps.hpp @@ -0,0 +1,226 @@ +// +// HybridViewProps.hpp +// react-native-nitro +// +// Created by Marc Rousavy on 19.08.26. +// + +#pragma once + +#include "CachedProp.hpp" +#include "NitroHash.hpp" +#include "PropNameIDCache.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace margelo::nitro { + +using namespace facebook; + +/** + * A compile-time string that can be used as a non-type template parameter. + */ +template struct FixedString final { +public: + char value[Size]{}; + +public: + constexpr FixedString(const char (&string)[Size]) noexcept { + for (size_t index = 0; index < Size; index++) { + value[index] = string[index]; + } + } + + [[nodiscard]] + constexpr const char *data() const noexcept { + return value; + } + + [[nodiscard]] + constexpr std::string_view stringView() const noexcept { + return std::string_view(value, Size - 1); + } + + template + [[nodiscard]] + constexpr bool + operator==(const FixedString &other) const noexcept { + return stringView() == other.stringView(); + } +}; + +template FixedString(const char (&)[Size]) -> FixedString; + +/** + * Describes a single prop on a `HybridViewProps` type. + */ +template struct ViewProp final { +public: + using Type = TValue; + static constexpr auto name = Name; +}; + +namespace detail { + +template struct IsViewProp : std::false_type {}; + +template +struct IsViewProp> : std::true_type {}; + +template struct IsStdFunction : std::false_type {}; + +template +struct IsStdFunction> : std::true_type {}; + +template +struct IsFunctionViewProp : IsStdFunction> {}; + +template +struct IsFunctionViewProp> : IsFunctionViewProp {}; + +template struct HasUniqueViewPropNames; + +template <> struct HasUniqueViewPropNames<> : std::true_type {}; + +template +struct HasUniqueViewPropNames + : std::bool_constant<((TProp::name != TRest::name) && ...) && + HasUniqueViewPropNames::value> {}; + +template struct FindViewProp; + +template struct FindViewProp { + using Type = void; +}; + +template +struct FindViewProp { + using Type = std::conditional_t::Type>; +}; + +template struct ViewPropStorage { +public: + using Type = typename TProp::Type; + +public: + CachedProp cachedProp; + +public: + ViewPropStorage() = default; + explicit ViewPropStorage(CachedProp &&prop) + : cachedProp(std::move(prop)) {} +}; + +template +CachedProp +parseViewProp(const react::RawProps &rawProps, + const CachedProp &sourceProp) { + try { + // This lookup must happen for every prop, and in the order in which the + // ViewProp descriptors were supplied to HybridViewProps. + const react::RawValue *rawValue = + rawProps.at(TProp::name.data(), nullptr, nullptr); + if (rawValue == nullptr) { + return sourceProp; + } + + const auto &[runtime, value] = + (std::pair)*rawValue; + if constexpr (IsFunctionViewProp::value) { + // React Native cannot transport functions as regular props. Nitrogen + // wraps them as `{ f: function }`, so unwrap `f` before converting and + // before comparing the JSI value with the cached value. + jsi::Value function = value.asObject(*runtime).getProperty( + *runtime, PropNameIDCache::get(*runtime, "f")); + return CachedProp::fromRawValue(*runtime, function, + sourceProp); + } else { + return CachedProp::fromRawValue(*runtime, value, + sourceProp); + } + } catch (const std::exception &exception) { + throw std::runtime_error(std::string(ViewName.data()) + "." + + TProp::name.data() + ": " + exception.what()); + } +} + +} // namespace detail + +/** + * The React Native props for a Nitro Hybrid View. + * + * Each `ViewProp` descriptor declares a prop name and its converted C++ type. + * This class owns RawProps lookup, function unwrapping, conversion, caching, + * and filtering for all declared props. + */ +template +class HybridViewProps final : public react::ViewProps, + private detail::ViewPropStorage... { + static_assert( + (detail::IsViewProp::value && ...), + "HybridViewProps only accepts ViewProp descriptors."); + static_assert( + detail::HasUniqueViewPropNames::value, + "HybridViewProps cannot contain multiple props with the same name."); + +public: + HybridViewProps() = default; + + HybridViewProps(const react::PropsParserContext &context, + const HybridViewProps &sourceProps, + const react::RawProps &rawProps) + : react::ViewProps(context, sourceProps, rawProps, filterObjectKeys), + detail::ViewPropStorage(detail::parseViewProp( + rawProps, + static_cast &>(sourceProps) + .cachedProp))... {} + +public: + template + [[nodiscard]] + decltype(auto) get() noexcept { + using TProp = typename detail::FindViewProp::Type; + if constexpr (std::is_void_v) { + static_assert(!std::is_void_v, + "HybridViewProps does not contain a prop with this name."); + } else { + return (static_cast &>(*this).cachedProp); + } + } + + template + [[nodiscard]] + decltype(auto) get() const noexcept { + using TProp = typename detail::FindViewProp::Type; + if constexpr (std::is_void_v) { + static_assert(!std::is_void_v, + "HybridViewProps does not contain a prop with this name."); + } else { + return (static_cast &>(*this) + .cachedProp); + } + } + +private: + static bool filterObjectKeys(const std::string &propName) { + const uint64_t propHash = hashString(propName); + return (false || ... || + (propHash == hashString(TProps::name.stringView()))); + } +}; + +} // namespace margelo::nitro diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake b/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake index 222b2ef3fe..adbbce2894 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake +++ b/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake @@ -37,11 +37,9 @@ target_sources( ../nitrogen/generated/shared/c++/HybridChildSpec.cpp ../nitrogen/generated/shared/c++/HybridPlatformObjectSpec.cpp ../nitrogen/generated/shared/c++/HybridRecyclableTestViewSpec.cpp - ../nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp ../nitrogen/generated/shared/c++/HybridTestObjectCppSpec.cpp ../nitrogen/generated/shared/c++/HybridTestObjectSwiftKotlinSpec.cpp ../nitrogen/generated/shared/c++/HybridTestViewSpec.cpp - ../nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp # Android-specific Nitrogen C++ sources ../nitrogen/generated/android/c++/JHybridBaseSpec.cpp ../nitrogen/generated/android/c++/JHybridChildSpec.cpp diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridRecyclableTestViewStateUpdater.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridRecyclableTestViewStateUpdater.cpp index cb4b7c3a8e..5c837e1096 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridRecyclableTestViewStateUpdater.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridRecyclableTestViewStateUpdater.cpp @@ -37,19 +37,19 @@ void JHybridRecyclableTestViewStateUpdater::updateViewProps(jni::alias_refisBlue.isDirty) { - hybridView->setIsBlue(props->isBlue.value); - props->isBlue.isDirty = false; + if (props->get<"isBlue">().isDirty) { + hybridView->setIsBlue(props->get<"isBlue">().value); + props->get<"isBlue">().isDirty = false; } // Update hybridRef if it changed - if (props->hybridRef.isDirty) { + if (props->get<"hybridRef">().isDirty) { // hybridRef changed - call it with new this - const auto& maybeFunc = props->hybridRef.value; + const auto& maybeFunc = props->get<"hybridRef">().value; if (maybeFunc.has_value()) { maybeFunc.value()(hybridView); } - props->hybridRef.isDirty = false; + props->get<"hybridRef">().isDirty = false; } } diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp index fb65848189..298a65e0a1 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp @@ -37,31 +37,31 @@ void JHybridTestViewStateUpdater::updateViewProps(jni::alias_ref /* } // Update all props if they are dirty - if (props->isBlue.isDirty) { - hybridView->setIsBlue(props->isBlue.value); - props->isBlue.isDirty = false; + if (props->get<"isBlue">().isDirty) { + hybridView->setIsBlue(props->get<"isBlue">().value); + props->get<"isBlue">().isDirty = false; } - if (props->hasBeenCalled.isDirty) { - hybridView->setHasBeenCalled(props->hasBeenCalled.value); - props->hasBeenCalled.isDirty = false; + if (props->get<"hasBeenCalled">().isDirty) { + hybridView->setHasBeenCalled(props->get<"hasBeenCalled">().value); + props->get<"hasBeenCalled">().isDirty = false; } - if (props->colorScheme.isDirty) { - hybridView->setColorScheme(props->colorScheme.value); - props->colorScheme.isDirty = false; + if (props->get<"colorScheme">().isDirty) { + hybridView->setColorScheme(props->get<"colorScheme">().value); + props->get<"colorScheme">().isDirty = false; } - if (props->someCallback.isDirty) { - hybridView->setSomeCallback(props->someCallback.value); - props->someCallback.isDirty = false; + if (props->get<"someCallback">().isDirty) { + hybridView->setSomeCallback(props->get<"someCallback">().value); + props->get<"someCallback">().isDirty = false; } // Update hybridRef if it changed - if (props->hybridRef.isDirty) { + if (props->get<"hybridRef">().isDirty) { // hybridRef changed - call it with new this - const auto& maybeFunc = props->hybridRef.value; + const auto& maybeFunc = props->get<"hybridRef">().value; if (maybeFunc.has_value()) { maybeFunc.value()(hybridView); } - props->hybridRef.isDirty = false; + props->get<"hybridRef">().isDirty = false; } } diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm index fd555bceb0..29f7b7732a 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm @@ -94,21 +94,21 @@ - (void) updateProps:(const std::shared_ptr&)props swiftPart.beforeUpdate(); // isBlue: boolean - if (newViewProps.isBlue.isDirty) { - swiftPart.setIsBlue(newViewProps.isBlue.value); - newViewProps.isBlue.isDirty = false; + if (newViewProps.get<"isBlue">().isDirty) { + swiftPart.setIsBlue(newViewProps.get<"isBlue">().value); + newViewProps.get<"isBlue">().isDirty = false; } swiftPart.afterUpdate(); // 3. Update hybridRef if it changed - if (newViewProps.hybridRef.isDirty) { + if (newViewProps.get<"hybridRef">().isDirty) { // hybridRef changed - call it with new this - const auto& maybeFunc = newViewProps.hybridRef.value; + const auto& maybeFunc = newViewProps.get<"hybridRef">().value; if (maybeFunc.has_value()) { maybeFunc.value()(_hybridView); } - newViewProps.hybridRef.isDirty = false; + newViewProps.get<"hybridRef">().isDirty = false; } // 4. Continue in base class diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm index 51d9e9d932..48ef5460a6 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm @@ -94,36 +94,36 @@ - (void) updateProps:(const std::shared_ptr&)props swiftPart.beforeUpdate(); // isBlue: boolean - if (newViewProps.isBlue.isDirty) { - swiftPart.setIsBlue(newViewProps.isBlue.value); - newViewProps.isBlue.isDirty = false; + if (newViewProps.get<"isBlue">().isDirty) { + swiftPart.setIsBlue(newViewProps.get<"isBlue">().value); + newViewProps.get<"isBlue">().isDirty = false; } // hasBeenCalled: boolean - if (newViewProps.hasBeenCalled.isDirty) { - swiftPart.setHasBeenCalled(newViewProps.hasBeenCalled.value); - newViewProps.hasBeenCalled.isDirty = false; + if (newViewProps.get<"hasBeenCalled">().isDirty) { + swiftPart.setHasBeenCalled(newViewProps.get<"hasBeenCalled">().value); + newViewProps.get<"hasBeenCalled">().isDirty = false; } // colorScheme: enum - if (newViewProps.colorScheme.isDirty) { - swiftPart.setColorScheme(static_cast(newViewProps.colorScheme.value)); - newViewProps.colorScheme.isDirty = false; + if (newViewProps.get<"colorScheme">().isDirty) { + swiftPart.setColorScheme(static_cast(newViewProps.get<"colorScheme">().value)); + newViewProps.get<"colorScheme">().isDirty = false; } // someCallback: function - if (newViewProps.someCallback.isDirty) { - swiftPart.setSomeCallback(newViewProps.someCallback.value); - newViewProps.someCallback.isDirty = false; + if (newViewProps.get<"someCallback">().isDirty) { + swiftPart.setSomeCallback(newViewProps.get<"someCallback">().value); + newViewProps.get<"someCallback">().isDirty = false; } swiftPart.afterUpdate(); // 3. Update hybridRef if it changed - if (newViewProps.hybridRef.isDirty) { + if (newViewProps.get<"hybridRef">().isDirty) { // hybridRef changed - call it with new this - const auto& maybeFunc = newViewProps.hybridRef.value; + const auto& maybeFunc = newViewProps.get<"hybridRef">().value; if (maybeFunc.has_value()) { maybeFunc.value()(_hybridView); } - newViewProps.hybridRef.isDirty = false; + newViewProps.get<"hybridRef">().isDirty = false; } // 4. Continue in base class diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp deleted file mode 100644 index e7c7a6bd8c..0000000000 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/// -/// HybridRecyclableTestViewComponent.cpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © Marc Rousavy @ Margelo -/// - -#include "HybridRecyclableTestViewComponent.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace margelo::nitro::test::views { - - extern const char HybridRecyclableTestViewComponentName[] = "RecyclableTestView"; - - HybridRecyclableTestViewProps::HybridRecyclableTestViewProps(const react::PropsParserContext& context, - const HybridRecyclableTestViewProps& sourceProps, - const react::RawProps& rawProps): - react::ViewProps(context, sourceProps, rawProps, filterObjectKeys), - isBlue([&]() -> CachedProp { - try { - const react::RawValue* rawValue = rawProps.at("isBlue", nullptr, nullptr); - if (rawValue == nullptr) return sourceProps.isBlue; - const auto& [runtime, value] = (std::pair)*rawValue; - return CachedProp::fromRawValue(*runtime, value, sourceProps.isBlue); - } catch (const std::exception& exc) { - throw std::runtime_error(std::string("RecyclableTestView.isBlue: ") + exc.what()); - } - }()), - hybridRef([&]() -> CachedProp& /* ref */)>>> { - try { - const react::RawValue* rawValue = rawProps.at("hybridRef", nullptr, nullptr); - if (rawValue == nullptr) return sourceProps.hybridRef; - const auto& [runtime, value] = (std::pair)*rawValue; - return CachedProp& /* ref */)>>>::fromRawValue(*runtime, value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f")), sourceProps.hybridRef); - } catch (const std::exception& exc) { - throw std::runtime_error(std::string("RecyclableTestView.hybridRef: ") + exc.what()); - } - }()) { } - - bool HybridRecyclableTestViewProps::filterObjectKeys(const std::string& propName) { - switch (hashString(propName)) { - case hashString("isBlue"): return true; - case hashString("hybridRef"): return true; - default: return false; - } - } - -} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp index 7d2b703690..5f4d5e6283 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp @@ -7,14 +7,8 @@ #pragma once -#include -#include -#include -#include -#include -#include #include -#include +#include #include #include @@ -30,25 +24,15 @@ namespace margelo::nitro::test::views { /** * The name of the actual native View. */ - extern const char HybridRecyclableTestViewComponentName[]; + inline constexpr char HybridRecyclableTestViewComponentName[] = "RecyclableTestView"; /** * Props for the "RecyclableTestView" View. */ - class HybridRecyclableTestViewProps final: public react::ViewProps { - public: - HybridRecyclableTestViewProps() = default; - HybridRecyclableTestViewProps(const react::PropsParserContext& context, - const HybridRecyclableTestViewProps& sourceProps, - const react::RawProps& rawProps); - - public: - CachedProp isBlue; - CachedProp& /* ref */)>>> hybridRef; - - private: - static bool filterObjectKeys(const std::string& propName); - }; + using HybridRecyclableTestViewProps = nitro::HybridViewProps< + "RecyclableTestView", + nitro::ViewProp<"isBlue", bool>, + nitro::ViewProp<"hybridRef", std::optional& /* ref */)>>>>; /** * State for the "RecyclableTestView" View. diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp deleted file mode 100644 index 09fa0414ed..0000000000 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/// -/// HybridTestViewComponent.cpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © Marc Rousavy @ Margelo -/// - -#include "HybridTestViewComponent.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace margelo::nitro::test::views { - - extern const char HybridTestViewComponentName[] = "TestView"; - - HybridTestViewProps::HybridTestViewProps(const react::PropsParserContext& context, - const HybridTestViewProps& sourceProps, - const react::RawProps& rawProps): - react::ViewProps(context, sourceProps, rawProps, filterObjectKeys), - isBlue([&]() -> CachedProp { - try { - const react::RawValue* rawValue = rawProps.at("isBlue", nullptr, nullptr); - if (rawValue == nullptr) return sourceProps.isBlue; - const auto& [runtime, value] = (std::pair)*rawValue; - return CachedProp::fromRawValue(*runtime, value, sourceProps.isBlue); - } catch (const std::exception& exc) { - throw std::runtime_error(std::string("TestView.isBlue: ") + exc.what()); - } - }()), - hasBeenCalled([&]() -> CachedProp { - try { - const react::RawValue* rawValue = rawProps.at("hasBeenCalled", nullptr, nullptr); - if (rawValue == nullptr) return sourceProps.hasBeenCalled; - const auto& [runtime, value] = (std::pair)*rawValue; - return CachedProp::fromRawValue(*runtime, value, sourceProps.hasBeenCalled); - } catch (const std::exception& exc) { - throw std::runtime_error(std::string("TestView.hasBeenCalled: ") + exc.what()); - } - }()), - colorScheme([&]() -> CachedProp { - try { - const react::RawValue* rawValue = rawProps.at("colorScheme", nullptr, nullptr); - if (rawValue == nullptr) return sourceProps.colorScheme; - const auto& [runtime, value] = (std::pair)*rawValue; - return CachedProp::fromRawValue(*runtime, value, sourceProps.colorScheme); - } catch (const std::exception& exc) { - throw std::runtime_error(std::string("TestView.colorScheme: ") + exc.what()); - } - }()), - someCallback([&]() -> CachedProp> { - try { - const react::RawValue* rawValue = rawProps.at("someCallback", nullptr, nullptr); - if (rawValue == nullptr) return sourceProps.someCallback; - const auto& [runtime, value] = (std::pair)*rawValue; - return CachedProp>::fromRawValue(*runtime, value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f")), sourceProps.someCallback); - } catch (const std::exception& exc) { - throw std::runtime_error(std::string("TestView.someCallback: ") + exc.what()); - } - }()), - hybridRef([&]() -> CachedProp& /* ref */)>>> { - try { - const react::RawValue* rawValue = rawProps.at("hybridRef", nullptr, nullptr); - if (rawValue == nullptr) return sourceProps.hybridRef; - const auto& [runtime, value] = (std::pair)*rawValue; - return CachedProp& /* ref */)>>>::fromRawValue(*runtime, value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f")), sourceProps.hybridRef); - } catch (const std::exception& exc) { - throw std::runtime_error(std::string("TestView.hybridRef: ") + exc.what()); - } - }()) { } - - bool HybridTestViewProps::filterObjectKeys(const std::string& propName) { - switch (hashString(propName)) { - case hashString("isBlue"): return true; - case hashString("hasBeenCalled"): return true; - case hashString("colorScheme"): return true; - case hashString("someCallback"): return true; - case hashString("hybridRef"): return true; - default: return false; - } - } - -} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp index 81db992f90..676ec8b376 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp @@ -7,14 +7,8 @@ #pragma once -#include -#include -#include -#include -#include -#include #include -#include +#include #include #include @@ -31,28 +25,18 @@ namespace margelo::nitro::test::views { /** * The name of the actual native View. */ - extern const char HybridTestViewComponentName[]; + inline constexpr char HybridTestViewComponentName[] = "TestView"; /** * Props for the "TestView" View. */ - class HybridTestViewProps final: public react::ViewProps { - public: - HybridTestViewProps() = default; - HybridTestViewProps(const react::PropsParserContext& context, - const HybridTestViewProps& sourceProps, - const react::RawProps& rawProps); - - public: - CachedProp isBlue; - CachedProp hasBeenCalled; - CachedProp colorScheme; - CachedProp> someCallback; - CachedProp& /* ref */)>>> hybridRef; - - private: - static bool filterObjectKeys(const std::string& propName); - }; + using HybridTestViewProps = nitro::HybridViewProps< + "TestView", + nitro::ViewProp<"isBlue", bool>, + nitro::ViewProp<"hasBeenCalled", bool>, + nitro::ViewProp<"colorScheme", ColorScheme>, + nitro::ViewProp<"someCallback", std::function>, + nitro::ViewProp<"hybridRef", std::optional& /* ref */)>>>>; /** * State for the "TestView" View. From 2043a3eac10caff6194afa51afd5a3637ec9eee9 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Wed, 19 Aug 2026 14:08:45 +0200 Subject: [PATCH 2/8] Update Podfile.lock --- example/ios/Podfile.lock | 132 +++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 5b27415bbc..9fd11103c2 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2254,84 +2254,84 @@ SPEC CHECKSUMS: FBLazyVector: 24e62c765683b8d89006a88a2c8f5cf019f0074d HarnessUI: bb94ae23e70e83983e4e914a8d7573969e33b930 hermes-engine: 411df881c1affac35ba53c66e3317eca368c0678 - NitroModules: 470614c13d0a7f77f3794faa04447b49978e9c92 - NitroTest: 0afe14464750e8e44fc5976c4fe6440b0cd811f1 - NitroTestExternal: 8f0567301acef980a553d93763e3f6059a5c2fed + NitroModules: 6d062711233e0862bea590b2da8a82a2ae8ea138 + NitroTest: 9b36616e4f13e2b687d329f91880ece8d6e067cb + NitroTestExternal: f54d8545e8749678c77c4c5b05dd82738f8c7d7d RCTDeprecation: a4c521821fab57cbb125b36effe84d897d0dfa12 RCTRequired: 9f3a7e5645d4bc3f551593de7550bb66ab6e42bc RCTSwiftUI: 239ed2eb9e73de5a6f518810630f0c95e01c8702 - RCTSwiftUIWrapper: 966ca7f5f22ac0b2b2255fb09cffc381f5440b03 + RCTSwiftUIWrapper: 7b8a1ffba8994a8f955c563511bffb74b4681317 RCTTypeSafety: 2a6403ba3492c04510e7c15bd635461646c43bb2 React: e2dc35338068bbd299c66f043ae0d7f25de8499e React-callinvoker: 28b25d21b124c26cebaea713ba7d801b9351dc48 - React-Core: 02ed7d2ffb70437bdf2aba074a13078a7b0b9ff0 + React-Core: f90d375d3bab515ad00df30605ce1bf02e6db12f React-Core-prebuilt: 39feb3a948d3918a0cae461d43c828d977b4e5c5 - React-CoreModules: b3a5a42dadcde3b5d47b325bd912eb2ced89e146 - React-cxxreact: fe8f88dda044e5905e99a00f41b7a874c3908716 + React-CoreModules: da4f80202ef954bdfb926ca4c9ac5f685900aa5c + React-cxxreact: 1d1d2a28a5f10e4e2e7cf2bfd54dc9c92c68c672 React-debug: 92944dc4d89f56d640e75498266cbde557a48189 - React-defaultsnativemodule: cd64bc09d7ca24112bbaf1b91edbbcf3d81ea7dc - React-domnativemodule: dacf5bc055ae041039574f38b73a20b91e368774 - React-Fabric: bb0baa33d91839631d315800eb23e9aaa4338a44 - React-FabricComponents: c504d0b0e2f3054b2ba19af839f175cb361153c0 - React-FabricImage: 91eaea1cc58d25ae2596a9277bcfe028f92374c3 - React-featureflags: 5ac0455da0af12ca79b40402e2f42c5c7556b638 - React-featureflagsnativemodule: df7da181b064f10f5959a7cd529b5aab3686ecb2 - React-graphics: d25b1195baf24c7918543f4aff9be89cc080906f - React-hermes: 663286153a8ad6bf752b742654c766ff5e8991e7 - React-idlecallbacksnativemodule: 0bd5392cb67f1ab25df736814b7c05213b1d3c68 - React-ImageManager: a03eed3e3d4222130dc0ad503a1a5f3aa89de746 - React-intersectionobservernativemodule: 5d0f1c3c7b30031b0f6f730ee52dd9d607e2c966 - React-jserrorhandler: d5d6e7e20c5a2d6e8607e18d31d8712d7de676f6 - React-jsi: eb7cc4cffcf24796cc302d5b2bca0e92544139a9 - React-jsiexecutor: 65006f60e64c72c6b82f62ef6bd17c84846e73f8 - React-jsinspector: 01e32e2247b2486117fbb143db7f7717ef462c6d - React-jsinspectorcdp: f1cbb34ca41d188ba22efd9b663cf258a911f6cd - React-jsinspectornetwork: f61acc94c881c41451f508abfe6efa748b956c21 - React-jsinspectortracing: 9395894d9bd4d17931b9afcf230c0e7f4cb3d674 - React-jsitooling: 03ead841daa12a93b18479f5e400ceab3732d36d - React-jsitracing: 4ae61c79e14360d1c6ab566031c62c490da78439 - React-logger: bf149dea4343a9037b74bade36cced8b63f03f46 - React-Mapbuffer: fec3e025f0ffba6b32cd2a1d7bbdee3e269aae90 - React-microtasksnativemodule: ab33a818d339f5a1da308893c11b487be66121a8 - React-mutationobservernativemodule: a42d1626651ccd7d0dc02a56e69d4ec77c248893 - react-native-safe-area-context: fb5c8ee9f6dd62ef710611b3d370c501f42a4ac0 - react-native-segmented-control: bf6e0032726727498e18dd437ae88afcdbc18e99 - React-NativeModulesApple: deba264b03bd79c6bd61014fa30e40321b5e443a - React-networking: 35e6070b084f435429f85c5db40b4d5b38652fe9 + React-defaultsnativemodule: 172d2ef7d11c531c40ee74f3be1ac98d258a817f + React-domnativemodule: 5a60a88075a35e20fa5879c94e7aa24e5f4071d0 + React-Fabric: 5c1954e06c094623e46d51da2dc2c926621c03a5 + React-FabricComponents: f32494150868073accd5d52d46b30a0496626813 + React-FabricImage: dfcd2826b10f05c19e5bfa68d4937c4401c129db + React-featureflags: 84bcfcb8f91f9475e437f3dd579399085a4af51e + React-featureflagsnativemodule: 83111c4ee188b8859aaa8643c1be640442098fef + React-graphics: e0441bc695f5c682a3fb1b0fd317e10765700f12 + React-hermes: da795ff5d5816d8940fca927c46dcdd81d7e9ad6 + React-idlecallbacksnativemodule: b6d79e1c9e335564e17d18e2649fe7524c4e74e4 + React-ImageManager: 0e137d7231092ddaa236f3520b67b4aa833e7079 + React-intersectionobservernativemodule: 160b3c85bb5a3df2bf50e60619c0db50aadbef8c + React-jserrorhandler: 619d382632f5ed166541c03f7ba7deee76fb1b16 + React-jsi: eac528e72d25146faa922ef1aad82d338addbb75 + React-jsiexecutor: 2d3000fdde95d0da451f8976cdf9018448756023 + React-jsinspector: 0377987f9635c64c5aaf72ec73aaf2b0b0da7744 + React-jsinspectorcdp: 9db641a9cd0dd5b693df27d2e665ac2540ddc336 + React-jsinspectornetwork: 61b00d0adfe6f175830660f1b98da576bc74eb0a + React-jsinspectortracing: 0f8d4f2ebd7523aece78cbc926cd4b6f2fc227dc + React-jsitooling: 36f3854063d507bc4d4a01227ebf1b63d9e24592 + React-jsitracing: cddf044c0c2847d3f5822a984018fd5596c1d448 + React-logger: 57001aefabd79d885589d2f31a8be05ccc393eaa + React-Mapbuffer: 1aa9126122d4247ffc24bf9d28d50ce923499a71 + React-microtasksnativemodule: d86581169e9bb5bb6f5fc3c5052f890016c1bf21 + React-mutationobservernativemodule: 9a0c4e866f1ef2a57acebc902ebacbdf469ae741 + react-native-safe-area-context: c1eb308f4b36372a4de4b3bdaa8ed695ec3dd461 + react-native-segmented-control: 44d14c6899ee12de3384517f4fa1cf4a66ae105c + React-NativeModulesApple: cc6ec4767844d610e92cc358bd3ea34937438d56 + React-networking: a8ce15641ed7775d5b54a9d0d32defc367c2216e React-oscompat: 64a0c7ef5441855dc6e2a6afe8ba8f92aa05075e - React-perflogger: ca04287f205086a1edb5c95882be7b6068458889 - React-performancecdpmetrics: cf1d0a3178ccd59353cedcacbda421f40100a889 - React-performancetimeline: c9771212e7a43032d6f8d5edfb58280d46a7ce1f + React-perflogger: b2b0144e4ba8dd157c1e90f8ebb02d22edbd040d + React-performancecdpmetrics: 5a715ec33f2dea48a93a70db37a78b4f51f9fd5a + React-performancetimeline: c292077a6fbc7f423269b01aab0fb7cc48efe3c0 React-RCTActionSheet: ab545c1e7b5f1ce4f8b40b6fa06afe2869095884 - React-RCTAnimation: 343147a9cd68c93d0ca280799fedfc7102d76ce4 - React-RCTAppDelegate: 5054754e92aaa9f8bfabe0f1022b84e46f3dcb57 - React-RCTBlob: 8c7ae3422ca4e72bc64b7a0142fd730efc5d4dfb - React-RCTFabric: be458db054b206c4d8e4f20f666e75d5f2c2d420 - React-RCTFBReactNativeSpec: 06db2e8d0f352d9fa23321aed1dd2cde25a3e83c - React-RCTImage: 481457bca63e039eb997f7d16c7560472f49657e - React-RCTLinking: 76cbb871240cec2dc5e7aa26c60f59e0ebbcf5a2 - React-RCTNetwork: e23a778225b7672e38545d3c5c24e1f4aad6d15f - React-RCTRuntime: 93c830b3ab3f7b494bbe7ae7289784f8b07b3947 - React-RCTSettings: 96196b535bef147381f96cc60ce9bda85d8be848 - React-RCTText: 749ebbd1a999fd84d80f37002ea3bf597fcea6df - React-RCTVibration: 5b41a7f274757c2928845981d970916ef9e4ca14 + React-RCTAnimation: 1f9a58c7b74c25ea4ca6e6fbd05f37cc4919097a + React-RCTAppDelegate: 856f82c57b47c1795009af585cfb7b87fe2d3b5a + React-RCTBlob: 1cb18861a19be0b4d7e44bed9315e791413399f9 + React-RCTFabric: 081853d9efb9b38fa868c1ff3d60e48106fe2a24 + React-RCTFBReactNativeSpec: cbc760c7a889bfce20746ca0037b97c10ea58665 + React-RCTImage: 0d94b22644ca87fcb778a8fa3ffbf990bc9028df + React-RCTLinking: 881a0c6772dd05a14ab0edfea05dadb698ec8090 + React-RCTNetwork: feb412861e3fff3426eb0961c2acdd96bee8dce1 + React-RCTRuntime: c61b848ffadc0209fe643406370d58021bd3585d + React-RCTSettings: b6e14b8764f807ebe9be2e7f0d72be83b359ac26 + React-RCTText: 1ecd4f85ff609183ebec858cf94c0f27a9dec163 + React-RCTVibration: 3611aa5cb59094632b3141d72c50cbb8ce3d5b08 React-rendererconsistency: 6708acd4bc39c1c5b00164370d0010d93b324c1f - React-renderercss: 80eb778756fed511d6128fc005188ac9008b0baf - React-rendererdebug: b46f338fb9d3f0bea6cf0621016c6c5a7a18e72e - React-RuntimeApple: c494b2089fad4a0c553cf63c2bb265f7eca2285d - React-RuntimeCore: b0bb151c3e2b26c6309d45d05c54aa65a6e0c094 - React-runtimeexecutor: 00b18635b6216a1708f6eb35dbadfd993ec91c7b - React-RuntimeHermes: bb44c4c574ce1b9507cad2e6be015344d18b94a9 - React-runtimescheduler: e1631e57209cb94b3efc29002b6a049cac3f6599 - React-timing: 356b88317ca60d373b0d94b6e7a71b0a572899f5 - React-utils: ccc01da318979af773259c4f6cdb1876f6f86f1a - React-webperformancenativemodule: f8b97c2cb6cfa94e92a503c09ad6d491c50a1390 - ReactAppDependencyProvider: 25c9c516839be2c5e3d3344f95dc7da5f7e63fc2 - ReactCodegen: 0f100aa6334186385a43f0dd13d63efc6805ea55 - ReactCommon: 7dfc3250793bf36cf221096ff59e1179e13eef7f + React-renderercss: d4a70898381431dcc07d6deba94a7eaef0cc9058 + React-rendererdebug: ad92235a960983f69183e2e59e2bce21e72c80a0 + React-RuntimeApple: 53a5b8fe60b044ec6fd4d254d66b7da69314a295 + React-RuntimeCore: 97e125471c0b8313db2bbeb1e9dc001a5503e979 + React-runtimeexecutor: 0ff42dcf1cc4bc7a89d29532a16a7d2d3849fb2f + React-RuntimeHermes: da5a1b7e39f3db1a7b3303d75d4c5bb6cd7c0d49 + React-runtimescheduler: 0e24c80f0c8b6e822a33e4ad89e0ab555704eedb + React-timing: f23e82ad46673716e34a786048414ab80f237594 + React-utils: 2851415c698da4b18331f9a445825d260fffa32c + React-webperformancenativemodule: 9f29ed34f6f6c01dac688b95fd2dfcbccf3aed7a + ReactAppDependencyProvider: a54c0c9b976766e1b6d5e2bb4d1ad0d15913e9e1 + ReactCodegen: 3a5a0ec931ffa938358b221bec9d592202be5078 + ReactCommon: 3ec2a55999d296af90c24beb66c8a77dc660d3ef ReactNativeDependencies: 69d317614e9b1fca501f383f23fb4d9d70ba5053 - RNScreens: 991cc417cd396602a6cf59a42139e5a9d91462a9 - Yoga: 77dfa8673de2874e1855002ae59c68b8be9b007b + RNScreens: 032083414633a4d94cbcf08f8ab5019d72d1ba36 + Yoga: 36fee8f1fca3f54a28c3d7f80e69f66d73d3af96 PODFILE CHECKSUM: cc2ab22c5169410d8739c73db2747f1617e7eaf0 From 9a5c5b157897f1c36d44929d3c80ace8577f2cda Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Wed, 19 Aug 2026 16:55:03 +0200 Subject: [PATCH 3/8] Generate props in user code again --- example/ios/Podfile.lock | 2 +- .../src/views/CppHybridViewComponent.ts | 88 ++++++- .../views/kotlin/KotlinHybridViewManager.ts | 26 +- .../src/views/swift/SwiftHybridViewManager.ts | 27 ++- .../NitroModules.podspec | 2 +- .../cpp/views/HybridViewProps.hpp | 226 ------------------ .../cpp/views/ViewPropParser.hpp | 72 ++++++ .../android/NitroTest+autolinking.cmake | 2 + .../JHybridRecyclableTestViewStateUpdater.cpp | 16 +- .../c++/views/JHybridTestViewStateUpdater.cpp | 34 +-- .../HybridRecyclableTestViewComponent.mm | 16 +- .../ios/c++/views/HybridTestViewComponent.mm | 34 +-- .../HybridRecyclableTestViewComponent.cpp | 34 +++ .../HybridRecyclableTestViewComponent.hpp | 29 ++- .../c++/views/HybridTestViewComponent.cpp | 40 ++++ .../c++/views/HybridTestViewComponent.hpp | 35 ++- 16 files changed, 360 insertions(+), 323 deletions(-) delete mode 100644 packages/react-native-nitro-modules/cpp/views/HybridViewProps.hpp create mode 100644 packages/react-native-nitro-modules/cpp/views/ViewPropParser.hpp create mode 100644 packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp create mode 100644 packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 9fd11103c2..e4f424592c 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2254,7 +2254,7 @@ SPEC CHECKSUMS: FBLazyVector: 24e62c765683b8d89006a88a2c8f5cf019f0074d HarnessUI: bb94ae23e70e83983e4e914a8d7573969e33b930 hermes-engine: 411df881c1affac35ba53c66e3317eca368c0678 - NitroModules: 6d062711233e0862bea590b2da8a82a2ae8ea138 + NitroModules: ffc31850b8b48c167125daaa4e323d0070f77f58 NitroTest: 9b36616e4f13e2b687d329f91880ece8d6e067cb NitroTestExternal: f54d8545e8749678c77c4c5b05dd82738f8c7d7d RCTDeprecation: a4c521821fab57cbb125b36effe84d897d0dfa12 diff --git a/packages/nitrogen/src/views/CppHybridViewComponent.ts b/packages/nitrogen/src/views/CppHybridViewComponent.ts index 306fb45fc4..5d820af95e 100644 --- a/packages/nitrogen/src/views/CppHybridViewComponent.ts +++ b/packages/nitrogen/src/views/CppHybridViewComponent.ts @@ -1,7 +1,11 @@ import type { SourceFile } from '../syntax/SourceFile.js' import type { HybridObjectSpec } from '../syntax/HybridObjectSpec.js' import { createIndentation, indent } from '../utils.js' -import { createFileMetadataString, isNotDuplicate } from '../syntax/helpers.js' +import { + createFileMetadataString, + escapeCppName, + isNotDuplicate, +} from '../syntax/helpers.js' import { getHybridObjectName } from '../syntax/getHybridObjectName.js' import { includeHeader } from '../syntax/c++/includeNitroHeader.js' import { createHostComponentJs } from './createHostComponentJs.js' @@ -37,7 +41,7 @@ export function getViewComponentNames( } } -function getHybridRefProperty(spec: HybridObjectSpec): Property { +export function getHybridRefProperty(spec: HybridObjectSpec): Property { const hybrid = new HybridObjectType(spec) const type = new FunctionType(new VoidType(), [ new NamedWrappingType('ref', hybrid), @@ -67,8 +71,12 @@ export function createViewComponentShadowNodeFiles( const namespace = spec.config.getCxxNamespace('c++', 'views') const props = [...spec.properties, getHybridRefProperty(spec)] - const propSchemas = props.map( - (p) => `nitro::ViewProp<"${p.name}", ${p.type.getCode('c++')}>` + const properties = props.map( + (p) => + `nitro::CachedProp<${p.type.getCode('c++')}> ${escapeCppName(p.name)};` + ) + const filterCases = props.map( + (prop) => `case hashString("${prop.name}"): return true;` ) const includes = props .flatMap((p) => @@ -83,10 +91,15 @@ ${createFileMetadataString(`${component}.hpp`)} #pragma once -#include -#include +#include #include #include +#include +#include +#include +#include + +#include ${includes.join('\n')} @@ -97,14 +110,24 @@ namespace ${namespace} { /** * The name of the actual native View. */ - inline constexpr char ${nameVariable}[] = "${T}"; + extern const char ${nameVariable}[]; /** * Props for the "${spec.name}" View. */ - using ${propsClassName} = nitro::HybridViewProps< - "${spec.name}", - ${indent(propSchemas.join(',\n'), ' ')}>; + class ${propsClassName} final: public react::ViewProps { + public: + ${propsClassName}() = default; + ${propsClassName}(const react::PropsParserContext& context, + ${createIndentation(propsClassName.length)} const ${propsClassName}& sourceProps, + ${createIndentation(propsClassName.length)} const react::RawProps& rawProps); + + public: + ${indent(properties.join('\n'), ' ')} + + private: + static bool filterObjectKeys(const std::string& propName); + }; /** * State for the "${spec.name}" View. @@ -126,6 +149,44 @@ namespace ${namespace} { /* The actual view for "${spec.name}" needs to be implemented in platform-specific code. */ +} // namespace ${namespace} +`.trim() + + const propInitializers = [ + 'react::ViewProps(context, sourceProps, rawProps, filterObjectKeys)', + ...props.map((prop) => { + const name = escapeCppName(prop.name) + const type = prop.type.getCode('c++') + return `${name}(nitro::parseViewProp<${type}>("${spec.name}", "${prop.name}", rawProps, sourceProps.${name}))` + }), + ] + const ctorIndent = createIndentation(propsClassName.length * 2) + const componentCode = ` +${createFileMetadataString(`${component}.cpp`)} + +#include "${component}.hpp" + +#include +#include + +namespace ${namespace} { + + using namespace facebook; + + extern const char ${nameVariable}[] = "${T}"; + + ${propsClassName}::${propsClassName}(const react::PropsParserContext& context, + ${ctorIndent} const ${propsClassName}& sourceProps, + ${ctorIndent} const react::RawProps& rawProps): + ${indent(propInitializers.join(',\n'), ' ')} { } + + bool ${propsClassName}::filterObjectKeys(const std::string& propName) { + switch (hashString(propName)) { + ${indent(filterCases.join('\n'), ' ')} + default: return false; + } + } + } // namespace ${namespace} `.trim() @@ -137,6 +198,13 @@ namespace ${namespace} { platform: 'shared', subdirectory: ['views'], }, + { + name: `${component}.cpp`, + content: componentCode, + language: 'c++', + platform: 'shared', + subdirectory: ['views'], + }, ] const jsFiles = createHostComponentJs(spec) files.push(...(jsFiles as unknown as SourceFile[])) diff --git a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts index 72e8e442be..76679bf5a6 100644 --- a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts +++ b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts @@ -2,9 +2,13 @@ import type { SourceFile } from '../../syntax/SourceFile.js' import type { HybridObjectSpec } from '../../syntax/HybridObjectSpec.js' import { createViewComponentShadowNodeFiles, + getHybridRefProperty, getViewComponentNames, } from '../CppHybridViewComponent.js' -import { createFileMetadataString } from '../../syntax/helpers.js' +import { + createFileMetadataString, + escapeCppName, +} from '../../syntax/helpers.js' import { getHybridObjectName } from '../../syntax/getHybridObjectName.js' import { addJNINativeRegistration } from '../../syntax/kotlin/JNINativeRegistrations.js' import { indent } from '../../utils.js' @@ -34,6 +38,9 @@ export function createKotlinHybridViewManager( ) } const viewImplementation = implementation.implementationClassName + const hybridRef = getHybridRefProperty(spec) + const hybridRefName = escapeCppName(hybridRef.name) + const hybridRefType = hybridRef.type.getCode('c++') const viewManagerCode = ` ${createFileMetadataString(`${manager}.kt`)} @@ -188,11 +195,12 @@ public: `.trim() const propsUpdaterCalls = spec.properties.map((p) => { + const name = escapeCppName(p.name) const setter = p.getSetterName('other') return ` -if (props->get<"${p.name}">().isDirty) { - hybridView->${setter}(props->get<"${p.name}">().value); - props->get<"${p.name}">().isDirty = false; +if (props->${name}.isDirty) { + hybridView->${setter}(props->${name}.value); + props->${name}.isDirty = false; } `.trim() }) @@ -219,10 +227,10 @@ void J${stateUpdaterName}::updateViewProps(jni::alias_ref /* class if (!stateWrapperInterface->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) [[unlikely]] { throw std::runtime_error("StateWrapper is not a StateWrapperImpl"); } - auto stateWrapper = jni::alias_ref{ + jni::alias_ref stateWrapper{ static_cast(rawStateWrapper)}; std::shared_ptr state = stateWrapper->cthis()->getState(); - auto concreteState = std::static_pointer_cast(state); + std::shared_ptr concreteState = std::static_pointer_cast(state); const ${stateClassName}& data = concreteState->getData(); const std::shared_ptr<${propsClassName}>& props = data.getProps(); if (props == nullptr) [[unlikely]] { @@ -234,13 +242,13 @@ void J${stateUpdaterName}::updateViewProps(jni::alias_ref /* class ${indent(propsUpdaterCalls.join('\n'), ' ')} // Update hybridRef if it changed - if (props->get<"hybridRef">().isDirty) { + if (props->${hybridRefName}.isDirty) { // hybridRef changed - call it with new this - const auto& maybeFunc = props->get<"hybridRef">().value; + const ${hybridRefType}& maybeFunc = props->${hybridRefName}.value; if (maybeFunc.has_value()) { maybeFunc.value()(hybridView); } - props->get<"hybridRef">().isDirty = false; + props->${hybridRefName}.isDirty = false; } } diff --git a/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts b/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts index b6408fc45d..f11fcceb47 100644 --- a/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts +++ b/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts @@ -2,9 +2,13 @@ import type { SourceFile } from '../../syntax/SourceFile.js' import type { HybridObjectSpec } from '../../syntax/HybridObjectSpec.js' import { createViewComponentShadowNodeFiles, + getHybridRefProperty, getViewComponentNames, } from '../CppHybridViewComponent.js' -import { createFileMetadataString } from '../../syntax/helpers.js' +import { + createFileMetadataString, + escapeCppName, +} from '../../syntax/helpers.js' import { getUmbrellaHeaderName } from '../../autolinking/ios/createSwiftUmbrellaHeader.js' import { getHybridObjectName } from '../../syntax/getHybridObjectName.js' import { @@ -32,18 +36,23 @@ export function createSwiftHybridViewManager( ) } + const hybridRef = getHybridRefProperty(spec) + const hybridRefName = escapeCppName(hybridRef.name) + const hybridRefType = hybridRef.type.getCode('c++') + const propAssignments = spec.properties.map((p) => { + const name = escapeCppName(p.name) const setter = p.getSetterName('swift') const bridge = new SwiftCxxBridgedType(p.type, false) const parse = bridge.parseFromCppToSwift( - `newViewProps.get<"${p.name}">().value`, + `newViewProps.${name}.value`, 'c++' ) return ` // ${p.jsSignature} -if (newViewProps.get<"${p.name}">().isDirty) { +if (newViewProps.${name}.isDirty) { swiftPart.${setter}(${indent(parse, ' ')}); - newViewProps.get<"${p.name}">().isDirty = false; + newViewProps.${name}.isDirty = false; } `.trim() }) @@ -132,8 +141,8 @@ using namespace ${namespace}::views; _didDropView = NO; // 1. Downcast props - const auto& newViewPropsConst = *std::static_pointer_cast<${propsClassName} const>(props); - auto& newViewProps = const_cast<${propsClassName}&>(newViewPropsConst); + const ${propsClassName}& newViewPropsConst = *std::static_pointer_cast<${propsClassName} const>(props); + ${propsClassName}& newViewProps = const_cast<${propsClassName}&>(newViewPropsConst); ${swiftNamespace}::${HybridTSpecCxx}& swiftPart = _hybridView->getSwiftPart(); // 2. Update each prop individually @@ -144,13 +153,13 @@ using namespace ${namespace}::views; swiftPart.afterUpdate(); // 3. Update hybridRef if it changed - if (newViewProps.get<"hybridRef">().isDirty) { + if (newViewProps.${hybridRefName}.isDirty) { // hybridRef changed - call it with new this - const auto& maybeFunc = newViewProps.get<"hybridRef">().value; + const ${hybridRefType}& maybeFunc = newViewProps.${hybridRefName}.value; if (maybeFunc.has_value()) { maybeFunc.value()(_hybridView); } - newViewProps.get<"hybridRef">().isDirty = false; + newViewProps.${hybridRefName}.isDirty = false; } // 4. Continue in base class diff --git a/packages/react-native-nitro-modules/NitroModules.podspec b/packages/react-native-nitro-modules/NitroModules.podspec index 064a5de1b8..14617d7fef 100644 --- a/packages/react-native-nitro-modules/NitroModules.podspec +++ b/packages/react-native-nitro-modules/NitroModules.podspec @@ -51,7 +51,7 @@ Pod::Spec.new do |s| "cpp/utils/NitroDefines.hpp", "cpp/utils/PropNameIDCache.hpp", "cpp/views/CachedProp.hpp", - "cpp/views/HybridViewProps.hpp", + "cpp/views/ViewPropParser.hpp", "cpp/views/ViewComponentDescriptor.hpp", "cpp/views/ViewPropsHolderState.hpp", # Public iOS-specific headers that will be exposed in modulemap (for Swift) diff --git a/packages/react-native-nitro-modules/cpp/views/HybridViewProps.hpp b/packages/react-native-nitro-modules/cpp/views/HybridViewProps.hpp deleted file mode 100644 index be9ff064ec..0000000000 --- a/packages/react-native-nitro-modules/cpp/views/HybridViewProps.hpp +++ /dev/null @@ -1,226 +0,0 @@ -// -// HybridViewProps.hpp -// react-native-nitro -// -// Created by Marc Rousavy on 19.08.26. -// - -#pragma once - -#include "CachedProp.hpp" -#include "NitroHash.hpp" -#include "PropNameIDCache.hpp" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace margelo::nitro { - -using namespace facebook; - -/** - * A compile-time string that can be used as a non-type template parameter. - */ -template struct FixedString final { -public: - char value[Size]{}; - -public: - constexpr FixedString(const char (&string)[Size]) noexcept { - for (size_t index = 0; index < Size; index++) { - value[index] = string[index]; - } - } - - [[nodiscard]] - constexpr const char *data() const noexcept { - return value; - } - - [[nodiscard]] - constexpr std::string_view stringView() const noexcept { - return std::string_view(value, Size - 1); - } - - template - [[nodiscard]] - constexpr bool - operator==(const FixedString &other) const noexcept { - return stringView() == other.stringView(); - } -}; - -template FixedString(const char (&)[Size]) -> FixedString; - -/** - * Describes a single prop on a `HybridViewProps` type. - */ -template struct ViewProp final { -public: - using Type = TValue; - static constexpr auto name = Name; -}; - -namespace detail { - -template struct IsViewProp : std::false_type {}; - -template -struct IsViewProp> : std::true_type {}; - -template struct IsStdFunction : std::false_type {}; - -template -struct IsStdFunction> : std::true_type {}; - -template -struct IsFunctionViewProp : IsStdFunction> {}; - -template -struct IsFunctionViewProp> : IsFunctionViewProp {}; - -template struct HasUniqueViewPropNames; - -template <> struct HasUniqueViewPropNames<> : std::true_type {}; - -template -struct HasUniqueViewPropNames - : std::bool_constant<((TProp::name != TRest::name) && ...) && - HasUniqueViewPropNames::value> {}; - -template struct FindViewProp; - -template struct FindViewProp { - using Type = void; -}; - -template -struct FindViewProp { - using Type = std::conditional_t::Type>; -}; - -template struct ViewPropStorage { -public: - using Type = typename TProp::Type; - -public: - CachedProp cachedProp; - -public: - ViewPropStorage() = default; - explicit ViewPropStorage(CachedProp &&prop) - : cachedProp(std::move(prop)) {} -}; - -template -CachedProp -parseViewProp(const react::RawProps &rawProps, - const CachedProp &sourceProp) { - try { - // This lookup must happen for every prop, and in the order in which the - // ViewProp descriptors were supplied to HybridViewProps. - const react::RawValue *rawValue = - rawProps.at(TProp::name.data(), nullptr, nullptr); - if (rawValue == nullptr) { - return sourceProp; - } - - const auto &[runtime, value] = - (std::pair)*rawValue; - if constexpr (IsFunctionViewProp::value) { - // React Native cannot transport functions as regular props. Nitrogen - // wraps them as `{ f: function }`, so unwrap `f` before converting and - // before comparing the JSI value with the cached value. - jsi::Value function = value.asObject(*runtime).getProperty( - *runtime, PropNameIDCache::get(*runtime, "f")); - return CachedProp::fromRawValue(*runtime, function, - sourceProp); - } else { - return CachedProp::fromRawValue(*runtime, value, - sourceProp); - } - } catch (const std::exception &exception) { - throw std::runtime_error(std::string(ViewName.data()) + "." + - TProp::name.data() + ": " + exception.what()); - } -} - -} // namespace detail - -/** - * The React Native props for a Nitro Hybrid View. - * - * Each `ViewProp` descriptor declares a prop name and its converted C++ type. - * This class owns RawProps lookup, function unwrapping, conversion, caching, - * and filtering for all declared props. - */ -template -class HybridViewProps final : public react::ViewProps, - private detail::ViewPropStorage... { - static_assert( - (detail::IsViewProp::value && ...), - "HybridViewProps only accepts ViewProp descriptors."); - static_assert( - detail::HasUniqueViewPropNames::value, - "HybridViewProps cannot contain multiple props with the same name."); - -public: - HybridViewProps() = default; - - HybridViewProps(const react::PropsParserContext &context, - const HybridViewProps &sourceProps, - const react::RawProps &rawProps) - : react::ViewProps(context, sourceProps, rawProps, filterObjectKeys), - detail::ViewPropStorage(detail::parseViewProp( - rawProps, - static_cast &>(sourceProps) - .cachedProp))... {} - -public: - template - [[nodiscard]] - decltype(auto) get() noexcept { - using TProp = typename detail::FindViewProp::Type; - if constexpr (std::is_void_v) { - static_assert(!std::is_void_v, - "HybridViewProps does not contain a prop with this name."); - } else { - return (static_cast &>(*this).cachedProp); - } - } - - template - [[nodiscard]] - decltype(auto) get() const noexcept { - using TProp = typename detail::FindViewProp::Type; - if constexpr (std::is_void_v) { - static_assert(!std::is_void_v, - "HybridViewProps does not contain a prop with this name."); - } else { - return (static_cast &>(*this) - .cachedProp); - } - } - -private: - static bool filterObjectKeys(const std::string &propName) { - const uint64_t propHash = hashString(propName); - return (false || ... || - (propHash == hashString(TProps::name.stringView()))); - } -}; - -} // namespace margelo::nitro diff --git a/packages/react-native-nitro-modules/cpp/views/ViewPropParser.hpp b/packages/react-native-nitro-modules/cpp/views/ViewPropParser.hpp new file mode 100644 index 0000000000..385f75eb7a --- /dev/null +++ b/packages/react-native-nitro-modules/cpp/views/ViewPropParser.hpp @@ -0,0 +1,72 @@ +// +// ViewPropParser.hpp +// react-native-nitro +// +// Created by Marc Rousavy on 19.08.26. +// + +#pragma once + +#include "CachedProp.hpp" +#include "PropNameIDCache.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace margelo::nitro { + +using namespace facebook; + +namespace detail { + + template + struct IsFunctionProp : std::false_type {}; + + template + struct IsFunctionProp> : std::true_type {}; + + template + struct IsFunctionProp> : IsFunctionProp {}; + +} // namespace detail + +/** + * Parses one generated Hybrid View prop from React Native's RawProps. + * + * This owns Nitro's conversion and caching behavior so generated Props classes + * only need to declare their named CachedProp members and connect each member + * to its React prop name. + */ +template +CachedProp parseViewProp(const char* viewName, const char* propName, const react::RawProps& rawProps, const CachedProp& sourceProp) { + try { + const react::RawValue* rawValue = rawProps.at(propName, nullptr, nullptr); + if (rawValue == nullptr) { + return sourceProp; + } + + auto [runtime, value] = static_cast>(*rawValue); + + if constexpr (detail::IsFunctionProp>::value) { + // React Native cannot transport functions as regular props. Nitrogen + // wraps them as `{ f: function }`, so unwrap `f` before converting and + // caching the JSI value. + jsi::Value function = value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f")); + return CachedProp::fromRawValue(*runtime, function, sourceProp); + } else { + return CachedProp::fromRawValue(*runtime, value, sourceProp); + } + } catch (const std::exception& exception) { + throw std::runtime_error(std::string(viewName) + "." + propName + ": " + exception.what()); + } +} + +} // namespace margelo::nitro diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake b/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake index adbbce2894..222b2ef3fe 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake +++ b/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake @@ -37,9 +37,11 @@ target_sources( ../nitrogen/generated/shared/c++/HybridChildSpec.cpp ../nitrogen/generated/shared/c++/HybridPlatformObjectSpec.cpp ../nitrogen/generated/shared/c++/HybridRecyclableTestViewSpec.cpp + ../nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp ../nitrogen/generated/shared/c++/HybridTestObjectCppSpec.cpp ../nitrogen/generated/shared/c++/HybridTestObjectSwiftKotlinSpec.cpp ../nitrogen/generated/shared/c++/HybridTestViewSpec.cpp + ../nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp # Android-specific Nitrogen C++ sources ../nitrogen/generated/android/c++/JHybridBaseSpec.cpp ../nitrogen/generated/android/c++/JHybridChildSpec.cpp diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridRecyclableTestViewStateUpdater.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridRecyclableTestViewStateUpdater.cpp index 5c837e1096..959fe48cf2 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridRecyclableTestViewStateUpdater.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridRecyclableTestViewStateUpdater.cpp @@ -25,10 +25,10 @@ void JHybridRecyclableTestViewStateUpdater::updateViewProps(jni::alias_refisInstanceOf(react::StateWrapperImpl::javaClassStatic())) [[unlikely]] { throw std::runtime_error("StateWrapper is not a StateWrapperImpl"); } - auto stateWrapper = jni::alias_ref{ + jni::alias_ref stateWrapper{ static_cast(rawStateWrapper)}; std::shared_ptr state = stateWrapper->cthis()->getState(); - auto concreteState = std::static_pointer_cast(state); + std::shared_ptr concreteState = std::static_pointer_cast(state); const HybridRecyclableTestViewState& data = concreteState->getData(); const std::shared_ptr& props = data.getProps(); if (props == nullptr) [[unlikely]] { @@ -37,19 +37,19 @@ void JHybridRecyclableTestViewStateUpdater::updateViewProps(jni::alias_refget<"isBlue">().isDirty) { - hybridView->setIsBlue(props->get<"isBlue">().value); - props->get<"isBlue">().isDirty = false; + if (props->isBlue.isDirty) { + hybridView->setIsBlue(props->isBlue.value); + props->isBlue.isDirty = false; } // Update hybridRef if it changed - if (props->get<"hybridRef">().isDirty) { + if (props->hybridRef.isDirty) { // hybridRef changed - call it with new this - const auto& maybeFunc = props->get<"hybridRef">().value; + const std::optional& /* ref */)>>& maybeFunc = props->hybridRef.value; if (maybeFunc.has_value()) { maybeFunc.value()(hybridView); } - props->get<"hybridRef">().isDirty = false; + props->hybridRef.isDirty = false; } } diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp index 298a65e0a1..cd75deb6ad 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp @@ -25,10 +25,10 @@ void JHybridTestViewStateUpdater::updateViewProps(jni::alias_ref /* if (!stateWrapperInterface->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) [[unlikely]] { throw std::runtime_error("StateWrapper is not a StateWrapperImpl"); } - auto stateWrapper = jni::alias_ref{ + jni::alias_ref stateWrapper{ static_cast(rawStateWrapper)}; std::shared_ptr state = stateWrapper->cthis()->getState(); - auto concreteState = std::static_pointer_cast(state); + std::shared_ptr concreteState = std::static_pointer_cast(state); const HybridTestViewState& data = concreteState->getData(); const std::shared_ptr& props = data.getProps(); if (props == nullptr) [[unlikely]] { @@ -37,31 +37,31 @@ void JHybridTestViewStateUpdater::updateViewProps(jni::alias_ref /* } // Update all props if they are dirty - if (props->get<"isBlue">().isDirty) { - hybridView->setIsBlue(props->get<"isBlue">().value); - props->get<"isBlue">().isDirty = false; + if (props->isBlue.isDirty) { + hybridView->setIsBlue(props->isBlue.value); + props->isBlue.isDirty = false; } - if (props->get<"hasBeenCalled">().isDirty) { - hybridView->setHasBeenCalled(props->get<"hasBeenCalled">().value); - props->get<"hasBeenCalled">().isDirty = false; + if (props->hasBeenCalled.isDirty) { + hybridView->setHasBeenCalled(props->hasBeenCalled.value); + props->hasBeenCalled.isDirty = false; } - if (props->get<"colorScheme">().isDirty) { - hybridView->setColorScheme(props->get<"colorScheme">().value); - props->get<"colorScheme">().isDirty = false; + if (props->colorScheme.isDirty) { + hybridView->setColorScheme(props->colorScheme.value); + props->colorScheme.isDirty = false; } - if (props->get<"someCallback">().isDirty) { - hybridView->setSomeCallback(props->get<"someCallback">().value); - props->get<"someCallback">().isDirty = false; + if (props->someCallback.isDirty) { + hybridView->setSomeCallback(props->someCallback.value); + props->someCallback.isDirty = false; } // Update hybridRef if it changed - if (props->get<"hybridRef">().isDirty) { + if (props->hybridRef.isDirty) { // hybridRef changed - call it with new this - const auto& maybeFunc = props->get<"hybridRef">().value; + const std::optional& /* ref */)>>& maybeFunc = props->hybridRef.value; if (maybeFunc.has_value()) { maybeFunc.value()(hybridView); } - props->get<"hybridRef">().isDirty = false; + props->hybridRef.isDirty = false; } } diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm index 29f7b7732a..47d25a03c7 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm @@ -86,29 +86,29 @@ - (void) updateProps:(const std::shared_ptr&)props _didDropView = NO; // 1. Downcast props - const auto& newViewPropsConst = *std::static_pointer_cast(props); - auto& newViewProps = const_cast(newViewPropsConst); + const HybridRecyclableTestViewProps& newViewPropsConst = *std::static_pointer_cast(props); + HybridRecyclableTestViewProps& newViewProps = const_cast(newViewPropsConst); NitroTest::HybridRecyclableTestViewSpec_cxx& swiftPart = _hybridView->getSwiftPart(); // 2. Update each prop individually swiftPart.beforeUpdate(); // isBlue: boolean - if (newViewProps.get<"isBlue">().isDirty) { - swiftPart.setIsBlue(newViewProps.get<"isBlue">().value); - newViewProps.get<"isBlue">().isDirty = false; + if (newViewProps.isBlue.isDirty) { + swiftPart.setIsBlue(newViewProps.isBlue.value); + newViewProps.isBlue.isDirty = false; } swiftPart.afterUpdate(); // 3. Update hybridRef if it changed - if (newViewProps.get<"hybridRef">().isDirty) { + if (newViewProps.hybridRef.isDirty) { // hybridRef changed - call it with new this - const auto& maybeFunc = newViewProps.get<"hybridRef">().value; + const std::optional& /* ref */)>>& maybeFunc = newViewProps.hybridRef.value; if (maybeFunc.has_value()) { maybeFunc.value()(_hybridView); } - newViewProps.get<"hybridRef">().isDirty = false; + newViewProps.hybridRef.isDirty = false; } // 4. Continue in base class diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm index 48ef5460a6..347eee3ee9 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm @@ -86,44 +86,44 @@ - (void) updateProps:(const std::shared_ptr&)props _didDropView = NO; // 1. Downcast props - const auto& newViewPropsConst = *std::static_pointer_cast(props); - auto& newViewProps = const_cast(newViewPropsConst); + const HybridTestViewProps& newViewPropsConst = *std::static_pointer_cast(props); + HybridTestViewProps& newViewProps = const_cast(newViewPropsConst); NitroTest::HybridTestViewSpec_cxx& swiftPart = _hybridView->getSwiftPart(); // 2. Update each prop individually swiftPart.beforeUpdate(); // isBlue: boolean - if (newViewProps.get<"isBlue">().isDirty) { - swiftPart.setIsBlue(newViewProps.get<"isBlue">().value); - newViewProps.get<"isBlue">().isDirty = false; + if (newViewProps.isBlue.isDirty) { + swiftPart.setIsBlue(newViewProps.isBlue.value); + newViewProps.isBlue.isDirty = false; } // hasBeenCalled: boolean - if (newViewProps.get<"hasBeenCalled">().isDirty) { - swiftPart.setHasBeenCalled(newViewProps.get<"hasBeenCalled">().value); - newViewProps.get<"hasBeenCalled">().isDirty = false; + if (newViewProps.hasBeenCalled.isDirty) { + swiftPart.setHasBeenCalled(newViewProps.hasBeenCalled.value); + newViewProps.hasBeenCalled.isDirty = false; } // colorScheme: enum - if (newViewProps.get<"colorScheme">().isDirty) { - swiftPart.setColorScheme(static_cast(newViewProps.get<"colorScheme">().value)); - newViewProps.get<"colorScheme">().isDirty = false; + if (newViewProps.colorScheme.isDirty) { + swiftPart.setColorScheme(static_cast(newViewProps.colorScheme.value)); + newViewProps.colorScheme.isDirty = false; } // someCallback: function - if (newViewProps.get<"someCallback">().isDirty) { - swiftPart.setSomeCallback(newViewProps.get<"someCallback">().value); - newViewProps.get<"someCallback">().isDirty = false; + if (newViewProps.someCallback.isDirty) { + swiftPart.setSomeCallback(newViewProps.someCallback.value); + newViewProps.someCallback.isDirty = false; } swiftPart.afterUpdate(); // 3. Update hybridRef if it changed - if (newViewProps.get<"hybridRef">().isDirty) { + if (newViewProps.hybridRef.isDirty) { // hybridRef changed - call it with new this - const auto& maybeFunc = newViewProps.get<"hybridRef">().value; + const std::optional& /* ref */)>>& maybeFunc = newViewProps.hybridRef.value; if (maybeFunc.has_value()) { maybeFunc.value()(_hybridView); } - newViewProps.get<"hybridRef">().isDirty = false; + newViewProps.hybridRef.isDirty = false; } // 4. Continue in base class diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp new file mode 100644 index 0000000000..41fddc42fc --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp @@ -0,0 +1,34 @@ +/// +/// HybridRecyclableTestViewComponent.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "HybridRecyclableTestViewComponent.hpp" + +#include +#include + +namespace margelo::nitro::test::views { + + using namespace facebook; + + extern const char HybridRecyclableTestViewComponentName[] = "RecyclableTestView"; + + HybridRecyclableTestViewProps::HybridRecyclableTestViewProps(const react::PropsParserContext& context, + const HybridRecyclableTestViewProps& sourceProps, + const react::RawProps& rawProps): + react::ViewProps(context, sourceProps, rawProps, filterObjectKeys), + isBlue(nitro::parseViewProp("RecyclableTestView", "isBlue", rawProps, sourceProps.isBlue)), + hybridRef(nitro::parseViewProp& /* ref */)>>>("RecyclableTestView", "hybridRef", rawProps, sourceProps.hybridRef)) { } + + bool HybridRecyclableTestViewProps::filterObjectKeys(const std::string& propName) { + switch (hashString(propName)) { + case hashString("isBlue"): return true; + case hashString("hybridRef"): return true; + default: return false; + } + } + +} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp index 5f4d5e6283..627fd6fe04 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp @@ -7,10 +7,15 @@ #pragma once -#include -#include +#include #include #include +#include +#include +#include +#include + +#include #include #include "HybridRecyclableTestViewSpec.hpp" @@ -24,15 +29,25 @@ namespace margelo::nitro::test::views { /** * The name of the actual native View. */ - inline constexpr char HybridRecyclableTestViewComponentName[] = "RecyclableTestView"; + extern const char HybridRecyclableTestViewComponentName[]; /** * Props for the "RecyclableTestView" View. */ - using HybridRecyclableTestViewProps = nitro::HybridViewProps< - "RecyclableTestView", - nitro::ViewProp<"isBlue", bool>, - nitro::ViewProp<"hybridRef", std::optional& /* ref */)>>>>; + class HybridRecyclableTestViewProps final: public react::ViewProps { + public: + HybridRecyclableTestViewProps() = default; + HybridRecyclableTestViewProps(const react::PropsParserContext& context, + const HybridRecyclableTestViewProps& sourceProps, + const react::RawProps& rawProps); + + public: + nitro::CachedProp isBlue; + nitro::CachedProp& /* ref */)>>> hybridRef; + + private: + static bool filterObjectKeys(const std::string& propName); + }; /** * State for the "RecyclableTestView" View. diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp new file mode 100644 index 0000000000..995602d5f4 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp @@ -0,0 +1,40 @@ +/// +/// HybridTestViewComponent.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "HybridTestViewComponent.hpp" + +#include +#include + +namespace margelo::nitro::test::views { + + using namespace facebook; + + extern const char HybridTestViewComponentName[] = "TestView"; + + HybridTestViewProps::HybridTestViewProps(const react::PropsParserContext& context, + const HybridTestViewProps& sourceProps, + const react::RawProps& rawProps): + react::ViewProps(context, sourceProps, rawProps, filterObjectKeys), + isBlue(nitro::parseViewProp("TestView", "isBlue", rawProps, sourceProps.isBlue)), + hasBeenCalled(nitro::parseViewProp("TestView", "hasBeenCalled", rawProps, sourceProps.hasBeenCalled)), + colorScheme(nitro::parseViewProp("TestView", "colorScheme", rawProps, sourceProps.colorScheme)), + someCallback(nitro::parseViewProp>("TestView", "someCallback", rawProps, sourceProps.someCallback)), + hybridRef(nitro::parseViewProp& /* ref */)>>>("TestView", "hybridRef", rawProps, sourceProps.hybridRef)) { } + + bool HybridTestViewProps::filterObjectKeys(const std::string& propName) { + switch (hashString(propName)) { + case hashString("isBlue"): return true; + case hashString("hasBeenCalled"): return true; + case hashString("colorScheme"): return true; + case hashString("someCallback"): return true; + case hashString("hybridRef"): return true; + default: return false; + } + } + +} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp index 676ec8b376..b5c51ad43b 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp @@ -7,10 +7,15 @@ #pragma once -#include -#include +#include #include #include +#include +#include +#include +#include + +#include #include "ColorScheme.hpp" #include @@ -25,18 +30,28 @@ namespace margelo::nitro::test::views { /** * The name of the actual native View. */ - inline constexpr char HybridTestViewComponentName[] = "TestView"; + extern const char HybridTestViewComponentName[]; /** * Props for the "TestView" View. */ - using HybridTestViewProps = nitro::HybridViewProps< - "TestView", - nitro::ViewProp<"isBlue", bool>, - nitro::ViewProp<"hasBeenCalled", bool>, - nitro::ViewProp<"colorScheme", ColorScheme>, - nitro::ViewProp<"someCallback", std::function>, - nitro::ViewProp<"hybridRef", std::optional& /* ref */)>>>>; + class HybridTestViewProps final: public react::ViewProps { + public: + HybridTestViewProps() = default; + HybridTestViewProps(const react::PropsParserContext& context, + const HybridTestViewProps& sourceProps, + const react::RawProps& rawProps); + + public: + nitro::CachedProp isBlue; + nitro::CachedProp hasBeenCalled; + nitro::CachedProp colorScheme; + nitro::CachedProp> someCallback; + nitro::CachedProp& /* ref */)>>> hybridRef; + + private: + static bool filterObjectKeys(const std::string& propName); + }; /** * State for the "TestView" View. From a3a7dd92ed35f23b055bc81155a70526900b4043 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Wed, 19 Aug 2026 17:05:59 +0200 Subject: [PATCH 4/8] Use CachedProp constructor --- example/ios/Podfile.lock | 132 +++++++++--------- .../src/views/CppHybridViewComponent.ts | 2 +- .../cpp/templates/IsFunctionProp.hpp | 25 ++++ .../cpp/views/CachedProp.hpp | 37 ++++- .../cpp/views/ViewPropParser.hpp | 72 ---------- .../HybridRecyclableTestViewComponent.cpp | 4 +- .../c++/views/HybridTestViewComponent.cpp | 10 +- 7 files changed, 135 insertions(+), 147 deletions(-) create mode 100644 packages/react-native-nitro-modules/cpp/templates/IsFunctionProp.hpp delete mode 100644 packages/react-native-nitro-modules/cpp/views/ViewPropParser.hpp diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index e4f424592c..c737aa9750 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2254,84 +2254,84 @@ SPEC CHECKSUMS: FBLazyVector: 24e62c765683b8d89006a88a2c8f5cf019f0074d HarnessUI: bb94ae23e70e83983e4e914a8d7573969e33b930 hermes-engine: 411df881c1affac35ba53c66e3317eca368c0678 - NitroModules: ffc31850b8b48c167125daaa4e323d0070f77f58 - NitroTest: 9b36616e4f13e2b687d329f91880ece8d6e067cb - NitroTestExternal: f54d8545e8749678c77c4c5b05dd82738f8c7d7d + NitroModules: 2f4f1e6148ce404a9260e181a00ce8d19642ad62 + NitroTest: 0afe14464750e8e44fc5976c4fe6440b0cd811f1 + NitroTestExternal: 8f0567301acef980a553d93763e3f6059a5c2fed RCTDeprecation: a4c521821fab57cbb125b36effe84d897d0dfa12 RCTRequired: 9f3a7e5645d4bc3f551593de7550bb66ab6e42bc RCTSwiftUI: 239ed2eb9e73de5a6f518810630f0c95e01c8702 - RCTSwiftUIWrapper: 7b8a1ffba8994a8f955c563511bffb74b4681317 + RCTSwiftUIWrapper: 966ca7f5f22ac0b2b2255fb09cffc381f5440b03 RCTTypeSafety: 2a6403ba3492c04510e7c15bd635461646c43bb2 React: e2dc35338068bbd299c66f043ae0d7f25de8499e React-callinvoker: 28b25d21b124c26cebaea713ba7d801b9351dc48 - React-Core: f90d375d3bab515ad00df30605ce1bf02e6db12f + React-Core: 02ed7d2ffb70437bdf2aba074a13078a7b0b9ff0 React-Core-prebuilt: 39feb3a948d3918a0cae461d43c828d977b4e5c5 - React-CoreModules: da4f80202ef954bdfb926ca4c9ac5f685900aa5c - React-cxxreact: 1d1d2a28a5f10e4e2e7cf2bfd54dc9c92c68c672 + React-CoreModules: b3a5a42dadcde3b5d47b325bd912eb2ced89e146 + React-cxxreact: fe8f88dda044e5905e99a00f41b7a874c3908716 React-debug: 92944dc4d89f56d640e75498266cbde557a48189 - React-defaultsnativemodule: 172d2ef7d11c531c40ee74f3be1ac98d258a817f - React-domnativemodule: 5a60a88075a35e20fa5879c94e7aa24e5f4071d0 - React-Fabric: 5c1954e06c094623e46d51da2dc2c926621c03a5 - React-FabricComponents: f32494150868073accd5d52d46b30a0496626813 - React-FabricImage: dfcd2826b10f05c19e5bfa68d4937c4401c129db - React-featureflags: 84bcfcb8f91f9475e437f3dd579399085a4af51e - React-featureflagsnativemodule: 83111c4ee188b8859aaa8643c1be640442098fef - React-graphics: e0441bc695f5c682a3fb1b0fd317e10765700f12 - React-hermes: da795ff5d5816d8940fca927c46dcdd81d7e9ad6 - React-idlecallbacksnativemodule: b6d79e1c9e335564e17d18e2649fe7524c4e74e4 - React-ImageManager: 0e137d7231092ddaa236f3520b67b4aa833e7079 - React-intersectionobservernativemodule: 160b3c85bb5a3df2bf50e60619c0db50aadbef8c - React-jserrorhandler: 619d382632f5ed166541c03f7ba7deee76fb1b16 - React-jsi: eac528e72d25146faa922ef1aad82d338addbb75 - React-jsiexecutor: 2d3000fdde95d0da451f8976cdf9018448756023 - React-jsinspector: 0377987f9635c64c5aaf72ec73aaf2b0b0da7744 - React-jsinspectorcdp: 9db641a9cd0dd5b693df27d2e665ac2540ddc336 - React-jsinspectornetwork: 61b00d0adfe6f175830660f1b98da576bc74eb0a - React-jsinspectortracing: 0f8d4f2ebd7523aece78cbc926cd4b6f2fc227dc - React-jsitooling: 36f3854063d507bc4d4a01227ebf1b63d9e24592 - React-jsitracing: cddf044c0c2847d3f5822a984018fd5596c1d448 - React-logger: 57001aefabd79d885589d2f31a8be05ccc393eaa - React-Mapbuffer: 1aa9126122d4247ffc24bf9d28d50ce923499a71 - React-microtasksnativemodule: d86581169e9bb5bb6f5fc3c5052f890016c1bf21 - React-mutationobservernativemodule: 9a0c4e866f1ef2a57acebc902ebacbdf469ae741 - react-native-safe-area-context: c1eb308f4b36372a4de4b3bdaa8ed695ec3dd461 - react-native-segmented-control: 44d14c6899ee12de3384517f4fa1cf4a66ae105c - React-NativeModulesApple: cc6ec4767844d610e92cc358bd3ea34937438d56 - React-networking: a8ce15641ed7775d5b54a9d0d32defc367c2216e + React-defaultsnativemodule: cd64bc09d7ca24112bbaf1b91edbbcf3d81ea7dc + React-domnativemodule: dacf5bc055ae041039574f38b73a20b91e368774 + React-Fabric: bb0baa33d91839631d315800eb23e9aaa4338a44 + React-FabricComponents: c504d0b0e2f3054b2ba19af839f175cb361153c0 + React-FabricImage: 91eaea1cc58d25ae2596a9277bcfe028f92374c3 + React-featureflags: 5ac0455da0af12ca79b40402e2f42c5c7556b638 + React-featureflagsnativemodule: df7da181b064f10f5959a7cd529b5aab3686ecb2 + React-graphics: d25b1195baf24c7918543f4aff9be89cc080906f + React-hermes: 663286153a8ad6bf752b742654c766ff5e8991e7 + React-idlecallbacksnativemodule: 0bd5392cb67f1ab25df736814b7c05213b1d3c68 + React-ImageManager: a03eed3e3d4222130dc0ad503a1a5f3aa89de746 + React-intersectionobservernativemodule: 5d0f1c3c7b30031b0f6f730ee52dd9d607e2c966 + React-jserrorhandler: d5d6e7e20c5a2d6e8607e18d31d8712d7de676f6 + React-jsi: eb7cc4cffcf24796cc302d5b2bca0e92544139a9 + React-jsiexecutor: 65006f60e64c72c6b82f62ef6bd17c84846e73f8 + React-jsinspector: 01e32e2247b2486117fbb143db7f7717ef462c6d + React-jsinspectorcdp: f1cbb34ca41d188ba22efd9b663cf258a911f6cd + React-jsinspectornetwork: f61acc94c881c41451f508abfe6efa748b956c21 + React-jsinspectortracing: 9395894d9bd4d17931b9afcf230c0e7f4cb3d674 + React-jsitooling: 03ead841daa12a93b18479f5e400ceab3732d36d + React-jsitracing: 4ae61c79e14360d1c6ab566031c62c490da78439 + React-logger: bf149dea4343a9037b74bade36cced8b63f03f46 + React-Mapbuffer: fec3e025f0ffba6b32cd2a1d7bbdee3e269aae90 + React-microtasksnativemodule: ab33a818d339f5a1da308893c11b487be66121a8 + React-mutationobservernativemodule: a42d1626651ccd7d0dc02a56e69d4ec77c248893 + react-native-safe-area-context: fb5c8ee9f6dd62ef710611b3d370c501f42a4ac0 + react-native-segmented-control: bf6e0032726727498e18dd437ae88afcdbc18e99 + React-NativeModulesApple: deba264b03bd79c6bd61014fa30e40321b5e443a + React-networking: 35e6070b084f435429f85c5db40b4d5b38652fe9 React-oscompat: 64a0c7ef5441855dc6e2a6afe8ba8f92aa05075e - React-perflogger: b2b0144e4ba8dd157c1e90f8ebb02d22edbd040d - React-performancecdpmetrics: 5a715ec33f2dea48a93a70db37a78b4f51f9fd5a - React-performancetimeline: c292077a6fbc7f423269b01aab0fb7cc48efe3c0 + React-perflogger: ca04287f205086a1edb5c95882be7b6068458889 + React-performancecdpmetrics: cf1d0a3178ccd59353cedcacbda421f40100a889 + React-performancetimeline: c9771212e7a43032d6f8d5edfb58280d46a7ce1f React-RCTActionSheet: ab545c1e7b5f1ce4f8b40b6fa06afe2869095884 - React-RCTAnimation: 1f9a58c7b74c25ea4ca6e6fbd05f37cc4919097a - React-RCTAppDelegate: 856f82c57b47c1795009af585cfb7b87fe2d3b5a - React-RCTBlob: 1cb18861a19be0b4d7e44bed9315e791413399f9 - React-RCTFabric: 081853d9efb9b38fa868c1ff3d60e48106fe2a24 - React-RCTFBReactNativeSpec: cbc760c7a889bfce20746ca0037b97c10ea58665 - React-RCTImage: 0d94b22644ca87fcb778a8fa3ffbf990bc9028df - React-RCTLinking: 881a0c6772dd05a14ab0edfea05dadb698ec8090 - React-RCTNetwork: feb412861e3fff3426eb0961c2acdd96bee8dce1 - React-RCTRuntime: c61b848ffadc0209fe643406370d58021bd3585d - React-RCTSettings: b6e14b8764f807ebe9be2e7f0d72be83b359ac26 - React-RCTText: 1ecd4f85ff609183ebec858cf94c0f27a9dec163 - React-RCTVibration: 3611aa5cb59094632b3141d72c50cbb8ce3d5b08 + React-RCTAnimation: 343147a9cd68c93d0ca280799fedfc7102d76ce4 + React-RCTAppDelegate: 5054754e92aaa9f8bfabe0f1022b84e46f3dcb57 + React-RCTBlob: 8c7ae3422ca4e72bc64b7a0142fd730efc5d4dfb + React-RCTFabric: be458db054b206c4d8e4f20f666e75d5f2c2d420 + React-RCTFBReactNativeSpec: 06db2e8d0f352d9fa23321aed1dd2cde25a3e83c + React-RCTImage: 481457bca63e039eb997f7d16c7560472f49657e + React-RCTLinking: 76cbb871240cec2dc5e7aa26c60f59e0ebbcf5a2 + React-RCTNetwork: e23a778225b7672e38545d3c5c24e1f4aad6d15f + React-RCTRuntime: 93c830b3ab3f7b494bbe7ae7289784f8b07b3947 + React-RCTSettings: 96196b535bef147381f96cc60ce9bda85d8be848 + React-RCTText: 749ebbd1a999fd84d80f37002ea3bf597fcea6df + React-RCTVibration: 5b41a7f274757c2928845981d970916ef9e4ca14 React-rendererconsistency: 6708acd4bc39c1c5b00164370d0010d93b324c1f - React-renderercss: d4a70898381431dcc07d6deba94a7eaef0cc9058 - React-rendererdebug: ad92235a960983f69183e2e59e2bce21e72c80a0 - React-RuntimeApple: 53a5b8fe60b044ec6fd4d254d66b7da69314a295 - React-RuntimeCore: 97e125471c0b8313db2bbeb1e9dc001a5503e979 - React-runtimeexecutor: 0ff42dcf1cc4bc7a89d29532a16a7d2d3849fb2f - React-RuntimeHermes: da5a1b7e39f3db1a7b3303d75d4c5bb6cd7c0d49 - React-runtimescheduler: 0e24c80f0c8b6e822a33e4ad89e0ab555704eedb - React-timing: f23e82ad46673716e34a786048414ab80f237594 - React-utils: 2851415c698da4b18331f9a445825d260fffa32c - React-webperformancenativemodule: 9f29ed34f6f6c01dac688b95fd2dfcbccf3aed7a - ReactAppDependencyProvider: a54c0c9b976766e1b6d5e2bb4d1ad0d15913e9e1 - ReactCodegen: 3a5a0ec931ffa938358b221bec9d592202be5078 - ReactCommon: 3ec2a55999d296af90c24beb66c8a77dc660d3ef + React-renderercss: 80eb778756fed511d6128fc005188ac9008b0baf + React-rendererdebug: b46f338fb9d3f0bea6cf0621016c6c5a7a18e72e + React-RuntimeApple: c494b2089fad4a0c553cf63c2bb265f7eca2285d + React-RuntimeCore: b0bb151c3e2b26c6309d45d05c54aa65a6e0c094 + React-runtimeexecutor: 00b18635b6216a1708f6eb35dbadfd993ec91c7b + React-RuntimeHermes: bb44c4c574ce1b9507cad2e6be015344d18b94a9 + React-runtimescheduler: e1631e57209cb94b3efc29002b6a049cac3f6599 + React-timing: 356b88317ca60d373b0d94b6e7a71b0a572899f5 + React-utils: ccc01da318979af773259c4f6cdb1876f6f86f1a + React-webperformancenativemodule: f8b97c2cb6cfa94e92a503c09ad6d491c50a1390 + ReactAppDependencyProvider: 25c9c516839be2c5e3d3344f95dc7da5f7e63fc2 + ReactCodegen: 0f100aa6334186385a43f0dd13d63efc6805ea55 + ReactCommon: 7dfc3250793bf36cf221096ff59e1179e13eef7f ReactNativeDependencies: 69d317614e9b1fca501f383f23fb4d9d70ba5053 - RNScreens: 032083414633a4d94cbcf08f8ab5019d72d1ba36 - Yoga: 36fee8f1fca3f54a28c3d7f80e69f66d73d3af96 + RNScreens: 991cc417cd396602a6cf59a42139e5a9d91462a9 + Yoga: 77dfa8673de2874e1855002ae59c68b8be9b007b PODFILE CHECKSUM: cc2ab22c5169410d8739c73db2747f1617e7eaf0 diff --git a/packages/nitrogen/src/views/CppHybridViewComponent.ts b/packages/nitrogen/src/views/CppHybridViewComponent.ts index 5d820af95e..dbf263f16d 100644 --- a/packages/nitrogen/src/views/CppHybridViewComponent.ts +++ b/packages/nitrogen/src/views/CppHybridViewComponent.ts @@ -157,7 +157,7 @@ namespace ${namespace} { ...props.map((prop) => { const name = escapeCppName(prop.name) const type = prop.type.getCode('c++') - return `${name}(nitro::parseViewProp<${type}>("${spec.name}", "${prop.name}", rawProps, sourceProps.${name}))` + return `${name}(nitro::CachedProp<${type}>::fromRawValue("${spec.name}", "${prop.name}", rawProps, sourceProps.${name}))` }), ] const ctorIndent = createIndentation(propsClassName.length * 2) diff --git a/packages/react-native-nitro-modules/cpp/templates/IsFunctionProp.hpp b/packages/react-native-nitro-modules/cpp/templates/IsFunctionProp.hpp new file mode 100644 index 0000000000..5a12a4c027 --- /dev/null +++ b/packages/react-native-nitro-modules/cpp/templates/IsFunctionProp.hpp @@ -0,0 +1,25 @@ +// +// IsFunctionProp.hpp +// NitroModules +// +// Created by Marc Rousavy on 21.06.24. +// + +#pragma once + +#include + +namespace margelo::nitro { + +using namespace facebook; + +template +struct IsFunctionProp : std::false_type {}; + +template +struct IsFunctionProp> : std::true_type {}; + +template +struct IsFunctionProp> : IsFunctionProp {}; + +} // namespace margelo::nitro diff --git a/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp b/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp index 4033e0035d..0152ffc0ce 100644 --- a/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp +++ b/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp @@ -5,10 +5,15 @@ #pragma once #include "BorrowingReference.hpp" +#include "IsFunctionProp.hpp" #include "JSIConverter.hpp" #include "NitroDefines.hpp" +#include "PropNameIDCache.hpp" #include +#include +#include + namespace margelo::nitro { using namespace facebook; @@ -46,7 +51,7 @@ struct CachedProp { } public: - static CachedProp fromRawValue(jsi::Runtime& runtime, const jsi::Value& value, const CachedProp& oldProp) { + static CachedProp fromJSIValue(jsi::Runtime& runtime, const jsi::Value& value, const CachedProp& oldProp) { if (oldProp.equals(runtime, value)) { // jsi::Value hasn't changed - no need to convert it again! return oldProp; @@ -59,6 +64,36 @@ struct CachedProp { } return CachedProp(std::move(converted), std::move(cached)); } + + static CachedProp fromRawValue(const char* viewName, const char* propName, const react::RawProps& rawProps, + const CachedProp& previousProp) { + try { + const react::RawValue* rawValue = rawProps.at(propName, nullptr, nullptr); + if (rawValue == nullptr) { + // This RawValue pack does not contain our prop, so skip it - it's still the same from before + return previousProp; + } + + auto [runtime, value] = static_cast>(*rawValue); + + if constexpr (IsFunctionProp>::value) { + // React Native cannot transport functions as regular props. Nitrogen + // wraps them as `{ f: function }`, so we unwrap `f` before converting + // and caching the JSI value. + jsi::Value function = value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f")); + return CachedProp::fromRawValue(*runtime, function, previousProp); + } else { + return CachedProp::fromRawValue(*runtime, value, previousProp); + } + } catch (const std::exception& exception) { + throw std::runtime_error(std::string(viewName) + "." + propName + ": " + exception.what()); + } + } + + [[deprecated("Update nitrogen and re-generate specs.")]] + static CachedProp fromRawValue(jsi::Runtime& runtime, const jsi::Value& value, const CachedProp& oldProp) { + return fromJSIValue(runtime, value, oldProp); + } }; } // namespace margelo::nitro diff --git a/packages/react-native-nitro-modules/cpp/views/ViewPropParser.hpp b/packages/react-native-nitro-modules/cpp/views/ViewPropParser.hpp deleted file mode 100644 index 385f75eb7a..0000000000 --- a/packages/react-native-nitro-modules/cpp/views/ViewPropParser.hpp +++ /dev/null @@ -1,72 +0,0 @@ -// -// ViewPropParser.hpp -// react-native-nitro -// -// Created by Marc Rousavy on 19.08.26. -// - -#pragma once - -#include "CachedProp.hpp" -#include "PropNameIDCache.hpp" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace margelo::nitro { - -using namespace facebook; - -namespace detail { - - template - struct IsFunctionProp : std::false_type {}; - - template - struct IsFunctionProp> : std::true_type {}; - - template - struct IsFunctionProp> : IsFunctionProp {}; - -} // namespace detail - -/** - * Parses one generated Hybrid View prop from React Native's RawProps. - * - * This owns Nitro's conversion and caching behavior so generated Props classes - * only need to declare their named CachedProp members and connect each member - * to its React prop name. - */ -template -CachedProp parseViewProp(const char* viewName, const char* propName, const react::RawProps& rawProps, const CachedProp& sourceProp) { - try { - const react::RawValue* rawValue = rawProps.at(propName, nullptr, nullptr); - if (rawValue == nullptr) { - return sourceProp; - } - - auto [runtime, value] = static_cast>(*rawValue); - - if constexpr (detail::IsFunctionProp>::value) { - // React Native cannot transport functions as regular props. Nitrogen - // wraps them as `{ f: function }`, so unwrap `f` before converting and - // caching the JSI value. - jsi::Value function = value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f")); - return CachedProp::fromRawValue(*runtime, function, sourceProp); - } else { - return CachedProp::fromRawValue(*runtime, value, sourceProp); - } - } catch (const std::exception& exception) { - throw std::runtime_error(std::string(viewName) + "." + propName + ": " + exception.what()); - } -} - -} // namespace margelo::nitro diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp index 41fddc42fc..4eaa9a3b31 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp @@ -20,8 +20,8 @@ namespace margelo::nitro::test::views { const HybridRecyclableTestViewProps& sourceProps, const react::RawProps& rawProps): react::ViewProps(context, sourceProps, rawProps, filterObjectKeys), - isBlue(nitro::parseViewProp("RecyclableTestView", "isBlue", rawProps, sourceProps.isBlue)), - hybridRef(nitro::parseViewProp& /* ref */)>>>("RecyclableTestView", "hybridRef", rawProps, sourceProps.hybridRef)) { } + isBlue(nitro::CachedProp::fromRawValue("RecyclableTestView", "isBlue", rawProps, sourceProps.isBlue)), + hybridRef(nitro::CachedProp& /* ref */)>>>::fromRawValue("RecyclableTestView", "hybridRef", rawProps, sourceProps.hybridRef)) { } bool HybridRecyclableTestViewProps::filterObjectKeys(const std::string& propName) { switch (hashString(propName)) { diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp index 995602d5f4..d60e8bd159 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp @@ -20,11 +20,11 @@ namespace margelo::nitro::test::views { const HybridTestViewProps& sourceProps, const react::RawProps& rawProps): react::ViewProps(context, sourceProps, rawProps, filterObjectKeys), - isBlue(nitro::parseViewProp("TestView", "isBlue", rawProps, sourceProps.isBlue)), - hasBeenCalled(nitro::parseViewProp("TestView", "hasBeenCalled", rawProps, sourceProps.hasBeenCalled)), - colorScheme(nitro::parseViewProp("TestView", "colorScheme", rawProps, sourceProps.colorScheme)), - someCallback(nitro::parseViewProp>("TestView", "someCallback", rawProps, sourceProps.someCallback)), - hybridRef(nitro::parseViewProp& /* ref */)>>>("TestView", "hybridRef", rawProps, sourceProps.hybridRef)) { } + isBlue(nitro::CachedProp::fromRawValue("TestView", "isBlue", rawProps, sourceProps.isBlue)), + hasBeenCalled(nitro::CachedProp::fromRawValue("TestView", "hasBeenCalled", rawProps, sourceProps.hasBeenCalled)), + colorScheme(nitro::CachedProp::fromRawValue("TestView", "colorScheme", rawProps, sourceProps.colorScheme)), + someCallback(nitro::CachedProp>::fromRawValue("TestView", "someCallback", rawProps, sourceProps.someCallback)), + hybridRef(nitro::CachedProp& /* ref */)>>>::fromRawValue("TestView", "hybridRef", rawProps, sourceProps.hybridRef)) { } bool HybridTestViewProps::filterObjectKeys(const std::string& propName) { switch (hashString(propName)) { From 5e77595bf4f5d28fbfec8ca66c655d2d115f94eb Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Wed, 19 Aug 2026 17:08:02 +0200 Subject: [PATCH 5/8] include properly --- packages/nitrogen/src/views/CppHybridViewComponent.ts | 2 +- .../shared/c++/views/HybridRecyclableTestViewComponent.cpp | 2 +- .../generated/shared/c++/views/HybridTestViewComponent.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nitrogen/src/views/CppHybridViewComponent.ts b/packages/nitrogen/src/views/CppHybridViewComponent.ts index dbf263f16d..234bc3e67e 100644 --- a/packages/nitrogen/src/views/CppHybridViewComponent.ts +++ b/packages/nitrogen/src/views/CppHybridViewComponent.ts @@ -167,7 +167,7 @@ ${createFileMetadataString(`${component}.cpp`)} #include "${component}.hpp" #include -#include +#include namespace ${namespace} { diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp index 4eaa9a3b31..8a6e91c401 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp @@ -8,7 +8,7 @@ #include "HybridRecyclableTestViewComponent.hpp" #include -#include +#include namespace margelo::nitro::test::views { diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp index d60e8bd159..e1ec7a8fc7 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp @@ -8,7 +8,7 @@ #include "HybridTestViewComponent.hpp" #include -#include +#include namespace margelo::nitro::test::views { From c894c2612b57158f825ed1ba211be2d0b14b2e06 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Wed, 19 Aug 2026 17:09:14 +0200 Subject: [PATCH 6/8] Update IsFunctionProp.hpp --- .../react-native-nitro-modules/cpp/templates/IsFunctionProp.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/react-native-nitro-modules/cpp/templates/IsFunctionProp.hpp b/packages/react-native-nitro-modules/cpp/templates/IsFunctionProp.hpp index 5a12a4c027..1f9bc6ae84 100644 --- a/packages/react-native-nitro-modules/cpp/templates/IsFunctionProp.hpp +++ b/packages/react-native-nitro-modules/cpp/templates/IsFunctionProp.hpp @@ -11,8 +11,6 @@ namespace margelo::nitro { -using namespace facebook; - template struct IsFunctionProp : std::false_type {}; From f269c09b434710d87f49b1bda1ac6958531ef82a Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Wed, 19 Aug 2026 17:20:20 +0200 Subject: [PATCH 7/8] refactor --- .../cpp/views/CachedProp.hpp | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp b/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp index 0152ffc0ce..af9735d1b0 100644 --- a/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp +++ b/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp @@ -51,20 +51,6 @@ struct CachedProp { } public: - static CachedProp fromJSIValue(jsi::Runtime& runtime, const jsi::Value& value, const CachedProp& oldProp) { - if (oldProp.equals(runtime, value)) { - // jsi::Value hasn't changed - no need to convert it again! - return oldProp; - } - T converted = JSIConverter::fromJSI(runtime, value); - BorrowingReference cached; - { - JSICacheReference cache = JSICache::getOrCreateCache(runtime); - cached = cache.makeShared(jsi::Value(runtime, value)); - } - return CachedProp(std::move(converted), std::move(cached)); - } - static CachedProp fromRawValue(const char* viewName, const char* propName, const react::RawProps& rawProps, const CachedProp& previousProp) { try { @@ -81,9 +67,9 @@ struct CachedProp { // wraps them as `{ f: function }`, so we unwrap `f` before converting // and caching the JSI value. jsi::Value function = value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f")); - return CachedProp::fromRawValue(*runtime, function, previousProp); + return CachedProp::fromJSIValue(*runtime, std::move(function), previousProp); } else { - return CachedProp::fromRawValue(*runtime, value, previousProp); + return CachedProp::fromJSIValue(*runtime, std::move(value), previousProp); } } catch (const std::exception& exception) { throw std::runtime_error(std::string(viewName) + "." + propName + ": " + exception.what()); @@ -92,7 +78,27 @@ struct CachedProp { [[deprecated("Update nitrogen and re-generate specs.")]] static CachedProp fromRawValue(jsi::Runtime& runtime, const jsi::Value& value, const CachedProp& oldProp) { - return fromJSIValue(runtime, value, oldProp); + if (oldProp.equals(runtime, value)) { + return oldProp; + } + + return convertAndCacheJSIValue(runtime, jsi::Value(runtime, value)); + } + +private: + static CachedProp fromJSIValue(jsi::Runtime& runtime, jsi::Value&& value, const CachedProp& previousProp) { + if (previousProp.equals(runtime, value)) { + // jsi::Value hasn't changed - no need to convert it again! + return previousProp; + } + // The new `value` differs from our previous value, so let's convert it using JSIConverter and cache it + return convertAndCacheJSIValue(runtime, std::move(value)); + } + static CachedProp convertAndCacheJSIValue(jsi::Runtime& runtime, jsi::Value&& value) { + T converted = JSIConverter::fromJSI(runtime, value); + JSICacheReference cache = JSICache::getOrCreateCache(runtime); + BorrowingReference cached = cache.makeShared(std::move(value)); + return CachedProp(std::move(converted), std::move(cached)); } }; From 78dd2455766ebc4ddeb172810299b623f4026865 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Wed, 19 Aug 2026 17:25:20 +0200 Subject: [PATCH 8/8] simplify --- .../src/views/CppHybridViewComponent.ts | 2 +- .../src/views/kotlin/KotlinHybridViewManager.ts | 17 +++++++---------- .../src/views/swift/SwiftHybridViewManager.ts | 11 +++-------- .../JHybridRecyclableTestViewStateUpdater.cpp | 9 +++++---- .../c++/views/JHybridTestViewStateUpdater.cpp | 9 +++++---- .../views/HybridRecyclableTestViewComponent.mm | 2 +- .../ios/c++/views/HybridTestViewComponent.mm | 2 +- 7 files changed, 23 insertions(+), 29 deletions(-) diff --git a/packages/nitrogen/src/views/CppHybridViewComponent.ts b/packages/nitrogen/src/views/CppHybridViewComponent.ts index 234bc3e67e..60fede68dd 100644 --- a/packages/nitrogen/src/views/CppHybridViewComponent.ts +++ b/packages/nitrogen/src/views/CppHybridViewComponent.ts @@ -41,7 +41,7 @@ export function getViewComponentNames( } } -export function getHybridRefProperty(spec: HybridObjectSpec): Property { +function getHybridRefProperty(spec: HybridObjectSpec): Property { const hybrid = new HybridObjectType(spec) const type = new FunctionType(new VoidType(), [ new NamedWrappingType('ref', hybrid), diff --git a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts index 76679bf5a6..75c7da5eed 100644 --- a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts +++ b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts @@ -2,7 +2,6 @@ import type { SourceFile } from '../../syntax/SourceFile.js' import type { HybridObjectSpec } from '../../syntax/HybridObjectSpec.js' import { createViewComponentShadowNodeFiles, - getHybridRefProperty, getViewComponentNames, } from '../CppHybridViewComponent.js' import { @@ -38,9 +37,6 @@ export function createKotlinHybridViewManager( ) } const viewImplementation = implementation.implementationClassName - const hybridRef = getHybridRefProperty(spec) - const hybridRefName = escapeCppName(hybridRef.name) - const hybridRefType = hybridRef.type.getCode('c++') const viewManagerCode = ` ${createFileMetadataString(`${manager}.kt`)} @@ -227,10 +223,11 @@ void J${stateUpdaterName}::updateViewProps(jni::alias_ref /* class if (!stateWrapperInterface->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) [[unlikely]] { throw std::runtime_error("StateWrapper is not a StateWrapperImpl"); } - jni::alias_ref stateWrapper{ - static_cast(rawStateWrapper)}; + auto stateWrapper = jni::alias_ref{ + static_cast(rawStateWrapper) + }; std::shared_ptr state = stateWrapper->cthis()->getState(); - std::shared_ptr concreteState = std::static_pointer_cast(state); + auto concreteState = std::static_pointer_cast(state); const ${stateClassName}& data = concreteState->getData(); const std::shared_ptr<${propsClassName}>& props = data.getProps(); if (props == nullptr) [[unlikely]] { @@ -242,13 +239,13 @@ void J${stateUpdaterName}::updateViewProps(jni::alias_ref /* class ${indent(propsUpdaterCalls.join('\n'), ' ')} // Update hybridRef if it changed - if (props->${hybridRefName}.isDirty) { + if (props->hybridRef.isDirty) { // hybridRef changed - call it with new this - const ${hybridRefType}& maybeFunc = props->${hybridRefName}.value; + const auto& maybeFunc = props->hybridRef.value; if (maybeFunc.has_value()) { maybeFunc.value()(hybridView); } - props->${hybridRefName}.isDirty = false; + props->hybridRef.isDirty = false; } } diff --git a/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts b/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts index f11fcceb47..0d56251f85 100644 --- a/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts +++ b/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts @@ -2,7 +2,6 @@ import type { SourceFile } from '../../syntax/SourceFile.js' import type { HybridObjectSpec } from '../../syntax/HybridObjectSpec.js' import { createViewComponentShadowNodeFiles, - getHybridRefProperty, getViewComponentNames, } from '../CppHybridViewComponent.js' import { @@ -36,10 +35,6 @@ export function createSwiftHybridViewManager( ) } - const hybridRef = getHybridRefProperty(spec) - const hybridRefName = escapeCppName(hybridRef.name) - const hybridRefType = hybridRef.type.getCode('c++') - const propAssignments = spec.properties.map((p) => { const name = escapeCppName(p.name) const setter = p.getSetterName('swift') @@ -153,13 +148,13 @@ using namespace ${namespace}::views; swiftPart.afterUpdate(); // 3. Update hybridRef if it changed - if (newViewProps.${hybridRefName}.isDirty) { + if (newViewProps.hybridRef.isDirty) { // hybridRef changed - call it with new this - const ${hybridRefType}& maybeFunc = newViewProps.${hybridRefName}.value; + const auto& maybeFunc = newViewProps.hybridRef.value; if (maybeFunc.has_value()) { maybeFunc.value()(_hybridView); } - newViewProps.${hybridRefName}.isDirty = false; + newViewProps.hybridRef.isDirty = false; } // 4. Continue in base class diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridRecyclableTestViewStateUpdater.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridRecyclableTestViewStateUpdater.cpp index 959fe48cf2..1265f1df75 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridRecyclableTestViewStateUpdater.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridRecyclableTestViewStateUpdater.cpp @@ -25,10 +25,11 @@ void JHybridRecyclableTestViewStateUpdater::updateViewProps(jni::alias_refisInstanceOf(react::StateWrapperImpl::javaClassStatic())) [[unlikely]] { throw std::runtime_error("StateWrapper is not a StateWrapperImpl"); } - jni::alias_ref stateWrapper{ - static_cast(rawStateWrapper)}; + auto stateWrapper = jni::alias_ref{ + static_cast(rawStateWrapper) + }; std::shared_ptr state = stateWrapper->cthis()->getState(); - std::shared_ptr concreteState = std::static_pointer_cast(state); + auto concreteState = std::static_pointer_cast(state); const HybridRecyclableTestViewState& data = concreteState->getData(); const std::shared_ptr& props = data.getProps(); if (props == nullptr) [[unlikely]] { @@ -45,7 +46,7 @@ void JHybridRecyclableTestViewStateUpdater::updateViewProps(jni::alias_refhybridRef.isDirty) { // hybridRef changed - call it with new this - const std::optional& /* ref */)>>& maybeFunc = props->hybridRef.value; + const auto& maybeFunc = props->hybridRef.value; if (maybeFunc.has_value()) { maybeFunc.value()(hybridView); } diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp index cd75deb6ad..36e859cd52 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp @@ -25,10 +25,11 @@ void JHybridTestViewStateUpdater::updateViewProps(jni::alias_ref /* if (!stateWrapperInterface->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) [[unlikely]] { throw std::runtime_error("StateWrapper is not a StateWrapperImpl"); } - jni::alias_ref stateWrapper{ - static_cast(rawStateWrapper)}; + auto stateWrapper = jni::alias_ref{ + static_cast(rawStateWrapper) + }; std::shared_ptr state = stateWrapper->cthis()->getState(); - std::shared_ptr concreteState = std::static_pointer_cast(state); + auto concreteState = std::static_pointer_cast(state); const HybridTestViewState& data = concreteState->getData(); const std::shared_ptr& props = data.getProps(); if (props == nullptr) [[unlikely]] { @@ -57,7 +58,7 @@ void JHybridTestViewStateUpdater::updateViewProps(jni::alias_ref /* // Update hybridRef if it changed if (props->hybridRef.isDirty) { // hybridRef changed - call it with new this - const std::optional& /* ref */)>>& maybeFunc = props->hybridRef.value; + const auto& maybeFunc = props->hybridRef.value; if (maybeFunc.has_value()) { maybeFunc.value()(hybridView); } diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm index 47d25a03c7..0fa8c6ce8d 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm @@ -104,7 +104,7 @@ - (void) updateProps:(const std::shared_ptr&)props // 3. Update hybridRef if it changed if (newViewProps.hybridRef.isDirty) { // hybridRef changed - call it with new this - const std::optional& /* ref */)>>& maybeFunc = newViewProps.hybridRef.value; + const auto& maybeFunc = newViewProps.hybridRef.value; if (maybeFunc.has_value()) { maybeFunc.value()(_hybridView); } diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm index 347eee3ee9..48332aa184 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm @@ -119,7 +119,7 @@ - (void) updateProps:(const std::shared_ptr&)props // 3. Update hybridRef if it changed if (newViewProps.hybridRef.isDirty) { // hybridRef changed - call it with new this - const std::optional& /* ref */)>>& maybeFunc = newViewProps.hybridRef.value; + const auto& maybeFunc = newViewProps.hybridRef.value; if (maybeFunc.has_value()) { maybeFunc.value()(_hybridView); }