@tamagui/native-registry 3.0.3 → 3.0.4
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/cpp/HybridTamaguiRegistry.cpp +283 -80
- package/cpp/HybridTamaguiRegistry.hpp +52 -9
- package/dist/cjs/index.cjs +9 -3
- package/dist/cjs/index.native.cjs +18 -7
- package/dist/cjs/index.native.js +18 -7
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/esm/index.js +7 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +7 -3
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +16 -7
- package/dist/esm/index.native.js.map +1 -1
- package/package.json +3 -3
- package/src/index.native.ts +58 -16
- package/src/index.ts +19 -5
- package/src/specs/TamaguiRegistry.nitro.ts +4 -4
- package/src/types.ts +0 -7
- package/types/index.d.ts +6 -4
- package/types/index.d.ts.map +2 -2
- package/types/index.native.d.ts +24 -10
- package/types/index.native.d.ts.map +2 -2
- package/types/specs/TamaguiRegistry.nitro.d.ts +4 -4
- package/types/specs/TamaguiRegistry.nitro.d.ts.map +1 -1
- package/types/types.d.ts +0 -6
- package/types/types.d.ts.map +2 -2
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
#include <jsi/JSIDynamic.h>
|
|
4
4
|
#include <react/renderer/components/root/RootShadowNode.h>
|
|
5
|
+
#include <react/renderer/components/text/ParagraphShadowNode.h>
|
|
6
|
+
#include <react/renderer/textlayoutmanager/TextMeasureCache.h>
|
|
5
7
|
#include <react/renderer/core/PropsParserContext.h>
|
|
6
8
|
#include <react/renderer/core/RawProps.h>
|
|
7
9
|
#include <react/renderer/core/ShadowNode.h>
|
|
@@ -12,7 +14,9 @@
|
|
|
12
14
|
#include <react/renderer/uimanager/UIManagerBinding.h>
|
|
13
15
|
#include <react/renderer/uimanager/primitives.h>
|
|
14
16
|
|
|
17
|
+
#include <cstring>
|
|
15
18
|
#include <unordered_set>
|
|
19
|
+
#include <vector>
|
|
16
20
|
|
|
17
21
|
namespace margelo::nitro::tamagui::registry {
|
|
18
22
|
|
|
@@ -22,12 +26,13 @@ using margelo::nitro::Prototype;
|
|
|
22
26
|
void HybridTamaguiRegistry::loadHybridMethods() {
|
|
23
27
|
HybridTamaguiRegistrySpec::loadHybridMethods();
|
|
24
28
|
registerHybrids(this, [](Prototype& prototype) {
|
|
25
|
-
prototype.registerRawHybridMethod("link",
|
|
29
|
+
prototype.registerRawHybridMethod("link", 4, &HybridTamaguiRegistry::link);
|
|
30
|
+
prototype.registerRawHybridMethod("linkTables", 4,
|
|
31
|
+
&HybridTamaguiRegistry::linkTables);
|
|
26
32
|
prototype.registerRawHybridMethod("applyViewStates", 1,
|
|
27
33
|
&HybridTamaguiRegistry::applyViewStates);
|
|
28
|
-
prototype.registerRawHybridMethod(
|
|
29
|
-
|
|
30
|
-
&HybridTamaguiRegistry::updateViewStateTables);
|
|
34
|
+
prototype.registerRawHybridMethod("setTableState", 3,
|
|
35
|
+
&HybridTamaguiRegistry::setTableState);
|
|
31
36
|
prototype.registerRawHybridMethod("getViewState", 1,
|
|
32
37
|
&HybridTamaguiRegistry::getViewState);
|
|
33
38
|
});
|
|
@@ -36,7 +41,7 @@ void HybridTamaguiRegistry::loadHybridMethods() {
|
|
|
36
41
|
jsi::Value HybridTamaguiRegistry::link(jsi::Runtime& rt, const jsi::Value&,
|
|
37
42
|
const jsi::Value* args, size_t count) {
|
|
38
43
|
if (count < 3) {
|
|
39
|
-
throw jsi::JSError(rt, "link(shadowNode, slots, scopeId) requires 3 arguments");
|
|
44
|
+
throw jsi::JSError(rt, "link(shadowNode, slots | tableId, scopeId) requires 3 arguments");
|
|
40
45
|
}
|
|
41
46
|
if (!args[0].isObject()) return jsi::Value::undefined();
|
|
42
47
|
auto obj = args[0].asObject(rt);
|
|
@@ -46,17 +51,90 @@ jsi::Value HybridTamaguiRegistry::link(jsi::Runtime& rt, const jsi::Value&,
|
|
|
46
51
|
|
|
47
52
|
runtime_ = &rt;
|
|
48
53
|
|
|
49
|
-
auto slots = jsi::dynamicFromValue(rt, args[1]);
|
|
50
54
|
const double id = nextId_++;
|
|
51
|
-
views_[id]
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
auto& view = views_[id];
|
|
56
|
+
view.node = wrapper->shadowNode;
|
|
57
|
+
view.scopeId = args[2].asString(rt).utf8(rt);
|
|
58
|
+
if (args[1].isNumber()) {
|
|
59
|
+
auto& table = tables_[args[1].asNumber()];
|
|
60
|
+
if (!table) table = std::make_shared<Table>();
|
|
61
|
+
view.table = table;
|
|
62
|
+
} else {
|
|
63
|
+
auto slots = jsi::dynamicFromValue(rt, args[1]);
|
|
64
|
+
view.baseProps = slots.getDefault("base", nullptr);
|
|
65
|
+
view.stateProps = slots.getDefault("state", nullptr);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (count > 3 && args[3].isBool() && args[3].getBool()) {
|
|
69
|
+
if (const auto* name = viewStateName(view)) {
|
|
70
|
+
LeafUpdates updates;
|
|
71
|
+
buildViewUpdate(view, *name, updates);
|
|
72
|
+
commitUpdates(rt, updates);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
57
75
|
return jsi::Value(id);
|
|
58
76
|
}
|
|
59
77
|
|
|
78
|
+
// links many views to shared tables in one call: entry i is shadow node
|
|
79
|
+
// nodes[i] on table tableIds[i] under scope scopes[scopeIndexes[i]]. ids are
|
|
80
|
+
// issued in order starting at the returned id; an entry without a live shadow
|
|
81
|
+
// node still consumes its id and stays unlinked
|
|
82
|
+
jsi::Value HybridTamaguiRegistry::linkTables(jsi::Runtime& rt, const jsi::Value&,
|
|
83
|
+
const jsi::Value* args, size_t count) {
|
|
84
|
+
if (count < 4 || !args[0].isObject() || !args[1].isObject() ||
|
|
85
|
+
!args[2].isObject() || !args[3].isObject()) {
|
|
86
|
+
throw jsi::JSError(
|
|
87
|
+
rt, "linkTables(nodes, tableIds, scopeIndexes, scopes) requires 4 arrays");
|
|
88
|
+
}
|
|
89
|
+
runtime_ = &rt;
|
|
90
|
+
auto nodes = args[0].asObject(rt).asArray(rt);
|
|
91
|
+
auto tableIds = args[1].asObject(rt).asArray(rt);
|
|
92
|
+
auto scopeIndexes = args[2].asObject(rt).asArray(rt);
|
|
93
|
+
auto scopeValues = args[3].asObject(rt).asArray(rt);
|
|
94
|
+
|
|
95
|
+
std::vector<std::string> scopes(scopeValues.size(rt));
|
|
96
|
+
for (size_t i = 0; i < scopes.size(); i++) {
|
|
97
|
+
scopes[i] = scopeValues.getValueAtIndex(rt, i).asString(rt).utf8(rt);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const size_t size = nodes.size(rt);
|
|
101
|
+
const double firstId = nextId_;
|
|
102
|
+
nextId_ += static_cast<double>(size);
|
|
103
|
+
|
|
104
|
+
std::shared_ptr<Table> table;
|
|
105
|
+
double tableId = -1;
|
|
106
|
+
for (size_t i = 0; i < size; i++) {
|
|
107
|
+
auto value = nodes.getValueAtIndex(rt, i);
|
|
108
|
+
if (!value.isObject()) continue;
|
|
109
|
+
auto obj = value.asObject(rt);
|
|
110
|
+
if (!obj.hasNativeState(rt)) continue;
|
|
111
|
+
auto wrapper = obj.getNativeState<ShadowNodeWrapper>(rt);
|
|
112
|
+
if (!wrapper || !wrapper->shadowNode) continue;
|
|
113
|
+
|
|
114
|
+
// consecutive entries usually share a table
|
|
115
|
+
const double nextTableId = tableIds.getValueAtIndex(rt, i).asNumber();
|
|
116
|
+
if (!table || nextTableId != tableId) {
|
|
117
|
+
tableId = nextTableId;
|
|
118
|
+
auto& entry = tables_[tableId];
|
|
119
|
+
if (!entry) entry = std::make_shared<Table>();
|
|
120
|
+
table = entry;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
auto& view = views_[firstId + static_cast<double>(i)];
|
|
124
|
+
view.node = wrapper->shadowNode;
|
|
125
|
+
view.scopeId = scopes.at(
|
|
126
|
+
static_cast<size_t>(scopeIndexes.getValueAtIndex(rt, i).asNumber()));
|
|
127
|
+
view.table = table;
|
|
128
|
+
}
|
|
129
|
+
return jsi::Value(firstId);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const std::string* HybridTamaguiRegistry::viewStateName(
|
|
133
|
+
const LinkedView& view) const {
|
|
134
|
+
return view.activeState.empty() ? activeStateName(view.scopeId)
|
|
135
|
+
: &view.activeState;
|
|
136
|
+
}
|
|
137
|
+
|
|
60
138
|
void HybridTamaguiRegistry::unlink(double id) {
|
|
61
139
|
views_.erase(id);
|
|
62
140
|
}
|
|
@@ -123,10 +201,32 @@ using LeafUpdates = HybridTamaguiRegistry::LeafUpdates;
|
|
|
123
201
|
using AffectedNodes =
|
|
124
202
|
std::unordered_map<const ShadowNodeFamily*, std::unordered_set<size_t>>;
|
|
125
203
|
|
|
204
|
+
// records, for every ancestor of an updated view, which children lead to one.
|
|
205
|
+
// returns whether `node` is or contains an updated view
|
|
206
|
+
bool collectAffected(const ShadowNode& node, const LeafUpdates& updates,
|
|
207
|
+
AffectedNodes& affected) {
|
|
208
|
+
bool hit = updates.contains(&node.getFamily());
|
|
209
|
+
const auto& children = node.getChildren();
|
|
210
|
+
for (size_t index = 0; index < children.size(); index++) {
|
|
211
|
+
if (collectAffected(*children[index], updates, affected)) {
|
|
212
|
+
affected[&node.getFamily()].insert(index);
|
|
213
|
+
hit = true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return hit;
|
|
217
|
+
}
|
|
218
|
+
|
|
126
219
|
AffectedNodes findAffectedNodes(const RootShadowNode& root,
|
|
127
220
|
const LeafUpdates& updates) {
|
|
128
221
|
AffectedNodes affected;
|
|
129
|
-
|
|
222
|
+
// getAncestors scans each parent's children to find the child, so many
|
|
223
|
+
// updates under one wide parent (a long list) cost updates x siblings; past
|
|
224
|
+
// a few updates one walk of the tree is cheaper
|
|
225
|
+
if (updates.size() > 32) {
|
|
226
|
+
collectAffected(root, updates, affected);
|
|
227
|
+
return affected;
|
|
228
|
+
}
|
|
229
|
+
for (const auto& [family, update] : updates) {
|
|
130
230
|
// families of unmounted views have no ancestors in this root: skipped
|
|
131
231
|
for (const auto& [parentNode, childIndex] : family->getAncestors(root)) {
|
|
132
232
|
affected[&parentNode.get().getFamily()].insert(childIndex);
|
|
@@ -135,61 +235,165 @@ AffectedNodes findAffectedNodes(const RootShadowNode& root,
|
|
|
135
235
|
return affected;
|
|
136
236
|
}
|
|
137
237
|
|
|
238
|
+
// RN dirties the measurement of every paragraph cloned with props, and the
|
|
239
|
+
// dirty flag relays out each ancestor: one text color in each row of a long
|
|
240
|
+
// list relays out the whole list. for a paragraph of plain strings, whose
|
|
241
|
+
// fragments take exactly its own text attributes, rebuild those attributes
|
|
242
|
+
// the way ParagraphShadowNode::getContent does; when RN's own measure cache
|
|
243
|
+
// would call them layout-equivalent (colors, decorations, shadows, opacity),
|
|
244
|
+
// restyle the laid-out text directly and build the node without the clone
|
|
245
|
+
// step that dirties it. null when the update needs a real layout
|
|
246
|
+
std::shared_ptr<ShadowNode> restyleParagraph(const ShadowNode& node,
|
|
247
|
+
const Props::Shared& props) {
|
|
248
|
+
if (std::strcmp(node.getComponentName(), ParagraphComponentName) != 0) {
|
|
249
|
+
return nullptr;
|
|
250
|
+
}
|
|
251
|
+
for (const auto& child : node.getChildren()) {
|
|
252
|
+
if (std::strcmp(child->getComponentName(), "RawText") != 0) return nullptr;
|
|
253
|
+
}
|
|
254
|
+
const auto& nextProps = static_cast<const ParagraphProps&>(*props);
|
|
255
|
+
const auto& paragraph = static_cast<const ParagraphShadowNode&>(node);
|
|
256
|
+
if (!(nextProps.paragraphAttributes ==
|
|
257
|
+
paragraph.getConcreteProps().paragraphAttributes)) {
|
|
258
|
+
return nullptr;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
auto data = paragraph.getStateData();
|
|
262
|
+
auto layoutManager = data.layoutManager.lock();
|
|
263
|
+
if (!layoutManager || data.attributedString.getFragments().empty()) {
|
|
264
|
+
return nullptr;
|
|
265
|
+
}
|
|
266
|
+
const auto& laidOut = data.attributedString.getBaseTextAttributes();
|
|
267
|
+
auto attributes = TextAttributes::defaultTextAttributes();
|
|
268
|
+
attributes.fontSizeMultiplier = laidOut.fontSizeMultiplier;
|
|
269
|
+
attributes.apply(nextProps.textAttributes);
|
|
270
|
+
attributes.layoutDirection = laidOut.layoutDirection;
|
|
271
|
+
if (!areTextAttributesEquivalentLayoutWise(laidOut, attributes)) {
|
|
272
|
+
return nullptr;
|
|
273
|
+
}
|
|
274
|
+
for (auto& fragment : data.attributedString.getFragments()) {
|
|
275
|
+
fragment.textAttributes = attributes;
|
|
276
|
+
}
|
|
277
|
+
data.attributedString.setBaseTextAttributes(attributes);
|
|
278
|
+
|
|
279
|
+
const ShadowNodeFragment fragment{
|
|
280
|
+
.props = props,
|
|
281
|
+
.children = ShadowNodeFragment::childrenPlaceholder(),
|
|
282
|
+
.state = node.getComponentDescriptor().createState(
|
|
283
|
+
node.getFamily(), std::make_shared<const ParagraphState>(std::move(data))),
|
|
284
|
+
};
|
|
285
|
+
// what ConcreteComponentDescriptor::cloneShadowNode does, minus
|
|
286
|
+
// completeClone (the dirtying step); adopt only sets the layout manager.
|
|
287
|
+
// a yoga style change still dirties the node through its constructor
|
|
288
|
+
auto clone = std::make_shared<ParagraphShadowNode>(node, fragment);
|
|
289
|
+
clone->setTextLayoutManager(std::move(layoutManager));
|
|
290
|
+
node.transferRuntimeShadowNodeReference(clone, fragment);
|
|
291
|
+
return clone;
|
|
292
|
+
}
|
|
293
|
+
|
|
138
294
|
std::shared_ptr<ShadowNode> cloneSubtree(const ShadowNode& node,
|
|
139
295
|
const LeafUpdates& updates,
|
|
140
296
|
const AffectedNodes& affected) {
|
|
141
297
|
const auto* family = &node.getFamily();
|
|
142
|
-
auto children = node.getChildren();
|
|
143
298
|
|
|
299
|
+
// only an ancestor of an updated view gets new children: RN dirties the
|
|
300
|
+
// layout of any measurable node (text) cloned with a children fragment,
|
|
301
|
+
// so passing them to the updated views themselves would re-measure every
|
|
302
|
+
// text and re-run layout for a color change
|
|
303
|
+
auto children = ShadowNodeFragment::childrenPlaceholder();
|
|
144
304
|
if (auto it = affected.find(family); it != affected.end()) {
|
|
305
|
+
auto cloned = node.getChildren();
|
|
145
306
|
for (auto index : it->second) {
|
|
146
|
-
|
|
307
|
+
cloned[index] = cloneSubtree(*cloned[index], updates, affected);
|
|
147
308
|
}
|
|
309
|
+
children = std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>(
|
|
310
|
+
std::move(cloned));
|
|
148
311
|
}
|
|
149
312
|
|
|
150
313
|
Props::Shared newProps = ShadowNodeFragment::propsPlaceholder();
|
|
151
314
|
if (auto it = updates.find(family); it != updates.end()) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
315
|
+
const auto& update = it->second;
|
|
316
|
+
const auto parse = [&] {
|
|
317
|
+
PropsParserContext ctx{node.getSurfaceId(), *node.getContextContainer()};
|
|
318
|
+
return node.getComponentDescriptor().cloneProps(ctx, node.getProps(),
|
|
319
|
+
RawProps(update.props()));
|
|
320
|
+
};
|
|
321
|
+
if (auto* warm = update.warm) {
|
|
322
|
+
if (warm->last != node.getProps() || warm->revision != update.revision) {
|
|
323
|
+
warm->byState.clear();
|
|
324
|
+
warm->revision = update.revision;
|
|
325
|
+
}
|
|
326
|
+
auto& props = warm->byState[*update.state];
|
|
327
|
+
if (!props) props = parse();
|
|
328
|
+
warm->last = props;
|
|
329
|
+
newProps = props;
|
|
330
|
+
} else {
|
|
331
|
+
newProps = parse();
|
|
332
|
+
}
|
|
333
|
+
if (auto restyled = restyleParagraph(node, newProps)) {
|
|
334
|
+
return restyled;
|
|
335
|
+
}
|
|
155
336
|
}
|
|
156
337
|
|
|
157
338
|
return node.clone({
|
|
158
339
|
.props = newProps,
|
|
159
|
-
.children =
|
|
160
|
-
|
|
161
|
-
.state = node.getState(),
|
|
340
|
+
.children = children,
|
|
341
|
+
.state = ShadowNodeFragment::statePlaceholder(),
|
|
162
342
|
});
|
|
163
343
|
}
|
|
164
344
|
|
|
165
345
|
} // namespace
|
|
166
346
|
|
|
167
|
-
// merge base + the named state's props for one view into `out
|
|
168
|
-
// family's native props so React re-commits of unchanged
|
|
169
|
-
// values instead of resurrecting a stale absorbed node
|
|
170
|
-
//
|
|
347
|
+
// merge base + the named state's props for one view into `out`. a runtime
|
|
348
|
+
// view also syncs its family's native props so React re-commits of unchanged
|
|
349
|
+
// children merge our values instead of resurrecting a stale absorbed node.
|
|
350
|
+
// returns false on a state miss.
|
|
171
351
|
bool HybridTamaguiRegistry::buildViewUpdate(LinkedView& view,
|
|
172
352
|
const std::string& stateName,
|
|
173
353
|
LeafUpdates& out) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (it == view.stateProps.items().end()) {
|
|
354
|
+
LeafUpdate update;
|
|
355
|
+
if (view.table) {
|
|
356
|
+
// a table view commits only its state keys, React rendered the rest
|
|
357
|
+
auto it = view.table->states.find(stateName);
|
|
358
|
+
if (it == view.table->states.items().end()) {
|
|
180
359
|
missCount_ += 1;
|
|
181
360
|
return false;
|
|
182
361
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
362
|
+
update.shared = &it->second;
|
|
363
|
+
update.warm = &view.warm;
|
|
364
|
+
update.state = &it->first.getString();
|
|
365
|
+
update.revision = view.table->revision;
|
|
366
|
+
} else {
|
|
367
|
+
auto& props = update.owned;
|
|
368
|
+
props = view.baseProps.isObject() ? view.baseProps : folly::dynamic::object();
|
|
369
|
+
if (view.stateProps.isObject()) {
|
|
370
|
+
auto it = view.stateProps.find(stateName);
|
|
371
|
+
if (it == view.stateProps.items().end()) {
|
|
372
|
+
missCount_ += 1;
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
if (props.empty()) {
|
|
376
|
+
props = it->second;
|
|
377
|
+
} else {
|
|
378
|
+
props.update(it->second);
|
|
379
|
+
}
|
|
380
|
+
} else if (props.empty()) {
|
|
381
|
+
return false;
|
|
187
382
|
}
|
|
188
|
-
} else if (props.empty()) {
|
|
189
|
-
return false;
|
|
190
383
|
}
|
|
384
|
+
const auto& props = update.props();
|
|
191
385
|
|
|
192
386
|
const auto& family = view.node->getFamily();
|
|
387
|
+
// the retained node keeps its family alive; unmounted views resolve to
|
|
388
|
+
// no ancestors during the transaction and are skipped safely
|
|
389
|
+
if (view.table) {
|
|
390
|
+
// React renders a table view with its scope's current state and the
|
|
391
|
+
// engine's clones take over React's handles, so nothing goes stale. a
|
|
392
|
+
// sticky copy would make RN re-parse the view's props on every clone
|
|
393
|
+
out[&family] = std::move(update);
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
std::lock_guard<std::mutex> lock(family.nativePropsMutex);
|
|
193
397
|
if (family.nativeProps_DEPRECATED) {
|
|
194
398
|
family.nativeProps_DEPRECATED->update(props);
|
|
195
399
|
} else {
|
|
@@ -205,9 +409,7 @@ bool HybridTamaguiRegistry::buildViewUpdate(LinkedView& view,
|
|
|
205
409
|
if (item.second.isNull()) sticky.erase(item.first.asString());
|
|
206
410
|
}
|
|
207
411
|
|
|
208
|
-
|
|
209
|
-
// no ancestors during the transaction and are skipped safely
|
|
210
|
-
out[&family] = std::move(props);
|
|
412
|
+
out[&family] = std::move(update);
|
|
211
413
|
return true;
|
|
212
414
|
}
|
|
213
415
|
|
|
@@ -218,6 +420,12 @@ void HybridTamaguiRegistry::commitUpdates(jsi::Runtime& rt,
|
|
|
218
420
|
auto binding = UIManagerBinding::getBinding(rt);
|
|
219
421
|
if (!binding) return;
|
|
220
422
|
|
|
423
|
+
// the clones this commit makes take over React's handles to the nodes they
|
|
424
|
+
// replace (RN's per-thread switch for updateRuntimeShadowNodeReferences), so
|
|
425
|
+
// React's next commit builds on them instead of its own stale nodes
|
|
426
|
+
const bool updatedReferences =
|
|
427
|
+
ShadowNode::getUseRuntimeShadowNodeReferenceUpdateOnThread();
|
|
428
|
+
ShadowNode::setUseRuntimeShadowNodeReferenceUpdateOnThread(true);
|
|
221
429
|
binding->getUIManager().getShadowTreeRegistry().enumerate(
|
|
222
430
|
[this, &updates](const ShadowTree& tree, bool& /*stop*/) {
|
|
223
431
|
auto status = tree.commit(
|
|
@@ -233,6 +441,7 @@ void HybridTamaguiRegistry::commitUpdates(jsi::Runtime& rt,
|
|
|
233
441
|
commitCount_ += 1;
|
|
234
442
|
}
|
|
235
443
|
});
|
|
444
|
+
ShadowNode::setUseRuntimeShadowNodeReferenceUpdateOnThread(updatedReferences);
|
|
236
445
|
}
|
|
237
446
|
|
|
238
447
|
void HybridTamaguiRegistry::applyUpdates(jsi::Runtime& rt,
|
|
@@ -246,9 +455,9 @@ void HybridTamaguiRegistry::applyUpdates(jsi::Runtime& rt,
|
|
|
246
455
|
// broadcasts must not fight their controller
|
|
247
456
|
if (!view.activeState.empty()) continue;
|
|
248
457
|
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
if (
|
|
458
|
+
const bool hasStates = view.table || view.stateProps.isObject();
|
|
459
|
+
const auto* name = hasStates ? activeStateName(view.scopeId) : nullptr;
|
|
460
|
+
if (hasStates && !name) continue;
|
|
252
461
|
|
|
253
462
|
buildViewUpdate(view, name ? *name : "", updates);
|
|
254
463
|
}
|
|
@@ -286,6 +495,11 @@ jsi::Value HybridTamaguiRegistry::applyViewStates(jsi::Runtime& rt,
|
|
|
286
495
|
|
|
287
496
|
const std::string stateName = stateIt->second.asString();
|
|
288
497
|
auto props = entry.getDefault("props", nullptr);
|
|
498
|
+
if (props.isObject() && view.table) {
|
|
499
|
+
// a runtime controller takes the view over: it owns its tables now
|
|
500
|
+
view.stateProps = view.table->states;
|
|
501
|
+
view.table = nullptr;
|
|
502
|
+
}
|
|
289
503
|
if (props.isObject()) {
|
|
290
504
|
if (!view.stateProps.isObject()) {
|
|
291
505
|
view.stateProps = folly::dynamic::object();
|
|
@@ -301,48 +515,28 @@ jsi::Value HybridTamaguiRegistry::applyViewStates(jsi::Runtime& rt,
|
|
|
301
515
|
return jsi::Value::undefined();
|
|
302
516
|
}
|
|
303
517
|
|
|
304
|
-
jsi::Value HybridTamaguiRegistry::
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
518
|
+
jsi::Value HybridTamaguiRegistry::setTableState(jsi::Runtime& rt,
|
|
519
|
+
const jsi::Value&,
|
|
520
|
+
const jsi::Value* args,
|
|
521
|
+
size_t count) {
|
|
522
|
+
if (count < 3 || !args[0].isNumber() || !args[1].isString() ||
|
|
523
|
+
!args[2].isObject()) {
|
|
524
|
+
throw jsi::JSError(rt, "setTableState(tableId, state, props) requires 3 arguments");
|
|
309
525
|
}
|
|
310
526
|
runtime_ = &rt;
|
|
311
527
|
|
|
312
|
-
auto
|
|
313
|
-
if (!
|
|
314
|
-
|
|
315
|
-
|
|
528
|
+
auto& table = tables_[args[0].asNumber()];
|
|
529
|
+
if (!table) table = std::make_shared<Table>();
|
|
530
|
+
const std::string stateName = args[1].asString(rt).utf8(rt);
|
|
531
|
+
table->states[stateName] = jsi::dynamicFromValue(rt, args[2]);
|
|
532
|
+
table->revision += 1;
|
|
316
533
|
|
|
317
534
|
LeafUpdates updates;
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
}
|
|
535
|
+
for (auto& [id, view] : views_) {
|
|
536
|
+
if (view.table != table) continue;
|
|
537
|
+
const auto* name = viewStateName(view);
|
|
538
|
+
if (name && *name == stateName) buildViewUpdate(view, stateName, updates);
|
|
344
539
|
}
|
|
345
|
-
|
|
346
540
|
commitUpdates(rt, updates);
|
|
347
541
|
return jsi::Value::undefined();
|
|
348
542
|
}
|
|
@@ -358,8 +552,17 @@ jsi::Value HybridTamaguiRegistry::getViewState(jsi::Runtime& rt,
|
|
|
358
552
|
|
|
359
553
|
folly::dynamic out = folly::dynamic::object("scopeId", view.scopeId)(
|
|
360
554
|
"activeState", view.activeState)("base", view.baseProps)(
|
|
361
|
-
"states", view.stateProps);
|
|
555
|
+
"states", view.table ? view.table->states : view.stateProps);
|
|
556
|
+
if (view.table) {
|
|
557
|
+
const auto* name = viewStateName(view);
|
|
558
|
+
auto state = name ? view.table->states.find(*name) : view.table->states.items().end();
|
|
559
|
+
out["nativeProps"] = state != view.table->states.items().end()
|
|
560
|
+
? state->second
|
|
561
|
+
: folly::dynamic(nullptr);
|
|
562
|
+
return jsi::valueFromDynamic(rt, out);
|
|
563
|
+
}
|
|
362
564
|
const auto& family = view.node->getFamily();
|
|
565
|
+
std::lock_guard<std::mutex> lock(family.nativePropsMutex);
|
|
363
566
|
out["nativeProps"] = family.nativeProps_DEPRECATED
|
|
364
567
|
? *family.nativeProps_DEPRECATED
|
|
365
568
|
: folly::dynamic(nullptr);
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
#include <jsi/jsi.h>
|
|
7
7
|
#include <react/renderer/core/ShadowNode.h>
|
|
8
8
|
|
|
9
|
+
#include <cstdint>
|
|
10
|
+
#include <memory>
|
|
11
|
+
#include <mutex>
|
|
9
12
|
#include <string>
|
|
10
13
|
#include <unordered_map>
|
|
11
14
|
|
|
@@ -38,9 +41,30 @@ namespace jsi = facebook::jsi;
|
|
|
38
41
|
*/
|
|
39
42
|
class HybridTamaguiRegistry : public HybridTamaguiRegistrySpec {
|
|
40
43
|
public:
|
|
44
|
+
// one view's props for a commit. table views point at their table's
|
|
45
|
+
// state entry, shared by every view of the table, so the commit can parse
|
|
46
|
+
// each distinct (props, update) pair once; per-view slots own theirs
|
|
47
|
+
// the props a table view committed per state. while the node still carries
|
|
48
|
+
// the props this engine committed last, a switch back to a state reuses
|
|
49
|
+
// that state's props instead of parsing them again; a React render or a
|
|
50
|
+
// table refill invalidates them
|
|
51
|
+
struct WarmProps {
|
|
52
|
+
facebook::react::Props::Shared last;
|
|
53
|
+
uint64_t revision = 0;
|
|
54
|
+
std::unordered_map<std::string, facebook::react::Props::Shared> byState;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
struct LeafUpdate {
|
|
58
|
+
const folly::dynamic* shared = nullptr;
|
|
59
|
+
folly::dynamic owned;
|
|
60
|
+
// table views only
|
|
61
|
+
WarmProps* warm = nullptr;
|
|
62
|
+
const std::string* state = nullptr;
|
|
63
|
+
uint64_t revision = 0;
|
|
64
|
+
const folly::dynamic& props() const { return shared ? *shared : owned; }
|
|
65
|
+
};
|
|
41
66
|
using LeafUpdates =
|
|
42
|
-
std::unordered_map<const facebook::react::ShadowNodeFamily*,
|
|
43
|
-
folly::dynamic>;
|
|
67
|
+
std::unordered_map<const facebook::react::ShadowNodeFamily*, LeafUpdate>;
|
|
44
68
|
|
|
45
69
|
HybridTamaguiRegistry() : HybridObject(TAG) {}
|
|
46
70
|
|
|
@@ -56,6 +80,14 @@ class HybridTamaguiRegistry : public HybridTamaguiRegistrySpec {
|
|
|
56
80
|
double getMissCount() override;
|
|
57
81
|
|
|
58
82
|
private:
|
|
83
|
+
// one state table shared by every view a compiler mapping links: state
|
|
84
|
+
// name -> props. filled once per mapping and state, never per view
|
|
85
|
+
struct Table {
|
|
86
|
+
folly::dynamic states = folly::dynamic::object();
|
|
87
|
+
// bumped on every refill so views drop props parsed from older states
|
|
88
|
+
uint64_t revision = 0;
|
|
89
|
+
};
|
|
90
|
+
|
|
59
91
|
struct LinkedView {
|
|
60
92
|
// retained so the family stays valid for in-transaction ancestor lookup;
|
|
61
93
|
// unmounted views resolve to no ancestors and are skipped
|
|
@@ -63,13 +95,19 @@ class HybridTamaguiRegistry : public HybridTamaguiRegistrySpec {
|
|
|
63
95
|
std::string scopeId;
|
|
64
96
|
folly::dynamic baseProps; // object, or nullptr when absent
|
|
65
97
|
folly::dynamic stateProps; // stateName -> props object, or nullptr
|
|
98
|
+
// shared table (compiler mappings); when set it replaces base/stateProps
|
|
99
|
+
// and a commit carries only the state's keys, React already rendered the rest
|
|
100
|
+
std::shared_ptr<Table> table;
|
|
66
101
|
// per-view active state (runtime mode). empty = follow scope state.
|
|
67
102
|
// runtime integrations resolve state names per view (nested themes);
|
|
68
103
|
// scope broadcast stays for compiler-emitted tables.
|
|
69
104
|
std::string activeState;
|
|
105
|
+
WarmProps warm;
|
|
70
106
|
};
|
|
71
107
|
|
|
72
|
-
// raw JSI: link(shadowNode, slots, scopeId) -> id
|
|
108
|
+
// raw JSI: link(shadowNode, slots | tableId, scopeId, commitNow?) -> id
|
|
109
|
+
// commitNow commits the view's active state once (its render raced a
|
|
110
|
+
// scope change)
|
|
73
111
|
jsi::Value link(jsi::Runtime& rt, const jsi::Value& thisValue,
|
|
74
112
|
const jsi::Value* args, size_t count);
|
|
75
113
|
|
|
@@ -77,15 +115,16 @@ class HybridTamaguiRegistry : public HybridTamaguiRegistrySpec {
|
|
|
77
115
|
// batched per-view state selection: for each entry, `props` (when given)
|
|
78
116
|
// replaces the view's state table entry under `state`, the view's active
|
|
79
117
|
// state becomes `state`, and all changed views commit in ONE transaction.
|
|
118
|
+
jsi::Value linkTables(jsi::Runtime& rt, const jsi::Value& thisValue,
|
|
119
|
+
const jsi::Value* args, size_t count);
|
|
120
|
+
|
|
80
121
|
jsi::Value applyViewStates(jsi::Runtime& rt, const jsi::Value& thisValue,
|
|
81
122
|
const jsi::Value* args, size_t count);
|
|
82
123
|
|
|
83
|
-
// raw JSI:
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const jsi::Value& thisValue,
|
|
88
|
-
const jsi::Value* args, size_t count);
|
|
124
|
+
// raw JSI: setTableState(tableId, state, props) -> void
|
|
125
|
+
// fills one state of a shared table; linked views showing that state commit
|
|
126
|
+
jsi::Value setTableState(jsi::Runtime& rt, const jsi::Value& thisValue,
|
|
127
|
+
const jsi::Value* args, size_t count);
|
|
89
128
|
|
|
90
129
|
// raw JSI: getViewState(id) -> {scopeId, activeState, base, states} | null
|
|
91
130
|
// debug/test introspection of a linked view's tables; not a hot path
|
|
@@ -106,7 +145,11 @@ class HybridTamaguiRegistry : public HybridTamaguiRegistrySpec {
|
|
|
106
145
|
// commit current props for all views (scopeFilter == nullptr) or one scope
|
|
107
146
|
void applyUpdates(jsi::Runtime& rt, const std::string* scopeFilter);
|
|
108
147
|
|
|
148
|
+
// the state a view shows right now: its own runtime state, else its scope's
|
|
149
|
+
const std::string* viewStateName(const LinkedView& view) const;
|
|
150
|
+
|
|
109
151
|
std::unordered_map<double, LinkedView> views_;
|
|
152
|
+
std::unordered_map<double, std::shared_ptr<Table>> tables_;
|
|
110
153
|
std::unordered_map<std::string, std::string> scopeStates_; // "" = root
|
|
111
154
|
double nextId_ = 1;
|
|
112
155
|
double commitCount_ = 0;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -27,11 +27,13 @@ __export(index_exports, {
|
|
|
27
27
|
getViewState: () => getViewState,
|
|
28
28
|
isAvailable: () => isAvailable,
|
|
29
29
|
link: () => link,
|
|
30
|
+
linkTables: () => linkTables,
|
|
30
31
|
processStyleColors: () => processStyleColors,
|
|
31
32
|
removeScope: () => removeScope,
|
|
32
33
|
resolveSlots: () => resolveSlots,
|
|
33
34
|
setStateName: () => setStateName,
|
|
34
|
-
|
|
35
|
+
setTableState: () => setTableState,
|
|
36
|
+
unlink: () => unlink
|
|
35
37
|
});
|
|
36
38
|
module.exports = __toCommonJS(index_exports);
|
|
37
39
|
var import_mirror = require("./mirror.cjs");
|
|
@@ -39,11 +41,15 @@ var import_mirror2 = require("./mirror.cjs");
|
|
|
39
41
|
function isAvailable() {
|
|
40
42
|
return false;
|
|
41
43
|
}
|
|
42
|
-
function link(_ref,
|
|
44
|
+
function link(_ref, _table, _scopeId, _commitNow) {
|
|
43
45
|
return null;
|
|
44
46
|
}
|
|
47
|
+
function linkTables(_refs, _tableIds, _scopeIndexes, _scopes) {
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
function unlink(_id) {}
|
|
45
51
|
function applyViewStates(_entries) {}
|
|
46
|
-
function
|
|
52
|
+
function setTableState(_tableId, _state, _props) {}
|
|
47
53
|
function getViewState(_id) {
|
|
48
54
|
return null;
|
|
49
55
|
}
|