Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2254,7 +2254,7 @@ SPEC CHECKSUMS:
FBLazyVector: 24e62c765683b8d89006a88a2c8f5cf019f0074d
HarnessUI: bb94ae23e70e83983e4e914a8d7573969e33b930
hermes-engine: 411df881c1affac35ba53c66e3317eca368c0678
NitroModules: 470614c13d0a7f77f3794faa04447b49978e9c92
NitroModules: 2f4f1e6148ce404a9260e181a00ce8d19642ad62
NitroTest: 0afe14464750e8e44fc5976c4fe6440b0cd811f1
NitroTestExternal: 8f0567301acef980a553d93763e3f6059a5c2fed
RCTDeprecation: a4c521821fab57cbb125b36effe84d897d0dfa12
Expand Down
72 changes: 21 additions & 51 deletions packages/nitrogen/src/views/CppHybridViewComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { createIndentation, indent } from '../utils.js'
import {
createFileMetadataString,
escapeCppName,
isFunction,
isNotDuplicate,
} from '../syntax/helpers.js'
import { getHybridObjectName } from '../syntax/getHybridObjectName.js'
Expand Down Expand Up @@ -73,9 +72,12 @@ export function createViewComponentShadowNodeFiles(

const props = [...spec.properties, getHybridRefProperty(spec)]
const properties = props.map(
(p) => `CachedProp<${p.type.getCode('c++')}> ${escapeCppName(p.name)};`
(p) =>
`nitro::CachedProp<${p.type.getCode('c++')}> ${escapeCppName(p.name)};`
)
const filterCases = props.map(
(prop) => `case hashString("${prop.name}"): return true;`
)
const cases = props.map((p) => `case hashString("${p.name}"): return true;`)
const includes = props
.flatMap((p) =>
p.getRequiredImports('c++').map((i) => includeHeader(i, true))
Expand All @@ -89,16 +91,15 @@ ${createFileMetadataString(`${component}.hpp`)}

#pragma once

#include <optional>
#include <NitroModules/NitroDefines.hpp>
#include <NitroModules/NitroHash.hpp>
#include <NitroModules/CachedProp.hpp>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewProps.h>
#include <NitroModules/ViewComponentDescriptor.hpp>
#include <NitroModules/ViewPropsHolderState.hpp>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/RawProps.h>

#include <string>

${includes.join('\n')}

Expand Down Expand Up @@ -151,58 +152,27 @@ namespace ${namespace} {
} // namespace ${namespace}
`.trim()

// .cpp code
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::CachedProp<${type}>::fromRawValue("${spec.name}", "${prop.name}", rawProps, sourceProps.${name}))`
}),
]
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<jsi::Runtime*, jsi::Value>)*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 <string>
#include <exception>
#include <utility>
#include <NitroModules/NitroDefines.hpp>
#include <NitroModules/JSIConverter.hpp>
#include <NitroModules/PropNameIDCache.hpp>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/core/ComponentDescriptor.h>
#include <react/renderer/components/view/ViewProps.h>
#include <NitroModules/NitroHash.hpp>
#include <NitroModules/CachedProp.hpp>

