@tamagui/native-registry 0.0.0-bootstrap.0 → 3.0.0-beta.643.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/TamaguiRegistry.podspec +32 -0
- package/android/CMakeLists.txt +25 -0
- package/android/build.gradle +83 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/cpp-adapter.cpp +11 -0
- package/android/src/main/java/com/tamagui/registry/TamaguiRegistryPackage.kt +25 -0
- package/cpp/HybridTamaguiRegistry.cpp +369 -0
- package/cpp/HybridTamaguiRegistry.hpp +117 -0
- package/dist/cjs/index.cjs +73 -0
- package/dist/cjs/index.native.cjs +144 -0
- package/dist/cjs/index.native.js +146 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/mirror.cjs +44 -0
- package/dist/cjs/mirror.native.cjs +48 -0
- package/dist/cjs/mirror.native.js +50 -0
- package/dist/cjs/mirror.native.js.map +1 -0
- package/dist/cjs/processStyleColors.cjs +56 -0
- package/dist/cjs/processStyleColors.native.cjs +58 -0
- package/dist/cjs/processStyleColors.native.js +60 -0
- package/dist/cjs/processStyleColors.native.js.map +1 -0
- package/dist/cjs/specs/TamaguiRegistry.nitro.cjs +17 -0
- package/dist/cjs/specs/TamaguiRegistry.nitro.native.cjs +19 -0
- package/dist/cjs/specs/TamaguiRegistry.nitro.native.js +21 -0
- package/dist/cjs/specs/TamaguiRegistry.nitro.native.js.map +1 -0
- package/dist/cjs/types.cjs +17 -0
- package/dist/cjs/types.native.cjs +19 -0
- package/dist/cjs/types.native.js +21 -0
- package/dist/cjs/types.native.js.map +1 -0
- package/dist/esm/index.js +39 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/index.mjs +39 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +107 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/mirror.mjs +17 -0
- package/dist/esm/mirror.mjs.map +1 -0
- package/dist/esm/mirror.native.js +19 -0
- package/dist/esm/mirror.native.js.map +1 -0
- package/dist/esm/processStyleColors.mjs +36 -0
- package/dist/esm/processStyleColors.mjs.map +1 -0
- package/dist/esm/processStyleColors.native.js +36 -0
- package/dist/esm/processStyleColors.native.js.map +1 -0
- package/dist/esm/specs/TamaguiRegistry.nitro.mjs +0 -0
- package/dist/esm/specs/TamaguiRegistry.nitro.native.js +0 -0
- package/dist/esm/types.mjs +0 -0
- package/dist/esm/types.native.js +0 -0
- package/nitro.json +18 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/tamagui/registry/tamaguiregistryOnLoad.kt +35 -0
- package/nitrogen/generated/android/tamaguiregistry+autolinking.cmake +81 -0
- package/nitrogen/generated/android/tamaguiregistry+autolinking.gradle +27 -0
- package/nitrogen/generated/android/tamaguiregistryOnLoad.cpp +49 -0
- package/nitrogen/generated/android/tamaguiregistryOnLoad.hpp +34 -0
- package/nitrogen/generated/ios/TamaguiRegistry+autolinking.rb +62 -0
- package/nitrogen/generated/ios/TamaguiRegistry-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/TamaguiRegistry-Swift-Cxx-Bridge.hpp +27 -0
- package/nitrogen/generated/ios/TamaguiRegistry-Swift-Cxx-Umbrella.hpp +38 -0
- package/nitrogen/generated/ios/TamaguiRegistryAutolinking.mm +35 -0
- package/nitrogen/generated/ios/TamaguiRegistryAutolinking.swift +16 -0
- package/nitrogen/generated/shared/c++/HybridTamaguiRegistrySpec.cpp +27 -0
- package/nitrogen/generated/shared/c++/HybridTamaguiRegistrySpec.hpp +68 -0
- package/package.json +69 -6
- package/src/index.native.ts +174 -0
- package/src/index.ts +79 -0
- package/src/mirror.ts +31 -0
- package/src/processStyleColors.ts +42 -0
- package/src/specs/TamaguiRegistry.nitro.ts +51 -0
- package/src/types.ts +65 -0
- package/types/index.d.ts +20 -0
- package/types/index.d.ts.map +11 -0
- package/types/index.native.d.ts +37 -0
- package/types/index.native.d.ts.map +11 -0
- package/types/mirror.d.ts +7 -0
- package/types/mirror.d.ts.map +11 -0
- package/types/processStyleColors.d.ts +3 -0
- package/types/processStyleColors.d.ts.map +11 -0
- package/types/specs/TamaguiRegistry.nitro.d.ts +49 -0
- package/types/specs/TamaguiRegistry.nitro.d.ts.map +11 -0
- package/types/types.d.ts +61 -0
- package/types/types.d.ts.map +11 -0
package/README.md
CHANGED
|
@@ -1 +1,59 @@
|
|
|
1
1
|
# @tamagui/native-registry
|
|
2
|
+
|
|
3
|
+
`@tamagui/native-registry` is the native engine for Tamagui's experimental
|
|
4
|
+
theme fast path. It links eligible Fabric views to a small C++ registry and
|
|
5
|
+
applies theme changes through one ShadowTree commit without re-rendering those
|
|
6
|
+
views in React.
|
|
7
|
+
|
|
8
|
+
The package supports React Native 0.82 and newer. It uses
|
|
9
|
+
`react-native-nitro-modules`, so adding it requires rebuilding the native app.
|
|
10
|
+
|
|
11
|
+
## Enable the experiment
|
|
12
|
+
|
|
13
|
+
Install both native packages:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
bun add @tamagui/native-registry react-native-nitro-modules
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Enable compiler output in `tamagui.build.ts`:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import type { TamaguiBuildOptions } from 'tamagui'
|
|
23
|
+
|
|
24
|
+
export default {
|
|
25
|
+
config: './tamagui.config.ts',
|
|
26
|
+
components: ['tamagui'],
|
|
27
|
+
experimental: {
|
|
28
|
+
nativeFastPath: true,
|
|
29
|
+
},
|
|
30
|
+
} satisfies TamaguiBuildOptions
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Install the engine before the themed application tree mounts:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import * as nativeRegistry from '@tamagui/native-registry'
|
|
37
|
+
import { setNativeStyleEngine } from 'tamagui'
|
|
38
|
+
|
|
39
|
+
if (nativeRegistry.isAvailable()) {
|
|
40
|
+
setNativeStyleEngine(nativeRegistry)
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then rebuild the iOS and Android applications. A Metro reload cannot add the
|
|
45
|
+
native module to an existing binary.
|
|
46
|
+
|
|
47
|
+
This API is experimental. Keep the flag off for production applications unless
|
|
48
|
+
you can run the native correctness suite for the React Native versions and list
|
|
49
|
+
implementations your application ships.
|
|
50
|
+
|
|
51
|
+
## Fallback behavior
|
|
52
|
+
|
|
53
|
+
The flag defaults to `false`. With the flag off, native compiler output is
|
|
54
|
+
unchanged. With the flag on but no engine installed, emitted components use the
|
|
55
|
+
existing theme hook and React render path. Web output is unchanged in both
|
|
56
|
+
cases.
|
|
57
|
+
|
|
58
|
+
The registry coexists with Tamagui's existing `DynamicColorIOS` optimization.
|
|
59
|
+
It does not replace or remove that path.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "TamaguiRegistry"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["repository"]["url"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = "Tamagui"
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => package["repository"]["url"], :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = [
|
|
17
|
+
"cpp/**/*.{h,hpp,cpp}",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
s.pod_target_xcconfig = {
|
|
21
|
+
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
|
|
22
|
+
"GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES FOLLY_MOBILE",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# nitrogen autolinking (adds NitroModules dep + generated sources)
|
|
26
|
+
load "nitrogen/generated/ios/TamaguiRegistry+autolinking.rb"
|
|
27
|
+
add_nitrogen_files(s)
|
|
28
|
+
|
|
29
|
+
s.dependency "React-jsi"
|
|
30
|
+
s.dependency "React-callinvoker"
|
|
31
|
+
install_modules_dependencies(s)
|
|
32
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
project(TamaguiRegistry)
|
|
2
|
+
cmake_minimum_required(VERSION 3.9.0)
|
|
3
|
+
|
|
4
|
+
set(PACKAGE_NAME tamaguiregistry)
|
|
5
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
6
|
+
|
|
7
|
+
add_library(${PACKAGE_NAME} SHARED
|
|
8
|
+
src/main/cpp/cpp-adapter.cpp
|
|
9
|
+
../cpp/HybridTamaguiRegistry.cpp
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# nitrogen autolinking (generated sources + NitroModules linkage)
|
|
13
|
+
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/tamaguiregistry+autolinking.cmake)
|
|
14
|
+
|
|
15
|
+
include_directories(
|
|
16
|
+
"../cpp"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
find_library(LOG_LIB log)
|
|
20
|
+
|
|
21
|
+
target_link_libraries(
|
|
22
|
+
${PACKAGE_NAME}
|
|
23
|
+
${LOG_LIB}
|
|
24
|
+
android
|
|
25
|
+
)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.safeExtGet = { prop, fallback ->
|
|
3
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath("com.android.tools.build:gradle:8.1.1")
|
|
13
|
+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet('kotlinVersion', '1.9.0')}")
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
apply plugin: "com.android.library"
|
|
18
|
+
apply plugin: "org.jetbrains.kotlin.android"
|
|
19
|
+
|
|
20
|
+
// nitrogen autolinking
|
|
21
|
+
apply from: "${project.projectDir}/../nitrogen/generated/android/tamaguiregistry+autolinking.gradle"
|
|
22
|
+
|
|
23
|
+
def safeExtGet(prop, fallback) {
|
|
24
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
android {
|
|
28
|
+
namespace = "com.tamagui.registry"
|
|
29
|
+
compileSdkVersion safeExtGet('compileSdkVersion', 35)
|
|
30
|
+
|
|
31
|
+
defaultConfig {
|
|
32
|
+
minSdkVersion safeExtGet('minSdkVersion', 24)
|
|
33
|
+
targetSdkVersion safeExtGet('targetSdkVersion', 35)
|
|
34
|
+
|
|
35
|
+
externalNativeBuild {
|
|
36
|
+
cmake {
|
|
37
|
+
cppFlags "-O2 -frtti -fexceptions -Wall -fstack-protector-all"
|
|
38
|
+
arguments "-DANDROID_STL=c++_shared"
|
|
39
|
+
abiFilters(*(rootProject.getProperties().get("reactNativeArchitectures")?.split(",")
|
|
40
|
+
?: ["x86", "x86_64", "armeabi-v7a", "arm64-v8a"]))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
externalNativeBuild {
|
|
46
|
+
cmake {
|
|
47
|
+
path "CMakeLists.txt"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
buildFeatures {
|
|
52
|
+
prefab true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// same exclusion list as react-native-nitro-modules: React Native's own AAR
|
|
56
|
+
// already ships every one of these, and packaging a second copy fails the
|
|
57
|
+
// app's mergeDebugAndroidTestNativeLibs with a duplicate-path error
|
|
58
|
+
packagingOptions {
|
|
59
|
+
excludes = [
|
|
60
|
+
"META-INF",
|
|
61
|
+
"META-INF/**",
|
|
62
|
+
"**/libc++_shared.so",
|
|
63
|
+
"**/libfbjni.so",
|
|
64
|
+
"**/libjsi.so",
|
|
65
|
+
"**/libfolly_json.so",
|
|
66
|
+
"**/libfolly_runtime.so",
|
|
67
|
+
"**/libglog.so",
|
|
68
|
+
"**/libhermes.so",
|
|
69
|
+
"**/libhermes-executor-debug.so",
|
|
70
|
+
"**/libhermes_executor.so",
|
|
71
|
+
"**/libreactnative.so",
|
|
72
|
+
"**/libreactnativejni.so",
|
|
73
|
+
"**/libturbomodulejsijni.so",
|
|
74
|
+
"**/libreact_nativemodule_core.so",
|
|
75
|
+
"**/libjscexecutor.so",
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
dependencies {
|
|
81
|
+
implementation 'com.facebook.react:react-native:+'
|
|
82
|
+
implementation project(':react-native-nitro-modules')
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#include <fbjni/fbjni.h>
|
|
2
|
+
#include <jni.h>
|
|
3
|
+
|
|
4
|
+
#include "tamaguiregistryOnLoad.hpp"
|
|
5
|
+
|
|
6
|
+
// called by System.loadLibrary("tamaguiregistry"); registers all nitro hybrids
|
|
7
|
+
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
|
|
8
|
+
return facebook::jni::initialize(vm, []() {
|
|
9
|
+
margelo::nitro::tamagui::registry::registerAllNatives();
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package com.tamagui.registry
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
7
|
+
import com.margelo.nitro.tamagui.registry.tamaguiregistryOnLoad
|
|
8
|
+
|
|
9
|
+
// no react modules: this package exists so autolinking loads the C++ library,
|
|
10
|
+
// which registers the TamaguiRegistry nitro hybrid on JNI_OnLoad
|
|
11
|
+
class TamaguiRegistryPackage : BaseReactPackage() {
|
|
12
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
17
|
+
return ReactModuleInfoProvider { HashMap() }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
companion object {
|
|
21
|
+
init {
|
|
22
|
+
tamaguiregistryOnLoad.initializeNative()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
#include "HybridTamaguiRegistry.hpp"
|
|
2
|
+
|
|
3
|
+
#include <jsi/JSIDynamic.h>
|
|
4
|
+
#include <react/renderer/components/root/RootShadowNode.h>
|
|
5
|
+
#include <react/renderer/core/PropsParserContext.h>
|
|
6
|
+
#include <react/renderer/core/RawProps.h>
|
|
7
|
+
#include <react/renderer/core/ShadowNode.h>
|
|
8
|
+
#include <react/renderer/core/ShadowNodeFragment.h>
|
|
9
|
+
#include <react/renderer/mounting/ShadowTree.h>
|
|
10
|
+
#include <react/renderer/mounting/ShadowTreeRegistry.h>
|
|
11
|
+
#include <react/renderer/uimanager/UIManager.h>
|
|
12
|
+
#include <react/renderer/uimanager/UIManagerBinding.h>
|
|
13
|
+
#include <react/renderer/uimanager/primitives.h>
|
|
14
|
+
|
|
15
|
+
#include <unordered_set>
|
|
16
|
+
|
|
17
|
+
namespace margelo::nitro::tamagui::registry {
|
|
18
|
+
|
|
19
|
+
using namespace facebook::react;
|
|
20
|
+
using margelo::nitro::Prototype;
|
|
21
|
+
|
|
22
|
+
void HybridTamaguiRegistry::loadHybridMethods() {
|
|
23
|
+
HybridTamaguiRegistrySpec::loadHybridMethods();
|
|
24
|
+
registerHybrids(this, [](Prototype& prototype) {
|
|
25
|
+
prototype.registerRawHybridMethod("link", 3, &HybridTamaguiRegistry::link);
|
|
26
|
+
prototype.registerRawHybridMethod("applyViewStates", 1,
|
|
27
|
+
&HybridTamaguiRegistry::applyViewStates);
|
|
28
|
+
prototype.registerRawHybridMethod(
|
|
29
|
+
"updateViewStateTables", 1,
|
|
30
|
+
&HybridTamaguiRegistry::updateViewStateTables);
|
|
31
|
+
prototype.registerRawHybridMethod("getViewState", 1,
|
|
32
|
+
&HybridTamaguiRegistry::getViewState);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
jsi::Value HybridTamaguiRegistry::link(jsi::Runtime& rt, const jsi::Value&,
|
|
37
|
+
const jsi::Value* args, size_t count) {
|
|
38
|
+
if (count < 3) {
|
|
39
|
+
throw jsi::JSError(rt, "link(shadowNode, slots, scopeId) requires 3 arguments");
|
|
40
|
+
}
|
|
41
|
+
if (!args[0].isObject()) return jsi::Value::undefined();
|
|
42
|
+
auto obj = args[0].asObject(rt);
|
|
43
|
+
if (!obj.hasNativeState(rt)) return jsi::Value::undefined();
|
|
44
|
+
auto wrapper = obj.getNativeState<ShadowNodeWrapper>(rt);
|
|
45
|
+
if (!wrapper || !wrapper->shadowNode) return jsi::Value::undefined();
|
|
46
|
+
|
|
47
|
+
runtime_ = &rt;
|
|
48
|
+
|
|
49
|
+
auto slots = jsi::dynamicFromValue(rt, args[1]);
|
|
50
|
+
const double id = nextId_++;
|
|
51
|
+
views_[id] = LinkedView{
|
|
52
|
+
wrapper->shadowNode,
|
|
53
|
+
args[2].asString(rt).utf8(rt),
|
|
54
|
+
slots.getDefault("base", nullptr),
|
|
55
|
+
slots.getDefault("state", nullptr),
|
|
56
|
+
};
|
|
57
|
+
return jsi::Value(id);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
void HybridTamaguiRegistry::unlink(double id) {
|
|
61
|
+
views_.erase(id);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const std::string* HybridTamaguiRegistry::activeStateName(
|
|
65
|
+
const std::string& scopeId) const {
|
|
66
|
+
if (auto it = scopeStates_.find(scopeId); it != scopeStates_.end()) {
|
|
67
|
+
return &it->second;
|
|
68
|
+
}
|
|
69
|
+
if (!scopeId.empty()) {
|
|
70
|
+
if (auto root = scopeStates_.find(""); root != scopeStates_.end()) {
|
|
71
|
+
return &root->second;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return nullptr;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
void HybridTamaguiRegistry::setStateName(const std::string& scopeId,
|
|
78
|
+
const std::string& stateName) {
|
|
79
|
+
auto it = scopeStates_.find(scopeId);
|
|
80
|
+
if (it != scopeStates_.end() && it->second == stateName) return;
|
|
81
|
+
scopeStates_[scopeId] = stateName;
|
|
82
|
+
if (runtime_) {
|
|
83
|
+
// a root change also affects scoped views inheriting root, so commit all
|
|
84
|
+
applyUpdates(*runtime_, scopeId.empty() ? nullptr : &scopeId);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
std::string HybridTamaguiRegistry::getStateName(const std::string& scopeId) {
|
|
89
|
+
const auto* name = activeStateName(scopeId);
|
|
90
|
+
return name ? *name : "";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
void HybridTamaguiRegistry::removeScope(const std::string& scopeId) {
|
|
94
|
+
scopeStates_.erase(scopeId);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
double HybridTamaguiRegistry::getViewCount() {
|
|
98
|
+
return static_cast<double>(views_.size());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
double HybridTamaguiRegistry::getCommitCount() {
|
|
102
|
+
return commitCount_;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
double HybridTamaguiRegistry::getMissCount() {
|
|
106
|
+
return missCount_;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── In-transaction tree cloning ──
|
|
110
|
+
//
|
|
111
|
+
// RN's UIManager::updateShadowTree (0.81+) pre-builds its clone from a
|
|
112
|
+
// revision read before the commit and returns it from the commit callback
|
|
113
|
+
// unchanged, so a commit that lands in between (a same-tick React render,
|
|
114
|
+
// a Reanimated frame) makes it commit a stale-based tree: updates get lost
|
|
115
|
+
// or revert. Phase 0 reproduced exactly that. So the engine does its own
|
|
116
|
+
// ShadowTree::commit and builds the clone INSIDE the transaction from the
|
|
117
|
+
// root the callback receives; on retry the clone is rebuilt from the fresh
|
|
118
|
+
// root and the race disappears.
|
|
119
|
+
|
|
120
|
+
namespace {
|
|
121
|
+
|
|
122
|
+
using LeafUpdates = HybridTamaguiRegistry::LeafUpdates;
|
|
123
|
+
using AffectedNodes =
|
|
124
|
+
std::unordered_map<const ShadowNodeFamily*, std::unordered_set<size_t>>;
|
|
125
|
+
|
|
126
|
+
AffectedNodes findAffectedNodes(const RootShadowNode& root,
|
|
127
|
+
const LeafUpdates& updates) {
|
|
128
|
+
AffectedNodes affected;
|
|
129
|
+
for (const auto& [family, props] : updates) {
|
|
130
|
+
// families of unmounted views have no ancestors in this root: skipped
|
|
131
|
+
for (const auto& [parentNode, childIndex] : family->getAncestors(root)) {
|
|
132
|
+
affected[&parentNode.get().getFamily()].insert(childIndex);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return affected;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
std::shared_ptr<ShadowNode> cloneSubtree(const ShadowNode& node,
|
|
139
|
+
const LeafUpdates& updates,
|
|
140
|
+
const AffectedNodes& affected) {
|
|
141
|
+
const auto* family = &node.getFamily();
|
|
142
|
+
auto children = node.getChildren();
|
|
143
|
+
|
|
144
|
+
if (auto it = affected.find(family); it != affected.end()) {
|
|
145
|
+
for (auto index : it->second) {
|
|
146
|
+
children[index] = cloneSubtree(*children[index], updates, affected);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
Props::Shared newProps = ShadowNodeFragment::propsPlaceholder();
|
|
151
|
+
if (auto it = updates.find(family); it != updates.end()) {
|
|
152
|
+
PropsParserContext ctx{node.getSurfaceId(), *node.getContextContainer()};
|
|
153
|
+
newProps = node.getComponentDescriptor().cloneProps(ctx, node.getProps(),
|
|
154
|
+
RawProps(it->second));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return node.clone({
|
|
158
|
+
.props = newProps,
|
|
159
|
+
.children = std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>(
|
|
160
|
+
std::move(children)),
|
|
161
|
+
.state = node.getState(),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
} // namespace
|
|
166
|
+
|
|
167
|
+
// merge base + the named state's props for one view into `out`, syncing the
|
|
168
|
+
// family's native props so React re-commits of unchanged children merge our
|
|
169
|
+
// values instead of resurrecting a stale absorbed node (phase 0: the JS
|
|
170
|
+
// mirror alone provably does not cover this). returns false on a state miss.
|
|
171
|
+
bool HybridTamaguiRegistry::buildViewUpdate(LinkedView& view,
|
|
172
|
+
const std::string& stateName,
|
|
173
|
+
LeafUpdates& out) {
|
|
174
|
+
folly::dynamic props =
|
|
175
|
+
view.baseProps.isObject() ? view.baseProps : folly::dynamic::object();
|
|
176
|
+
|
|
177
|
+
if (view.stateProps.isObject()) {
|
|
178
|
+
auto it = view.stateProps.find(stateName);
|
|
179
|
+
if (it == view.stateProps.items().end()) {
|
|
180
|
+
missCount_ += 1;
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
if (props.empty()) {
|
|
184
|
+
props = it->second;
|
|
185
|
+
} else {
|
|
186
|
+
props.update(it->second);
|
|
187
|
+
}
|
|
188
|
+
} else if (props.empty()) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const auto& family = view.node->getFamily();
|
|
193
|
+
if (family.nativeProps_DEPRECATED) {
|
|
194
|
+
family.nativeProps_DEPRECATED->update(props);
|
|
195
|
+
} else {
|
|
196
|
+
family.nativeProps_DEPRECATED = std::make_unique<folly::dynamic>(props);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// a null value means reset-to-default (a style key dropped by a media
|
|
200
|
+
// change): it must COMMIT as null so RawProps clears the prop, but must not
|
|
201
|
+
// stick in nativeProps_DEPRECATED or every future React render would keep
|
|
202
|
+
// re-clearing a key the render may now legitimately set
|
|
203
|
+
auto& sticky = *family.nativeProps_DEPRECATED;
|
|
204
|
+
for (const auto& item : props.items()) {
|
|
205
|
+
if (item.second.isNull()) sticky.erase(item.first.asString());
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// the retained node keeps its family alive; unmounted views resolve to
|
|
209
|
+
// no ancestors during the transaction and are skipped safely
|
|
210
|
+
out[&family] = std::move(props);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
void HybridTamaguiRegistry::commitUpdates(jsi::Runtime& rt,
|
|
215
|
+
LeafUpdates& updates) {
|
|
216
|
+
if (updates.empty()) return;
|
|
217
|
+
|
|
218
|
+
auto binding = UIManagerBinding::getBinding(rt);
|
|
219
|
+
if (!binding) return;
|
|
220
|
+
|
|
221
|
+
binding->getUIManager().getShadowTreeRegistry().enumerate(
|
|
222
|
+
[this, &updates](const ShadowTree& tree, bool& /*stop*/) {
|
|
223
|
+
auto status = tree.commit(
|
|
224
|
+
[&updates](const RootShadowNode& oldRoot)
|
|
225
|
+
-> RootShadowNode::Unshared {
|
|
226
|
+
auto affected = findAffectedNodes(oldRoot, updates);
|
|
227
|
+
if (affected.empty()) return nullptr;
|
|
228
|
+
return std::static_pointer_cast<RootShadowNode>(
|
|
229
|
+
cloneSubtree(oldRoot, updates, affected));
|
|
230
|
+
},
|
|
231
|
+
{.enableStateReconciliation = false, .mountSynchronously = true});
|
|
232
|
+
if (status == ShadowTree::CommitStatus::Succeeded) {
|
|
233
|
+
commitCount_ += 1;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
void HybridTamaguiRegistry::applyUpdates(jsi::Runtime& rt,
|
|
239
|
+
const std::string* scopeFilter) {
|
|
240
|
+
LeafUpdates updates;
|
|
241
|
+
updates.reserve(views_.size());
|
|
242
|
+
|
|
243
|
+
for (auto& [id, view] : views_) {
|
|
244
|
+
if (scopeFilter && view.scopeId != *scopeFilter) continue;
|
|
245
|
+
// runtime-managed views (applyViewStates) resolve state per view; scope
|
|
246
|
+
// broadcasts must not fight their controller
|
|
247
|
+
if (!view.activeState.empty()) continue;
|
|
248
|
+
|
|
249
|
+
const auto* name =
|
|
250
|
+
view.stateProps.isObject() ? activeStateName(view.scopeId) : nullptr;
|
|
251
|
+
if (view.stateProps.isObject() && !name) continue;
|
|
252
|
+
|
|
253
|
+
buildViewUpdate(view, name ? *name : "", updates);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
commitUpdates(rt, updates);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
jsi::Value HybridTamaguiRegistry::applyViewStates(jsi::Runtime& rt,
|
|
260
|
+
const jsi::Value&,
|
|
261
|
+
const jsi::Value* args,
|
|
262
|
+
size_t count) {
|
|
263
|
+
if (count < 1 || !args[0].isObject()) {
|
|
264
|
+
throw jsi::JSError(rt, "applyViewStates(entries) requires an array");
|
|
265
|
+
}
|
|
266
|
+
runtime_ = &rt;
|
|
267
|
+
|
|
268
|
+
auto entries = jsi::dynamicFromValue(rt, args[0]);
|
|
269
|
+
if (!entries.isArray()) {
|
|
270
|
+
throw jsi::JSError(rt, "applyViewStates(entries) requires an array");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
LeafUpdates updates;
|
|
274
|
+
updates.reserve(entries.size());
|
|
275
|
+
|
|
276
|
+
for (auto& entry : entries) {
|
|
277
|
+
if (!entry.isObject()) continue;
|
|
278
|
+
auto idIt = entry.find("id");
|
|
279
|
+
auto stateIt = entry.find("state");
|
|
280
|
+
if (idIt == entry.items().end() || stateIt == entry.items().end()) continue;
|
|
281
|
+
|
|
282
|
+
auto viewIt = views_.find(idIt->second.asDouble());
|
|
283
|
+
// unlinked between the JS pass and this call: stale entry, not a miss
|
|
284
|
+
if (viewIt == views_.end()) continue;
|
|
285
|
+
auto& view = viewIt->second;
|
|
286
|
+
|
|
287
|
+
const std::string stateName = stateIt->second.asString();
|
|
288
|
+
auto props = entry.getDefault("props", nullptr);
|
|
289
|
+
if (props.isObject()) {
|
|
290
|
+
if (!view.stateProps.isObject()) {
|
|
291
|
+
view.stateProps = folly::dynamic::object();
|
|
292
|
+
}
|
|
293
|
+
view.stateProps[stateName] = std::move(props);
|
|
294
|
+
}
|
|
295
|
+
view.activeState = stateName;
|
|
296
|
+
|
|
297
|
+
buildViewUpdate(view, stateName, updates);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
commitUpdates(rt, updates);
|
|
301
|
+
return jsi::Value::undefined();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
jsi::Value HybridTamaguiRegistry::updateViewStateTables(
|
|
305
|
+
jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args,
|
|
306
|
+
size_t count) {
|
|
307
|
+
if (count < 1 || !args[0].isObject()) {
|
|
308
|
+
throw jsi::JSError(rt, "updateViewStateTables(entries) requires an array");
|
|
309
|
+
}
|
|
310
|
+
runtime_ = &rt;
|
|
311
|
+
|
|
312
|
+
auto entries = jsi::dynamicFromValue(rt, args[0]);
|
|
313
|
+
if (!entries.isArray()) {
|
|
314
|
+
throw jsi::JSError(rt, "updateViewStateTables(entries) requires an array");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
LeafUpdates updates;
|
|
318
|
+
updates.reserve(entries.size());
|
|
319
|
+
for (auto& entry : entries) {
|
|
320
|
+
if (!entry.isObject()) continue;
|
|
321
|
+
auto idIt = entry.find("id");
|
|
322
|
+
auto stateIt = entry.find("state");
|
|
323
|
+
auto propsIt = entry.find("props");
|
|
324
|
+
if (idIt == entry.items().end() || stateIt == entry.items().end() ||
|
|
325
|
+
propsIt == entry.items().end() || !propsIt->second.isObject()) {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
auto viewIt = views_.find(idIt->second.asDouble());
|
|
330
|
+
if (viewIt == views_.end()) continue;
|
|
331
|
+
auto& view = viewIt->second;
|
|
332
|
+
const std::string stateName = stateIt->second.asString();
|
|
333
|
+
if (!view.stateProps.isObject()) {
|
|
334
|
+
view.stateProps = folly::dynamic::object();
|
|
335
|
+
}
|
|
336
|
+
view.stateProps[stateName] = propsIt->second;
|
|
337
|
+
|
|
338
|
+
const auto* activeName = view.activeState.empty()
|
|
339
|
+
? activeStateName(view.scopeId)
|
|
340
|
+
: &view.activeState;
|
|
341
|
+
if (activeName && *activeName == stateName) {
|
|
342
|
+
buildViewUpdate(view, stateName, updates);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
commitUpdates(rt, updates);
|
|
347
|
+
return jsi::Value::undefined();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
jsi::Value HybridTamaguiRegistry::getViewState(jsi::Runtime& rt,
|
|
351
|
+
const jsi::Value&,
|
|
352
|
+
const jsi::Value* args,
|
|
353
|
+
size_t count) {
|
|
354
|
+
if (count < 1 || !args[0].isNumber()) return jsi::Value::null();
|
|
355
|
+
auto it = views_.find(args[0].asNumber());
|
|
356
|
+
if (it == views_.end()) return jsi::Value::null();
|
|
357
|
+
const auto& view = it->second;
|
|
358
|
+
|
|
359
|
+
folly::dynamic out = folly::dynamic::object("scopeId", view.scopeId)(
|
|
360
|
+
"activeState", view.activeState)("base", view.baseProps)(
|
|
361
|
+
"states", view.stateProps);
|
|
362
|
+
const auto& family = view.node->getFamily();
|
|
363
|
+
out["nativeProps"] = family.nativeProps_DEPRECATED
|
|
364
|
+
? *family.nativeProps_DEPRECATED
|
|
365
|
+
: folly::dynamic(nullptr);
|
|
366
|
+
return jsi::valueFromDynamic(rt, out);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
} // namespace margelo::nitro::tamagui::registry
|