react-native-unistyles 3.3.0 → 3.4.0

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.
Files changed (47) hide show
  1. package/README.md +3 -3
  2. package/cxx/core/UnistylesRegistry.cpp +135 -47
  3. package/cxx/core/UnistylesRegistry.h +23 -3
  4. package/cxx/hybridObjects/HybridShadowRegistry.cpp +4 -7
  5. package/cxx/parser/Parser.cpp +1 -2
  6. package/cxx/shadowTree/NativeProps.h +44 -0
  7. package/cxx/shadowTree/ShadowLeafUpdate.h +8 -0
  8. package/cxx/shadowTree/ShadowTrafficController.h +16 -37
  9. package/cxx/shadowTree/ShadowTreeManager.cpp +8 -148
  10. package/cxx/shadowTree/ShadowTreeManager.h +1 -5
  11. package/lib/commonjs/components/native/Animated.js +2 -0
  12. package/lib/commonjs/components/native/Animated.js.map +1 -1
  13. package/lib/commonjs/components/native/Pressable.js +2 -0
  14. package/lib/commonjs/components/native/Pressable.js.map +1 -1
  15. package/lib/commonjs/components/native/Pressable.native.js +2 -0
  16. package/lib/commonjs/components/native/Pressable.native.js.map +1 -1
  17. package/lib/commonjs/web/convert/object/boxShadow.js +2 -2
  18. package/lib/commonjs/web/convert/object/boxShadow.js.map +1 -1
  19. package/lib/commonjs/web/index.js.map +1 -1
  20. package/lib/module/components/native/Animated.js +3 -0
  21. package/lib/module/components/native/Animated.js.map +1 -1
  22. package/lib/module/components/native/Pressable.js +2 -0
  23. package/lib/module/components/native/Pressable.js.map +1 -1
  24. package/lib/module/components/native/Pressable.native.js +2 -0
  25. package/lib/module/components/native/Pressable.native.js.map +1 -1
  26. package/lib/module/web/convert/object/boxShadow.js +2 -2
  27. package/lib/module/web/convert/object/boxShadow.js.map +1 -1
  28. package/lib/module/web/index.js.map +1 -1
  29. package/lib/typescript/src/components/native/Animated.d.ts +16 -36
  30. package/lib/typescript/src/components/native/Animated.d.ts.map +1 -1
  31. package/lib/typescript/src/components/native/Pressable.d.ts +4 -2
  32. package/lib/typescript/src/components/native/Pressable.d.ts.map +1 -1
  33. package/lib/typescript/src/components/native/Pressable.native.d.ts +5 -2
  34. package/lib/typescript/src/components/native/Pressable.native.d.ts.map +1 -1
  35. package/lib/typescript/src/types/core.d.ts +7 -2
  36. package/lib/typescript/src/types/core.d.ts.map +1 -1
  37. package/lib/typescript/src/web/index.d.ts.map +1 -1
  38. package/nitrogen/generated/android/kotlin/com/margelo/nitro/unistyles/Func_void_UnistylesNativeMiniRuntime.kt +0 -2
  39. package/nitrogen/generated/android/kotlin/com/margelo/nitro/unistyles/Func_void_std__vector_UnistyleDependency__UnistylesNativeMiniRuntime.kt +0 -2
  40. package/nitrogen/generated/android/kotlin/com/margelo/nitro/unistyles/HybridNativePlatformSpec.kt +2 -0
  41. package/package.json +27 -26
  42. package/src/components/native/Animated.tsx +18 -1
  43. package/src/components/native/Pressable.native.tsx +10 -2
  44. package/src/components/native/Pressable.tsx +7 -1
  45. package/src/types/core.ts +7 -28
  46. package/src/web/convert/object/boxShadow.ts +2 -2
  47. package/src/web/index.ts +2 -1