namespace ${namespace} {

using namespace facebook;

extern const char ${nameVariable}[] = "${T}";

${propsClassName}::${propsClassName}(const react::PropsParserContext& context,
Expand All @@ -212,7 +182,7 @@ namespace ${namespace} {

bool ${propsClassName}::filterObjectKeys(const std::string& propName) {
switch (hashString(propName)) {
${indent(cases.join('\n'), ' ')}
${indent(filterCases.join('\n'), ' ')}
default: return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ void J${stateUpdaterName}::updateViewProps(jni::alias_ref<jni::JClass> /* class
throw std::runtime_error("StateWrapper is not a StateWrapperImpl");
}
auto stateWrapper = jni::alias_ref<react::StateWrapperImpl::javaobject>{
static_cast<react::StateWrapperImpl::javaobject>(rawStateWrapper)};
static_cast<react::StateWrapperImpl::javaobject>(rawStateWrapper)
};
std::shared_ptr<const react::State> state = stateWrapper->cthis()->getState();
auto concreteState = std::static_pointer_cast<const ConcreteStateData>(state);
const ${stateClassName}& data = concreteState->getData();
Expand Down
4 changes: 2 additions & 2 deletions packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,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
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-nitro-modules/NitroModules.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Pod::Spec.new do |s|
"cpp/utils/NitroDefines.hpp",
"cpp/utils/PropNameIDCache.hpp",
"cpp/views/CachedProp.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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// IsFunctionProp.hpp
// NitroModules
//
// Created by Marc Rousavy on 21.06.24.
//

#pragma once

#include <type_traits>

namespace margelo::nitro {

template <typename T>
struct IsFunctionProp : std::false_type {};

template <typename TResult, typename... TArgs>
struct IsFunctionProp<std::function<TResult(TArgs...)>> : std::true_type {};

template <typename T>
struct IsFunctionProp<std::optional<T>> : IsFunctionProp<T> {};

} // namespace margelo::nitro
53 changes: 47 additions & 6 deletions packages/react-native-nitro-modules/cpp/views/CachedProp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
#pragma once

#include "BorrowingReference.hpp"
#include "IsFunctionProp.hpp"
#include "JSIConverter.hpp"
#include "NitroDefines.hpp"
#include "PropNameIDCache.hpp"
#include <jsi/jsi.h>

#include <react/renderer/core/RawProps.h>
#include <react/renderer/core/RawValue.h>

namespace margelo::nitro {

using namespace facebook;
Expand Down Expand Up @@ -46,17 +51,53 @@ struct CachedProp {
}

public:
static CachedProp<T> fromRawValue(const char* viewName, const char* propName, const react::RawProps& rawProps,
const CachedProp<T>& 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<std::pair<jsi::Runtime*, jsi::Value>>(*rawValue);

if constexpr (IsFunctionProp<std::remove_cv_t<T>>::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<T>::fromJSIValue(*runtime, std::move(function), previousProp);
} else {
return CachedProp<T>::fromJSIValue(*runtime, std::move(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<T> fromRawValue(jsi::Runtime& runtime, const jsi::Value& value, const CachedProp<T>& oldProp) {
if (oldProp.equals(runtime, value)) {
// jsi::Value hasn't changed - no need to convert it again!
return oldProp;
}
T converted = JSIConverter<T>::fromJSI(runtime, value);
BorrowingReference<jsi::Value> cached;
{
JSICacheReference cache = JSICache::getOrCreateCache(runtime);
cached = cache.makeShared(jsi::Value(runtime, value));

return convertAndCacheJSIValue(runtime, jsi::Value(runtime, value));
}

private:
static CachedProp<T> fromJSIValue(jsi::Runtime& runtime, jsi::Value&& value, const CachedProp<T>& 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<T> convertAndCacheJSIValue(jsi::Runtime& runtime, jsi::Value&& value) {
T converted = JSIConverter<T>::fromJSI(runtime, value);
JSICacheReference cache = JSICache::getOrCreateCache(runtime);
BorrowingReference<jsi::Value> cached = cache.makeShared(std::move(value));
return CachedProp<T>(std::move(converted), std::move(cached));
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ void JHybridRecyclableTestViewStateUpdater::updateViewProps(jni::alias_ref<jni::
throw std::runtime_error("StateWrapper is not a StateWrapperImpl");
}
auto stateWrapper = jni::alias_ref<react::StateWrapperImpl::javaobject>{
static_cast<react::StateWrapperImpl::javaobject>(rawStateWrapper)};
static_cast<react::StateWrapperImpl::javaobject>(rawStateWrapper)
};
std::shared_ptr<const react::State> state = stateWrapper->cthis()->getState();
auto concreteState = std::static_pointer_cast<const ConcreteStateData>(state);
const HybridRecyclableTestViewState& data = concreteState->getData();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ void JHybridTestViewStateUpdater::updateViewProps(jni::alias_ref<jni::JClass> /*
throw std::runtime_error("StateWrapper is not a StateWrapperImpl");
}
auto stateWrapper = jni::alias_ref<react::StateWrapperImpl::javaobject>{
static_cast<react::StateWrapperImpl::javaobject>(rawStateWrapper)};
static_cast<react::StateWrapperImpl::javaobject>(rawStateWrapper)
};
std::shared_ptr<const react::State> state = stateWrapper->cthis()->getState();
auto concreteState = std::static_pointer_cast<const ConcreteStateData>(state);
const HybridTestViewState& data = concreteState->getData();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ - (void) updateProps:(const std::shared_ptr<const react::Props>&)props
_didDropView = NO;

// 1. Downcast props
const auto& newViewPropsConst = *std::static_pointer_cast<HybridRecyclableTestViewProps const>(props);
auto& newViewProps = const_cast<HybridRecyclableTestViewProps&>(newViewPropsConst);
const HybridRecyclableTestViewProps& newViewPropsConst = *std::static_pointer_cast<HybridRecyclableTestViewProps const>(props);
HybridRecyclableTestViewProps& newViewProps = const_cast<HybridRecyclableTestViewProps&>(newViewPropsConst);
NitroTest::HybridRecyclableTestViewSpec_cxx& swiftPart = _hybridView->getSwiftPart();

// 2. Update each prop individually
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ - (void) updateProps:(const std::shared_ptr<const react::Props>&)props
_didDropView = NO;

// 1. Downcast props
const auto& newViewPropsConst = *std::static_pointer_cast<HybridTestViewProps const>(props);
auto& newViewProps = const_cast<HybridTestViewProps&>(newViewPropsConst);
const HybridTestViewProps& newViewPropsConst = *std::static_pointer_cast<HybridTestViewProps const>(props);
HybridTestViewProps& newViewProps = const_cast<HybridTestViewProps&>(newViewPropsConst);
NitroTest::HybridTestViewSpec_cxx& swiftPart = _hybridView->getSwiftPart();

// 2. Update each prop individually
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,21 @@

#include "HybridRecyclableTestViewComponent.hpp"

#include <string>
#include <exception>
#include <utility>
#include <NitroModules/NitroDefines.hpp>
#include <NitroModules/JSIConverter.hpp>
#include <NitroModules/PropNameIDCache.hpp>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/core/ComponentDescriptor.h>
#include <react/renderer/components/view/ViewProps.h>
#include <NitroModules/NitroHash.hpp>
#include <NitroModules/CachedProp.hpp>

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([&]() -> CachedProp<bool> {
try {
const react::RawValue* rawValue = rawProps.at("isBlue", nullptr, nullptr);
if (rawValue == nullptr) return sourceProps.isBlue;
const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
return CachedProp<bool>::fromRawValue(*runtime, value, sourceProps.isBlue);
} catch (const std::exception& exc) {
throw std::runtime_error(std::string("RecyclableTestView.isBlue: ") + exc.what());
}
}()),
hybridRef([&]() -> CachedProp<std::optional<std::function<void(const std::shared_ptr<HybridRecyclableTestViewSpec>& /* ref */)>>> {
try {
const react::RawValue* rawValue = rawProps.at("hybridRef", nullptr, nullptr);
if (rawValue == nullptr) return sourceProps.hybridRef;
const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
return CachedProp<std::optional<std::function<void(const std::shared_ptr<HybridRecyclableTestViewSpec>& /* 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());
}
}()) { }
isBlue(nitro::CachedProp<bool>::fromRawValue("RecyclableTestView", "isBlue", rawProps, sourceProps.isBlue)),
hybridRef(nitro::CachedProp<std::optional<std::function<void(const std::shared_ptr<HybridRecyclableTestViewSpec>& /* ref */)>>>::fromRawValue("RecyclableTestView", "hybridRef", rawProps, sourceProps.hybridRef)) { }

bool HybridRecyclableTestViewProps::filterObjectKeys(const std::string& propName) {
switch (hashString(propName)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@

#pragma once

#include <optional>
#include <NitroModules/NitroDefines.hpp>
#include <NitroModules/NitroHash.hpp>
#include <NitroModules/CachedProp.hpp>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewProps.h>
#include <NitroModules/ViewComponentDescriptor.hpp>
#include <NitroModules/ViewPropsHolderState.hpp>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/RawProps.h>

#include <string>

#include <memory>
#include "HybridRecyclableTestViewSpec.hpp"
Expand All @@ -43,8 +42,8 @@ namespace margelo::nitro::test::views {
const react::RawProps& rawProps);

public:
CachedProp<bool> isBlue;
CachedProp<std::optional<std::function<void(const std::shared_ptr<HybridRecyclableTestViewSpec>& /* ref */)>>> hybridRef;
nitro::CachedProp<bool> isBlue;
nitro::CachedProp<std::optional<std::function<void(const std::shared_ptr<HybridRecyclableTestViewSpec>& /* ref */)>>> hybridRef;

private:
static bool filterObjectKeys(const std::string& propName);
Expand Down
Loading
Loading