Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Create React Native ViewManager (+ Props + State) for HybridViews #500

Merged
merged 29 commits into from
Jan 20, 2025
Merged
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c6ba220
fix: Fix required prop connecting
mrousavy Jan 15, 2025
19b6b9a
fix: Use default value and register View!
mrousavy Jan 15, 2025
2410f70
feat: Add types to `getHostComponent`
mrousavy Jan 15, 2025
1b66e79
feat: Expose `getView()` and call it!
mrousavy Jan 15, 2025
8de491c
fix: Pass to C++ as `void*`
mrousavy Jan 15, 2025
ed79295
fix: Fix name and get view
mrousavy Jan 15, 2025
38e4fe5
perf: Use `&` for `getSwiftPart()`
mrousavy Jan 15, 2025
5bd8734
add comments
mrousavy Jan 15, 2025
3a2ea44
feat: Add `CachedProp.hpp`
mrousavy Jan 15, 2025
224925f
fix: Fix OwningRef/BorrowingRef empty copy constructo
mrousavy Jan 15, 2025
8fa40b2
fix: Properly downcast
mrousavy Jan 16, 2025
53ec8b4
perf: Sort view props alphabetically because fabric is implemented th…
mrousavy Jan 16, 2025
d030add
fix: Check for null in OwningReference
mrousavy Jan 16, 2025
ccfc707
format
mrousavy Jan 16, 2025
eb5cf6b
fix: Rename
mrousavy Jan 16, 2025
6daa311
fix: Dont sort alphabetically
mrousavy Jan 16, 2025
1e962bd
feat: Add `try`/`catch` block for prop parsing
mrousavy Jan 16, 2025
a9427ae
fix: Merge props if `nullptr`
mrousavy Jan 16, 2025
d3b9e54
fix: Fix imports
mrousavy Jan 16, 2025
8772df9
feat: Add `beforeUpdate()` and `afterUpdate()`
mrousavy Jan 16, 2025
8e56d0b
feat: Generate view config as .json
mrousavy Jan 16, 2025
8828679
feat: Generate Kotlin/JNI code for this
mrousavy Jan 20, 2025
b73562a
fix: getState() call
mrousavy Jan 20, 2025
d974f76
fix: Use JNI descriptor separator (/)
mrousavy Jan 20, 2025
441a032
fix: Use views/ import
mrousavy Jan 20, 2025
deae283
fix: Add Android extra impl
mrousavy Jan 20, 2025
322fba8
fix: Use `override` instead of `virtual` destructor
mrousavy Jan 20, 2025
a8b075d
feat: Add if-guard to wrap `registerNative` calls
mrousavy Jan 20, 2025
10d208a
fix: Move `#if` check up
mrousavy Jan 20, 2025
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
Prev Previous commit
Next Next commit
feat: Add CachedProp.hpp
mrousavy committed Jan 15, 2025
commit 3a2ea44cf456cb190e71552be15c5c429872ef42
16 changes: 8 additions & 8 deletions packages/nitrogen/src/views/ViewComponentShadowNode.ts
Original file line number Diff line number Diff line change
@@ -53,7 +53,7 @@ export function createViewComponentShadowNodeFiles(
const namespace = NitroConfig.getCxxNamespace('c++', 'views')

const properties = spec.properties.map(
(p) => `${p.type.getCode('c++')} ${escapeCppName(p.name)};`
(p) => `CachedProp<${p.type.getCode('c++')}> ${escapeCppName(p.name)};`
)
const cases = spec.properties.map(
(p) => `case hashString("${p.name}"): return true;`
@@ -74,6 +74,7 @@ ${createFileMetadataString(`${component}.hpp`)}
#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>
@@ -157,13 +158,14 @@ namespace ${namespace} {
]
const propCopyInitializers = ['react::ViewProps()']
for (const prop of spec.properties) {
const type = prop.type.getCode('c++')
propInitializers.push(
`
/* ${prop.name} */ ${escapeCppName(prop.name)}([&]() -> ${prop.type.getCode('c++')} {
${escapeCppName(prop.name)}([&]() -> CachedProp<${type}> {
const react::RawValue* rawValue = rawProps.at("${prop.name}", nullptr, nullptr);
if (rawValue == nullptr) { return {}; }
if (rawValue == nullptr) return {};
const auto& [runtime, value] = (std::pair<jsi::Runtime*, const jsi::Value&>)*rawValue;
return JSIConverter<${prop.type.getCode('c++')}>::fromJSI(*runtime, value);
return CachedProp<${type}>::fromRawValue(*runtime, value, sourceProps.${escapeCppName(prop.name)});
}())`.trim()
)
propCopyInitializers.push(
@@ -185,12 +187,10 @@ namespace ${namespace} {
${propsClassName}::${propsClassName}(const react::PropsParserContext& context,
${ctorIndent} const ${propsClassName}& sourceProps,
${ctorIndent} const react::RawProps& rawProps):
${indent(propInitializers.join(',\n'), ' ')} {
// TODO: Instead of eagerly converting each prop, only convert the ones that changed on demand.
}
${indent(propInitializers.join(',\n'), ' ')} { }

${propsClassName}::${propsClassName}(const ${propsClassName}& other):
${indent(propCopyInitializers.join(',\n'), ' ')} {}
${indent(propCopyInitializers.join(',\n'), ' ')} { }

bool ${propsClassName}::filterObjectKeys(const std::string& propName) {
switch (hashString(propName)) {
12 changes: 8 additions & 4 deletions packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts
Original file line number Diff line number Diff line change
@@ -33,10 +33,14 @@ export function createSwiftHybridViewManager(
)
}

const propAssignments = spec.properties.map(
(p) =>
`swiftPart.${p.cppSetterName}(newViewProps.${escapeCppName(p.name)});`
)
const propAssignments = spec.properties.map((p) => {
const name = escapeCppName(p.name)
return `
if (newViewProps.${name}.isDirty) {
swiftPart.${p.cppSetterName}(newViewProps.${name}.value);
}
`.trim()
})

const mmFile = `
${createFileMetadataString(`${component}.mm`)}
Original file line number Diff line number Diff line change
@@ -66,8 +66,12 @@ - (void) updateProps:(const react::Props::Shared&)props
const auto& newViewProps = *std::static_pointer_cast<HybridTestViewProps const>(props);
NitroImage::HybridTestViewSpec_cxx& swiftPart = _hybridView->getSwiftPart();
// 2. Update each prop
swiftPart.setSomeProp(newViewProps.someProp);
swiftPart.setSomeCallback(newViewProps.someCallback);
if (newViewProps.someProp.isDirty) {
swiftPart.setSomeProp(newViewProps.someProp.value);
}
if (newViewProps.someCallback.isDirty) {
swiftPart.setSomeCallback(newViewProps.someCallback.value);
}
// 3. Continue in base class
[super updateProps:props oldProps:oldProps];
}
Original file line number Diff line number Diff line change
@@ -16,25 +16,23 @@ namespace margelo::nitro::image::views {
const HybridTestViewProps& sourceProps,
const react::RawProps& rawProps):
react::ViewProps(context, sourceProps, rawProps, filterObjectKeys),
/* someProp */ someProp([&]() -> bool {
someProp([&]() -> CachedProp<bool> {
const react::RawValue* rawValue = rawProps.at("someProp", nullptr, nullptr);
if (rawValue == nullptr) { return {}; }
if (rawValue == nullptr) return {};
const auto& [runtime, value] = (std::pair<jsi::Runtime*, const jsi::Value&>)*rawValue;
return JSIConverter<bool>::fromJSI(*runtime, value);
return CachedProp<bool>::fromRawValue(*runtime, value, sourceProps.someProp);
}()),
/* someCallback */ someCallback([&]() -> std::function<void(double /* someParam */)> {
someCallback([&]() -> CachedProp<std::function<void(double /* someParam */)>> {
const react::RawValue* rawValue = rawProps.at("someCallback", nullptr, nullptr);
if (rawValue == nullptr) { return {}; }
if (rawValue == nullptr) return {};
const auto& [runtime, value] = (std::pair<jsi::Runtime*, const jsi::Value&>)*rawValue;
return JSIConverter<std::function<void(double /* someParam */)>>::fromJSI(*runtime, value);
}()) {
// TODO: Instead of eagerly converting each prop, only convert the ones that changed on demand.
}
return CachedProp<std::function<void(double /* someParam */)>>::fromRawValue(*runtime, value, sourceProps.someCallback);
}()) { }

HybridTestViewProps::HybridTestViewProps(const HybridTestViewProps& other):
react::ViewProps(),
someProp(other.someProp),
someCallback(other.someCallback) {}
someCallback(other.someCallback) { }

bool HybridTestViewProps::filterObjectKeys(const std::string& propName) {
switch (hashString(propName)) {
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
#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>
@@ -40,8 +41,8 @@ namespace margelo::nitro::image::views {
const react::RawProps& rawProps);

public:
bool someProp;
std::function<void(double /* someParam */)> someCallback;
CachedProp<bool> someProp;
CachedProp<std::function<void(double /* someParam */)>> someCallback;

private:
static bool filterObjectKeys(const std::string& propName);
1 change: 1 addition & 0 deletions packages/react-native-nitro-modules/NitroModules.podspec
Original file line number Diff line number Diff line change
@@ -44,6 +44,7 @@ Pod::Spec.new do |s|
"cpp/threading/Dispatcher.hpp",
"cpp/utils/NitroHash.hpp",
"cpp/utils/NitroDefines.hpp",
"cpp/views/CachedProp.hpp",
# Public iOS-specific headers that will be exposed in modulemap (for Swift)
"ios/core/ArrayBufferHolder.hpp",
"ios/core/AnyMapHolder.hpp",
1 change: 1 addition & 0 deletions packages/react-native-nitro-modules/cpp/jsi/JSICache.cpp
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@ JSICache::~JSICache() {
Logger::log(LogLevel::Info, TAG, "Destroying JSICache...");
std::unique_lock lock(_mutex);

destroyReferences(_valueCache);
destroyReferences(_objectCache);
destroyReferences(_functionCache);
destroyReferences(_weakObjectCache);
6 changes: 6 additions & 0 deletions packages/react-native-nitro-modules/cpp/jsi/JSICache.hpp
Original file line number Diff line number Diff line change
@@ -59,6 +59,7 @@ class JSICache final : public jsi::NativeState {

private:
std::mutex _mutex;
std::vector<BorrowingReference<jsi::Value>> _valueCache;
std::vector<BorrowingReference<jsi::Object>> _objectCache;
std::vector<BorrowingReference<jsi::Function>> _functionCache;
std::vector<BorrowingReference<jsi::WeakObject>> _weakObjectCache;
@@ -82,6 +83,11 @@ class JSICacheReference final {
}

public:
OwningReference<jsi::Value> makeShared(jsi::Value&& value) {
OwningReference<jsi::Value> owning(new jsi::Value(std::move(value)));
_strongCache->_valueCache.push_back(owning.weak());
return owning;
}
OwningReference<jsi::Object> makeShared(jsi::Object&& value) {
OwningReference<jsi::Object> owning(new jsi::Object(std::move(value)));
_strongCache->_objectCache.push_back(owning.weak());
42 changes: 42 additions & 0 deletions packages/react-native-nitro-modules/cpp/views/CachedProp.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// Created by Marc Rousavy on 30.07.24.
//

#pragma once

#include "NitroDefines.hpp"
#include <jsi/jsi.h>
#include "OwningReference.hpp"
#include "JSIConverter.hpp"

namespace margelo::nitro {

using namespace facebook;

template <typename T>
struct CachedProp {
public:
T value;
OwningReference<jsi::Value> jsiValue;
bool isDirty;

public:
bool equals(jsi::Runtime& runtime, const jsi::Value& other) const {
if (!jsiValue) return false;
return jsi::Value::strictEquals(runtime, *jsiValue, other);
}

public:
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);
auto cache = JSICache::getOrCreateCache(runtime);
auto cached = cache.makeShared(jsi::Value(runtime, value));
return CachedProp<T>(std::move(converted), cached, /* isDirty */ true);
}
};

} // namespace margelo::nitro