package/README.md CHANGED
@@ -58,9 +58,6 @@ Then follow [installation guides](https://unistyl.es/v3/start/getting-started) f
58
58
  <a href="https://codemask.com">
59
59
  <img src="https://avatars.githubusercontent.com/u/51229884?s=200&v=4" height="70px" width="70px" alt="codemask" />
60
60
  </a>
61
- <a href="https://galaxies.dev">
62
- <img src="https://avatars.githubusercontent.com/u/118431096?s=200&v=4" height="70px" width="70px" alt="galaxies-dev" />
63
- </a>
64
61
  <a href="https://github.com/ryanlanciaux">
65
62
  <img src="https://avatars.githubusercontent.com/u/85041?v=4" height="70px" width="70px" alt="ryanlanciaux" />
66
63
  </a>
@@ -155,6 +152,9 @@ Then follow [installation guides](https://unistyl.es/v3/start/getting-started) f
155
152
  <a href="https://github.com/AdiRishi">
156
153
  <img src="https://avatars.githubusercontent.com/u/8351234?v=4" height="60px" width="60px" alt="AdiRishi" />
157
154
  </a>
155
+ <a href="https://galaxies.dev">
156
+ <img src="https://avatars.githubusercontent.com/u/118431096?s=200&v=4" height="60px" width="60px" alt="galaxies-dev" />
157
+ </a>
158
158
 
159
159
  ## Sponsor my work
160
160
 
@@ -1,6 +1,7 @@
1
1
  #include "UnistylesRegistry.h"
2
2
  #include "UnistylesState.h"
3
3
  #include "Parser.h"
4
+ #include "NativeProps.h"
4
5
 
5
6
  using namespace margelo::nitro::unistyles;
6
7
  using namespace facebook;
@@ -60,37 +61,85 @@ void core::UnistylesRegistry::updateTheme(jsi::Runtime& rt, std::string& themeNa
60
61
 
61
62
  void core::UnistylesRegistry::linkShadowNodeWithUnistyle(
62
63
  jsi::Runtime& rt,
63
- const ShadowNodeFamily* shadowNodeFamily,
64
+ const std::shared_ptr<const ShadowNodeFamily>& shadowNodeFamily,
64
65
  std::vector<std::shared_ptr<UnistyleData>>& unistylesData,
65
66
  std::optional<folly::dynamic> initialScopedUpdate
66
67
  ) {
67
- this->trafficController.withLock([this, &rt, &unistylesData, shadowNodeFamily, &initialScopedUpdate](){
68
+ // released after unlocking, so a family destructor never runs within the lock
69
+ ReleasedFamilies releasedFamilies;
70
+
71
+ this->trafficController.withLock([this, &unistylesData, &shadowNodeFamily, &initialScopedUpdate, &releasedFamilies](){
72
+ auto family = shadowNodeFamily.get();
73
+ auto it = this->_shadowRegistry.find(family);
74
+
68
75
  // Clear suspension state if this family was previously suspended
69
- if (_suspendedFamilies.erase(shadowNodeFamily) > 0) {
70
- auto* mutableFamily = const_cast<ShadowNodeFamily*>(shadowNodeFamily);
71
- mutableFamily->nativeProps_DEPRECATED.reset();
76
+ if (it != this->_shadowRegistry.end() && it->second.isSuspended) {
77
+ shadow::resetNativeProps(family);
72
78
  // Clear old registry entries to prevent stale UnistyleData accumulation
73
- this->_shadowRegistry.erase(shadowNodeFamily);
74
- // Remove any stale traffic controller entry (e.g. from a theme change during suspension)
75
- this->trafficController.removeShadowNode(shadowNodeFamily);
79
+ // and any stale traffic controller entry (e.g. from a theme change during suspension)
80
+ this->forgetFamilyUnsafe(family, releasedFamilies);
76
81
  }
77
82
 
78
- std::for_each(unistylesData.begin(), unistylesData.end(), [this, shadowNodeFamily](std::shared_ptr<UnistyleData> unistyleData){
79
- this->_shadowRegistry[shadowNodeFamily].emplace_back(unistyleData);
80
- });
83
+ auto& linkedFamily = this->_shadowRegistry[family];
84
+
85
+ linkedFamily.family = shadowNodeFamily;
86
+ linkedFamily.unistyles.insert(linkedFamily.unistyles.end(), unistylesData.begin(), unistylesData.end());
81
87
 
82
88
  // Required for scoped themes to apply on initial mount
83
89
  if (initialScopedUpdate.has_value()) {
84
- shadow::ShadowLeafUpdates updates;
90
+ this->trafficController.setUpdate(shadowNodeFamily, std::move(*initialScopedUpdate));
91
+ }
85
92
 
86
- updates.emplace(shadowNodeFamily, std::move(*initialScopedUpdate));
87
- this->trafficController.setUpdates(updates);
93
+ // families unmounted without unlink (eg. while frozen) are pinned by us only
94
+ // sweep them once the registry doubles, so it's amortized O(1) per link
95
+ if (this->_shadowRegistry.size() >= this->_sweepThreshold) {
96
+ this->sweepUnownedFamiliesUnsafe(releasedFamilies);
97
+ this->_sweepThreshold = std::max(MIN_SWEEP_THRESHOLD, this->_shadowRegistry.size() * 2);
88
98
  }
89
99
  });
90
100
  }
91
101
 
102
+ bool core::UnistylesRegistry::isOwnedOnlyByUnistyles(const LinkedFamily& linkedFamily) noexcept {
103
+ // shadow nodes own their family, so if nothing but our pin owns it, the family is not part of any
104
+ // shadow tree anymore. React Native can't get it back, as it only keeps weak references beside shadow nodes
105
+ return linkedFamily.family.use_count() == 1;
106
+ }
107
+
108
+ void core::UnistylesRegistry::forgetFamilyUnsafe(const ShadowNodeFamily* shadowNodeFamily, ReleasedFamilies& releasedFamilies) {
109
+ auto it = this->_shadowRegistry.find(shadowNodeFamily);
110
+
111
+ if (it != this->_shadowRegistry.end()) {
112
+ releasedFamilies.emplace_back(std::move(it->second.family));
113
+ this->_shadowRegistry.erase(it);
114
+ }
115
+
116
+ if (auto pendingFamily = this->trafficController.removeShadowNode(shadowNodeFamily)) {
117
+ releasedFamilies.emplace_back(std::move(pendingFamily));
118
+ }
119
+ }
120
+
121
+ void core::UnistylesRegistry::sweepUnownedFamiliesUnsafe(ReleasedFamilies& releasedFamilies) {
122
+ std::vector<const ShadowNodeFamily*> unownedFamilies;
123
+
124
+ for (const auto& [family, linkedFamily] : this->_shadowRegistry) {
125
+ if (isOwnedOnlyByUnistyles(linkedFamily)) {
126
+ unownedFamilies.emplace_back(family);
127
+ }
128
+ }
129
+
130
+ for (const auto* family : unownedFamilies) {
131
+ this->forgetFamilyUnsafe(family, releasedFamilies);
132
+ }
133
+ }
134
+
92
135
  void core::UnistylesRegistry::removeDuplicatedUnistyles(const ShadowNodeFamily *shadowNodeFamily, std::vector<core::Unistyle::Shared>& unistyles) {
93
- auto targetFamilyUnistyles = this->_shadowRegistry[shadowNodeFamily];
136
+ auto it = this->_shadowRegistry.find(shadowNodeFamily);
137
+
138
+ if (it == this->_shadowRegistry.end()) {
139
+ return;
140
+ }
141
+
142
+ auto& targetFamilyUnistyles = it->second.unistyles;
94
143
 
95
144
  unistyles.erase(
96
145
  std::remove_if(
@@ -111,23 +160,27 @@ void core::UnistylesRegistry::removeDuplicatedUnistyles(const ShadowNodeFamily *
111
160
  }
112
161
 
113
162
  void core::UnistylesRegistry::unlinkShadowNodeWithUnistyles(const ShadowNodeFamily* shadowNodeFamily) {
114
- this->trafficController.withLock([this, shadowNodeFamily](){
115
- this->_shadowRegistry.erase(shadowNodeFamily);
116
- this->_suspendedFamilies.erase(shadowNodeFamily);
117
- this->trafficController.removeShadowNode(shadowNodeFamily);
163
+ ReleasedFamilies releasedFamilies;
164
+
165
+ this->trafficController.withLock([this, shadowNodeFamily, &releasedFamilies](){
166
+ this->forgetFamilyUnsafe(shadowNodeFamily, releasedFamilies);
118
167
  });
119
168
  }
120
169
 
121
170
  void core::UnistylesRegistry::suspendShadowNode(const ShadowNodeFamily* shadowNodeFamily) {
122
171
  this->trafficController.withLock([this, shadowNodeFamily](){
123
- if (this->_shadowRegistry.contains(shadowNodeFamily)) {
124
- this->_suspendedFamilies.insert(shadowNodeFamily);
172
+ auto it = this->_shadowRegistry.find(shadowNodeFamily);
173
+
174
+ if (it != this->_shadowRegistry.end()) {
175
+ it->second.isSuspended = true;
125
176
  }
126
177
  });
127
178
  }
128
179
 
129
180
  bool core::UnistylesRegistry::isSuspended(const ShadowNodeFamily* family) const noexcept {
130
- return _suspendedFamilies.count(family) > 0;
181
+ auto it = this->_shadowRegistry.find(family);
182
+
183
+ return it != this->_shadowRegistry.end() && it->second.isSuspended;
131
184
  }
132
185
 
133
186
  std::shared_ptr<core::StyleSheet> core::UnistylesRegistry::addStyleSheet(jsi::Runtime& rt, core::StyleSheetType type, jsi::Object&& rawValue) {
@@ -143,36 +196,56 @@ core::DependencyMap core::UnistylesRegistry::buildDependencyMap(std::vector<Unis
143
196
  core::DependencyMap dependencyMap;
144
197
 
145
198
  std::unordered_set<UnistyleDependency> uniqueDependencies(deps.begin(), deps.end());
199
+ ReleasedFamilies releasedFamilies;
200
+
201
+ // families stay pinned by the registry until the shadow tree update is committed
202
+ this->trafficController.withLock([this, &dependencyMap, &uniqueDependencies, &releasedFamilies](){
203
+ // we iterate whole registry anyway, so it's a free moment to drop families unmounted without unlink
204
+ this->sweepUnownedFamiliesUnsafe(releasedFamilies);
205
+
206
+ for (const auto& [family, linkedFamily] : this->_shadowRegistry) {
207
+ bool hasAnyOfDependencies = false;
208
+
209
+ // Check if any dependency matches
210
+ for (const auto& unistyleData : linkedFamily.unistyles) {
211
+ for (const auto& dep : unistyleData->unistyle->dependencies) {
212
+ if (uniqueDependencies.count(dep)) {
213
+ hasAnyOfDependencies = true;
214
+ break;
215
+ }
216
+ }
146
217
 
147
- for (const auto& [family, unistyles] : this->_shadowRegistry) {
148
- bool hasAnyOfDependencies = false;
149
-
150
- // Check if any dependency matches
151
- for (const auto& unistyleData : unistyles) {
152
- for (const auto& dep : unistyleData->unistyle->dependencies) {
153
- if (uniqueDependencies.count(dep)) {
154
- hasAnyOfDependencies = true;
218
+ if (hasAnyOfDependencies) {
155
219
  break;
156
- }
220
+ };
157
221
  }
158
222
 
159
- if (hasAnyOfDependencies) {
160
- break;
161
- };
223
+ if (!hasAnyOfDependencies) {
224
+ continue;
225
+ }
226
+
227
+ dependencyMap[family].insert(
228
+ dependencyMap[family].end(),
229
+ linkedFamily.unistyles.begin(),
230
+ linkedFamily.unistyles.end()
231
+ );
162
232
  }
233
+ });
234
+
235
+ return dependencyMap;
236
+ }
163
237
 
164
- if (!hasAnyOfDependencies) {
238
+ void core::UnistylesRegistry::queueShadowLeafUpdatesUnsafe(shadow::ShadowLeafUpdates& updates) {
239
+ for (auto& [family, props] : updates) {
240
+ auto it = this->_shadowRegistry.find(family);
241
+
242
+ // family was unlinked in the meantime, there is nothing to update
243
+ if (it == this->_shadowRegistry.end()) {
165
244
  continue;
166
245
  }
167
246
 
168
- dependencyMap[family].insert(
169
- dependencyMap[family].end(),
170
- unistyles.begin(),
171
- unistyles.end()
172
- );
247
+ this->trafficController.setUpdate(it->second.family, std::move(props));
173
248
  }
174
-
175
- return dependencyMap;
176
249
  }
177
250
 
178
251
  // called from proxied function only, we don't know host
@@ -185,15 +258,15 @@ void core::UnistylesRegistry::shadowLeafUpdateFromUnistyle(jsi::Runtime& rt, Uni
185
258
  ? std::make_optional(maybePressableId.asString(rt).utf8(rt))
186
259
  : std::nullopt;
187
260
 
188
- for (const auto& [family, unistyles] : this->_shadowRegistry) {
189
- for (const auto& unistyleData : unistyles) {
261
+ for (const auto& [family, linkedFamily] : this->_shadowRegistry) {
262
+ for (const auto& unistyleData : linkedFamily.unistyles) {
190
263
  if (unistyleData->unistyle == unistyle) {
191
264
  updates[family] = parser.parseStylesToShadowTreeStyles(rt, { unistyleData });
192
265
  }
193
266
  }
194
267
  }
195
268
 
196
- this->trafficController.setUpdates(updates);
269
+ this->queueShadowLeafUpdatesUnsafe(updates);
197
270
  });
198
271
  }
199
272
 
@@ -263,10 +336,25 @@ void core::UnistylesRegistry::setScopedTheme(std::optional<std::string> themeNam
263
336
  }
264
337
 
265
338
  void core::UnistylesRegistry::destroy() {
339
+ // called when module is invalidated, before React Native destroys the runtime
340
+ // pins must be released now, as families hold JS handles that can't outlive the runtime
341
+ ReleasedFamilies releasedFamilies;
342
+
343
+ this->trafficController.withLock([this, &releasedFamilies](){
344
+ for (auto& [_, linkedFamily] : this->_shadowRegistry) {
345
+ releasedFamilies.emplace_back(std::move(linkedFamily.family));
346
+ }
347
+
348
+ for (auto& [_, pendingUpdate] : this->trafficController.takeUpdates()) {
349
+ releasedFamilies.emplace_back(std::move(pendingUpdate.family));
350
+ }
351
+
352
+ this->_shadowRegistry.clear();
353
+ this->_sweepThreshold = MIN_SWEEP_THRESHOLD;
354
+ });
355
+
266
356
  this->_state.reset();
267
357
  this->_styleSheetRegistry.clear();
268
- this->_shadowRegistry.clear();
269
- this->_suspendedFamilies.clear();
270
358
  this->_scopedTheme = std::nullopt;
271
359
  _nextStyleSheetTag.store(0);
272
360
  }
@@ -23,6 +23,12 @@ using namespace facebook::react;
23
23
 
24
24
  using DependencyMap = std::unordered_map<const ShadowNodeFamily*, std::vector<std::shared_ptr<UnistyleData>>>;
25
25
 
26
+ struct LinkedFamily {
27
+ std::shared_ptr<const ShadowNodeFamily> family;
28
+ std::vector<std::shared_ptr<UnistyleData>> unistyles;
29
+ bool isSuspended = false;
30
+ };
31
+
26
32
  struct UnistylesRegistry: public StyleSheetRegistry {
27
33
  static UnistylesRegistry& get();
28
34
 
@@ -40,13 +46,15 @@ struct UnistylesRegistry: public StyleSheetRegistry {
40
46
  UnistylesState& getState();
41
47
  void createState();
42
48
  std::vector<std::shared_ptr<core::StyleSheet>> getStyleSheetsToRefresh(std::vector<UnistyleDependency>& unistylesDependencies);
43
- void linkShadowNodeWithUnistyle(jsi::Runtime& rt, const ShadowNodeFamily*, std::vector<std::shared_ptr<UnistyleData>>& unistylesData, std::optional<folly::dynamic> initialScopedUpdate = std::nullopt);
49
+ void linkShadowNodeWithUnistyle(jsi::Runtime& rt, const std::shared_ptr<const ShadowNodeFamily>& shadowNodeFamily, std::vector<std::shared_ptr<UnistyleData>>& unistylesData, std::optional<folly::dynamic> initialScopedUpdate = std::nullopt);
44
50
  void unlinkShadowNodeWithUnistyles(const ShadowNodeFamily*);
45
51
  void suspendShadowNode(const ShadowNodeFamily*);
46
52
  bool isSuspended(const ShadowNodeFamily*) const noexcept;
47
53
  std::shared_ptr<core::StyleSheet> addStyleSheet(jsi::Runtime& rt, core::StyleSheetType type, jsi::Object&& rawValue);
48
54
  DependencyMap buildDependencyMap(std::vector<UnistyleDependency>& deps);
49
55
  void shadowLeafUpdateFromUnistyle(jsi::Runtime& rt, Unistyle::Shared unistyle, jsi::Value& maybePressableId);
56
+ // call it only within trafficController.withLock!
57
+ void queueShadowLeafUpdatesUnsafe(shadow::ShadowLeafUpdates& updates);
50
58
  shadow::ShadowTrafficController trafficController{};
51
59
  const std::optional<std::string> getScopedTheme();
52
60
  void removeDuplicatedUnistyles(const ShadowNodeFamily* shadowNodeFamily, std::vector<core::Unistyle::Shared>& unistyles);
@@ -55,14 +63,26 @@ struct UnistylesRegistry: public StyleSheetRegistry {
55
63
  void destroy();
56
64
 
57
65
  private:
66
+ using ReleasedFamilies = std::vector<std::shared_ptr<const ShadowNodeFamily>>;
67
+
58
68
  UnistylesRegistry() = default;
59
69
 
70
+ // call it only within trafficController.withLock!
71
+ // pins are moved to releasedFamilies, so they can be released after unlocking
72
+ void forgetFamilyUnsafe(const ShadowNodeFamily* shadowNodeFamily, ReleasedFamilies& releasedFamilies);
73
+ void sweepUnownedFamiliesUnsafe(ReleasedFamilies& releasedFamilies);
74
+
75
+ // React Native doesn't own the family anymore, it was unmounted without unlink
76
+ static bool isOwnedOnlyByUnistyles(const LinkedFamily& linkedFamily) noexcept;
77
+
78
+ static constexpr size_t MIN_SWEEP_THRESHOLD = 64;
79
+
60
80
  static std::atomic<int> _nextStyleSheetTag;
61
81
  std::optional<std::string> _scopedTheme{};
62
82
  std::unique_ptr<UnistylesState> _state{};
63
83
  std::unordered_map<int, std::shared_ptr<core::StyleSheet>> _styleSheetRegistry{};
64
- std::unordered_map<const ShadowNodeFamily*, std::vector<std::shared_ptr<UnistyleData>>> _shadowRegistry{};
65
- std::unordered_set<const ShadowNodeFamily*> _suspendedFamilies{};
84
+ std::unordered_map<const ShadowNodeFamily*, LinkedFamily> _shadowRegistry{};
85
+ size_t _sweepThreshold = MIN_SWEEP_THRESHOLD;
66
86
  };
67
87
 
68
88
  inline UnistylesRegistry& UnistylesRegistry::get() {
@@ -87,9 +87,10 @@ jsi::Value HybridShadowRegistry::link(jsi::Runtime &rt, const jsi::Value &thisVa
87
87
  parser.rebuildUnistyleWithScopedTheme(rt, parsedStyleSheet, unistyleData);
88
88
  }
89
89
 
90
- // Initialize parsedStyle from the unistyle's current computed style from withUnistyles
90
+ // The shared cache can belong to another view by the time React attaches refs.
91
+ // Restore this view's arguments and variants before saving its native styles.
91
92
  if (!unistyleData->parsedStyle.has_value() && unistyle->parsedStyle.has_value()) {
92
- unistyleData->parsedStyle = jsi::Value(rt, unistyle->parsedStyle.value()).asObject(rt);
93
+ parser.rebuildUnistyleWithVariants(rt, unistyleData);
93
94
  }
94
95
 
95
96
  unistylesData.emplace_back(unistyleData);
@@ -103,7 +104,7 @@ jsi::Value HybridShadowRegistry::link(jsi::Runtime &rt, const jsi::Value &thisVa
103
104
 
104
105
  registry.linkShadowNodeWithUnistyle(
105
106
  rt,
106
- &shadowNodeWrapper->getFamily(),
107
+ shadowNodeWrapper->getFamilyShared(),
107
108
  unistylesData,
108
109
  std::move(initialScopedUpdate)
109
110
  );
@@ -170,9 +171,5 @@ jsi::Value HybridShadowRegistry::getScopedTheme(jsi::Runtime &rt, const jsi::Val
170
171
  }
171
172
 
172
173
  std::shared_ptr<const core::ShadowNode> HybridShadowRegistry::getShadowNodeFromRef(jsi::Runtime& rt, const jsi::Value& maybeRef) {
173
- #if REACT_NATIVE_VERSION_MINOR >= 81
174
174
  return Bridging<std::shared_ptr<const ShadowNode>>::fromJs(rt, maybeRef);
175
- #else
176
- return shadowNodeFromValue(rt, maybeRef);
177
- #endif
178
175
  }
@@ -444,8 +444,7 @@ void parser::Parser::rebuildShadowLeafUpdates(jsi::Runtime& rt, core::Dependency
444
444
  updates.emplace(shadowNode, std::move(rawProps));
445
445
  }
446
446
 
447
- registry.trafficController.setUpdates(updates);
448
- registry.trafficController.resumeUnistylesTraffic();
447
+ registry.queueShadowLeafUpdatesUnsafe(updates);
449
448
  });
450
449
  }
451
450
 
@@ -0,0 +1,44 @@
1
+ #pragma once
2
+
3
+ #include <folly/dynamic.h>
4
+ #include <react/renderer/core/ShadowNodeFamily.h>
5
+ #include <cxxreact/ReactNativeVersion.h>
6
+ #include <memory>
7
+ #include <mutex>
8
+
9
+ // React Native reads ShadowNodeFamily::nativeProps_DEPRECATED from any thread
10
+ // (eg. ShadowNode::clone during main thread layout) and since 0.86.1 guards every access with nativePropsMutex
11
+ // we need to hold the same lock, otherwise mutating / replacing the folly::dynamic races with RN copying it
12
+ #if REACT_NATIVE_VERSION_MINOR > 86 || (REACT_NATIVE_VERSION_MINOR == 86 && REACT_NATIVE_VERSION_PATCH >= 1)
13
+ #define UNISTYLES_HAS_NATIVE_PROPS_MUTEX 1
14
+ #else
15
+ #define UNISTYLES_HAS_NATIVE_PROPS_MUTEX 0
16
+ #endif
17
+
18
+ namespace margelo::nitro::unistyles::shadow {
19
+
20
+ using namespace facebook::react;
21
+
22
+ inline void mergeNativeProps(const ShadowNodeFamily* family, const folly::dynamic& props) {
23
+ #if UNISTYLES_HAS_NATIVE_PROPS_MUTEX
24
+ std::lock_guard<std::mutex> lock(family->nativePropsMutex);
25
+ #endif
26
+
27
+ if (family->nativeProps_DEPRECATED && family->nativeProps_DEPRECATED->isObject()) {
28
+ family->nativeProps_DEPRECATED->update(props);
29
+
30
+ return;
31
+ }
32
+
33
+ family->nativeProps_DEPRECATED = std::make_unique<folly::dynamic>(props);
34
+ }
35
+
36
+ inline void resetNativeProps(const ShadowNodeFamily* family) {
37
+ #if UNISTYLES_HAS_NATIVE_PROPS_MUTEX
38
+ std::lock_guard<std::mutex> lock(family->nativePropsMutex);
39
+ #endif
40
+
41
+ family->nativeProps_DEPRECATED.reset();
42
+ }
43
+
44
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include <jsi/jsi.h>
4
4
  #include <folly/dynamic.h>
5
+ #include <memory>
5
6
  #include <react/renderer/uimanager/UIManager.h>
6
7
 
7
8
  namespace margelo::nitro::unistyles::shadow {
@@ -12,4 +13,11 @@ using namespace facebook::react;
12
13
  // translates Unistyles changes to unified shadow tree changes
13
14
  using ShadowLeafUpdates = std::unordered_map<const ShadowNodeFamily*, folly::dynamic>;
14
15
 
16
+ struct PinnedShadowLeafUpdate {
17
+ std::shared_ptr<const ShadowNodeFamily> family;
18
+ folly::dynamic props;
19
+ };
20
+
21
+ using PinnedShadowLeafUpdates = std::unordered_map<const ShadowNodeFamily*, PinnedShadowLeafUpdate>;
22
+
15
23
  }
@@ -8,52 +8,32 @@ namespace margelo::nitro::unistyles::shadow {
8
8
  // Like a traffic officer managing a jam, this struct ensures everything
9
9
  // is synchronized within a set timeframe, controlling flow and preventing chaos.
10
10
  struct ShadowTrafficController {
11
- inline bool shouldStop() {
12
- return !_canCommit;
13
- }
14
-
15
- inline void stopUnistylesTraffic() {
16
- this->_canCommit = false;
17
- }
11
+ inline void setUpdate(std::shared_ptr<const ShadowNodeFamily> family, folly::dynamic props) {
12
+ // call it only within withLock!
13
+ auto key = family.get();
18
14
 
19
- inline void resumeUnistylesTraffic() {
20
- this->_canCommit = true;
15
+ _unistylesUpdates.insert_or_assign(key, PinnedShadowLeafUpdate{std::move(family), std::move(props)});
21
16
  }
22
17
 
23
- inline shadow::ShadowLeafUpdates& getUpdates() {
18
+ inline PinnedShadowLeafUpdates takeUpdates() {
24
19
  // call it only within withLock!
25
- return _unistylesUpdates;
20
+ return std::exchange(_unistylesUpdates, {});
26
21
  }
27
22
 
28
- inline void setUpdates(shadow::ShadowLeafUpdates& newUpdates) {
23
+ inline std::shared_ptr<const ShadowNodeFamily> removeShadowNode(const ShadowNodeFamily* shadowNodeFamily) {
29
24
  // call it only within withLock!
30
- auto& targetUpdates = _unistylesUpdates;
31
-
32
- // this is important as overriding updates may skip some interim changes
33
- // Unistyles emits different events so this will make sure that everything is synced
34
- std::for_each(newUpdates.begin(), newUpdates.end(), [&targetUpdates](auto& pair){
35
- if (targetUpdates.contains(pair.first)) {
36
- targetUpdates[pair.first] = std::move(pair.second);
37
-
38
- return;
39
- }
25
+ // returns the pin, so the caller can release it after unlocking
26
+ auto it = _unistylesUpdates.find(shadowNodeFamily);
40
27
 
41
- targetUpdates.emplace(pair.first, std::move(pair.second));
42
- });
43
- }
44
-
45
- inline void removeShadowNode(const ShadowNodeFamily* shadowNodeFamily) {
46
- // call it only within withLock!
47
- if (_unistylesUpdates.contains(shadowNodeFamily)) {
48
- _unistylesUpdates.erase(shadowNodeFamily);
28
+ if (it == _unistylesUpdates.end()) {
29
+ return nullptr;
49
30
  }
50
- }
51
31
 
52
- inline void restore() {
53
- // call it only within withLock!
32
+ auto family = std::move(it->second.family);
33
+
34
+ _unistylesUpdates.erase(it);
54
35
 
55
- _unistylesUpdates = {};
56
- _canCommit = false;
36
+ return family;
57
37
  }
58
38
 
59
39
  template <typename F>
@@ -64,8 +44,7 @@ struct ShadowTrafficController {
64
44
  }
65
45
 
66
46
  private:
67
- std::atomic<bool> _canCommit = false;
68
- shadow::ShadowLeafUpdates _unistylesUpdates{};
47
+ PinnedShadowLeafUpdates _unistylesUpdates{};
69
48
 
70
49
  // this struct should be accessed in thread-safe manner. Otherwise shadow tree updates
71
50
  // from different threads will break it