@symbiote-native/engine 1.3.0 → 1.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.
- package/build/accessibility-props.d.ts +0 -11
- package/build/accessibility-props.js +30 -68
- package/build/animated/graph.js +1 -1
- package/build/animated/leaf-lifecycle.js +2 -2
- package/build/asset-source-resolver.d.ts +2 -0
- package/build/asset-source-resolver.js +13 -0
- package/build/back-handler/index.d.ts +1 -5
- package/build/back-handler/index.js +0 -6
- package/build/debug.js +8 -22
- package/build/dispatch.js +3 -9
- package/build/events/delivery.d.ts +9 -0
- package/build/events/delivery.js +143 -0
- package/build/events/index.js +199 -660
- package/build/events/names.d.ts +24 -0
- package/build/events/names.js +80 -0
- package/build/events/press.d.ts +20 -0
- package/build/events/press.js +89 -0
- package/build/events/responder.d.ts +6 -0
- package/build/events/responder.js +124 -0
- package/build/fabric-props.js +74 -179
- package/build/fabric.d.ts +0 -13
- package/build/fabric.js +18 -38
- package/build/host-access.d.ts +1 -128
- package/build/host-access.js +96 -205
- package/build/host-behavior.d.ts +0 -100
- package/build/host-behavior.js +125 -311
- package/build/image-loader.js +10 -23
- package/build/image-source-resolver.js +3 -7
- package/build/image-source-write.d.ts +0 -11
- package/build/image-source-write.js +14 -34
- package/build/imperative.d.ts +2 -28
- package/build/imperative.js +60 -93
- package/build/index.d.ts +6 -2
- package/build/index.js +29 -39
- package/build/mutation-buffer.d.ts +3 -177
- package/build/mutation-buffer.js +162 -316
- package/build/native-engine.d.ts +6 -102
- package/build/native-engine.js +60 -141
- package/build/native-events.js +9 -18
- package/build/native-tree-host.d.ts +0 -21
- package/build/native-tree-host.js +15 -31
- package/build/node-events.d.ts +11 -0
- package/build/node-events.js +145 -0
- package/build/node-instance.d.ts +8 -0
- package/build/node-instance.js +168 -0
- package/build/node-props.d.ts +13 -0
- package/build/node-props.js +131 -0
- package/build/node-route.d.ts +2 -0
- package/build/node-route.js +151 -0
- package/build/node-style.d.ts +15 -0
- package/build/node-style.js +214 -0
- package/build/node-tree.d.ts +6 -0
- package/build/node-tree.js +159 -0
- package/build/node-types.d.ts +70 -0
- package/build/node-types.js +36 -0
- package/build/node.d.ts +7 -309
- package/build/node.js +9 -1564
- package/build/post-commit.js +3 -8
- package/build/process-aspect-ratio.js +3 -7
- package/build/process-background-longhands.js +10 -19
- package/build/process-filter.js +11 -19
- package/build/process-font-variant.js +3 -7
- package/build/registry.d.ts +0 -33
- package/build/registry.js +22 -57
- package/build/report-error.js +4 -18
- package/build/structured-style.d.ts +0 -9
- package/build/structured-style.js +16 -31
- package/build/styles.js +3 -6
- package/build/surface.d.ts +0 -26
- package/build/surface.js +29 -76
- package/build/text-input-state.js +4 -8
- package/build/touch-history.js +5 -11
- package/build/tree-host.d.ts +7 -270
- package/build/tree-host.js +63 -153
- package/build/view-config.js +17 -37
- package/cpp/SymbioteEngineBindings.cpp +19 -18
- package/cpp/SymbioteTree.cpp +81 -156
- package/cpp/SymbioteTree.h +6 -0
- package/package.json +2 -2
package/cpp/SymbioteTree.cpp
CHANGED
|
@@ -1540,33 +1540,16 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1540
1540
|
|
|
1541
1541
|
size_t opsLength = 0;
|
|
1542
1542
|
const int32_t *ops = int32ArrayData(runtime, arguments[0], opsLength);
|
|
1543
|
-
//
|
|
1544
|
-
//
|
|
1545
|
-
// `getObject`/`getArray` rather than `asObject`/`asArray`. The checking pair runs an `isObject`
|
|
1546
|
-
// and an `isArray` per table — eight JSI round trips for four arguments — and they were 2.8 us of
|
|
1547
|
-
// a 4.4 us fixed entry cost. Measured on an EMPTY batch against a 0.13 us bare host call
|
|
1548
|
-
// (`small-batch-crossing-cost.itest.ts`): prologue 4.38 -> 1.54 us, and a whole small drain
|
|
1549
|
-
// 5.54 -> 2.76 us.
|
|
1550
|
-
//
|
|
1551
|
-
// WHY IT IS SAFE TO DROP THEM, and it is the harness's own split rather than a shrug: `takeBatch`
|
|
1552
|
-
// is the only producer on this wire and always hands over four arrays, and `jsi::Value::getObject`
|
|
1553
|
-
// / `Object::getArray` carry `assert`s that are LIVE in the correctness build — `core/engine/cpp/
|
|
1554
|
-
// tests/build` is Debug with `NDEBUG` off, which is the whole reason it exists. So a fixture that
|
|
1555
|
-
// hand-builds a malformed batch aborts there and the build that ships pays nothing for the check.
|
|
1556
|
-
//
|
|
1557
|
-
// WHAT IT COSTS ANYONE: a framework that navigates between mutations pays this entry per
|
|
1558
|
-
// mutation, not per commit. Solid's `cleanChildren` enters `applyOps` 2 000 times to clear a
|
|
1559
|
-
// thousand rows.
|
|
1543
|
+
// `getObject`/`getArray` and never the checking pair, which was 2.8 us of a 4.4 us entry. Why
|
|
1544
|
+
// that is safe: `symbiote-engine-op-application` skill, "The prologue"
|
|
1560
1545
|
auto strings = arguments[1].getObject(runtime).getArray(runtime);
|
|
1561
1546
|
auto values = arguments[2].getObject(runtime).getArray(runtime);
|
|
1562
1547
|
auto instanceHandles = arguments[3].getObject(runtime).getArray(runtime);
|
|
1563
1548
|
auto handles = arguments[4].getObject(runtime).getArray(runtime);
|
|
1564
1549
|
const size_t slotCount = handles.size(runtime);
|
|
1565
1550
|
|
|
1566
|
-
// Slot -> node, resolved at most ONCE per batch and usually not at all
|
|
1567
|
-
//
|
|
1568
|
-
// nearly every slot. Only a slot naming a node an EARLIER batch created costs a read, and it costs
|
|
1569
|
-
// exactly one however many ops go on to name it.
|
|
1551
|
+
// Slot -> node, resolved at most ONCE per batch and usually not at all. See the skill,
|
|
1552
|
+
// "The three per-batch tables"
|
|
1570
1553
|
std::vector<NodePtr> bySlot(slotCount);
|
|
1571
1554
|
|
|
1572
1555
|
auto checkSlot = [&](int32_t slot) -> size_t {
|
|
@@ -1606,18 +1589,8 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1606
1589
|
walkCost_.publishNs += nanosSince(publishStartedAt);
|
|
1607
1590
|
};
|
|
1608
1591
|
|
|
1609
|
-
// Decoded ONCE per batch, not once per op that names a string.
|
|
1610
|
-
//
|
|
1611
|
-
// This used to read the JSI array and allocate a fresh `std::string` inside the op loop, which
|
|
1612
|
-
// spent exactly the saving `mutation-buffer.ts` interns for: its own comment says a 1 000-row
|
|
1613
|
-
// create emits about a dozen distinct view names across 10 000 elements and draws every prop key
|
|
1614
|
-
// from a set of a few hundred, and none of that reached here. Counted through a real adapter
|
|
1615
|
-
// (`adapters/solid/src/batch-decode-census.probe.test.tsx`): 16 005 decodes against a table of
|
|
1616
|
-
// 2 008 entries on a create, and the same 8.0x on an append.
|
|
1617
|
-
//
|
|
1618
|
-
// Two costs go, and only one of them is measurable without a device. The allocation half a bench
|
|
1619
|
-
// puts at 3.26x for the whole path (`core/engine/bench/batch-string-decode.cpp`); the other half
|
|
1620
|
-
// is 13 997 JSI crossings that simply stop happening, and nothing headless can price those.
|
|
1592
|
+
// Decoded ONCE per batch, not once per op that names a string. The 8.0x this turns on, and what
|
|
1593
|
+
// is not measurable headless: the skill, "The three per-batch tables"
|
|
1621
1594
|
std::vector<std::string> decodedStrings;
|
|
1622
1595
|
{
|
|
1623
1596
|
const auto stringsStartedAt = ISteadyClock::now();
|
|
@@ -1630,17 +1603,8 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1630
1603
|
walkCost_.stringDecodeNs += nanosSince(stringsStartedAt);
|
|
1631
1604
|
}
|
|
1632
1605
|
|
|
1633
|
-
// Prop VALUES, converted at most once per entry per batch
|
|
1634
|
-
//
|
|
1635
|
-
// many nodes were handed it, so a `StyleSheet.create` style shared by a thousand rows arrives as
|
|
1636
|
-
// one entry; this is what turns that into one conversion instead of a thousand identical ones.
|
|
1637
|
-
//
|
|
1638
|
-
// LAZY rather than eager, unlike the strings above: a batch's value table can hold entries no
|
|
1639
|
-
// surviving op names — a prop written and then overwritten in the same batch — and converting one
|
|
1640
|
-
// eagerly would charge for work the ops do not ask for. The strings table has no such shape.
|
|
1641
|
-
//
|
|
1642
|
-
// One consequence worth knowing when a conversion throws: `boundedDynamicFrom`'s message names the
|
|
1643
|
-
// prop and view of the FIRST op to reach a given entry, not every op that shares it.
|
|
1606
|
+
// Prop VALUES, converted at most once per entry per batch, and LAZILY unlike the strings above.
|
|
1607
|
+
// Why lazily, and what that costs a thrown conversion: the skill, "The three per-batch tables"
|
|
1644
1608
|
std::vector<folly::dynamic> convertedValues(values.size(runtime));
|
|
1645
1609
|
std::vector<bool> valueIsConverted(convertedValues.size(), false);
|
|
1646
1610
|
walkCost_.valueEntries += convertedValues.size();
|
|
@@ -1657,10 +1621,8 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1657
1621
|
return decodedStrings[static_cast<size_t>(index)];
|
|
1658
1622
|
};
|
|
1659
1623
|
|
|
1660
|
-
// `auto &&describe` and not a `std::function`: the description must stay a lambda the compiler
|
|
1661
|
-
// inline away
|
|
1662
|
-
// eagerly was 48.8% of the decode path once, and a `std::function` per op would allocate to
|
|
1663
|
-
// reintroduce half of it.
|
|
1624
|
+
// `auto &&describe` and not a `std::function`: the description must stay a lambda the compiler
|
|
1625
|
+
// can inline away. See `boundedDynamicFrom`, and the skill for the 48.8% behind it
|
|
1664
1626
|
auto valueAt = [&](int32_t index, auto &&describe) -> const folly::dynamic & {
|
|
1665
1627
|
if (index < 0 || static_cast<size_t>(index) >= convertedValues.size()) {
|
|
1666
1628
|
throw jsi::JSError(
|
|
@@ -1729,8 +1691,8 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1729
1691
|
const auto holdStartedAt = ISteadyClock::now();
|
|
1730
1692
|
holdHandle(runtime, *child);
|
|
1731
1693
|
walkCost_.holdHandleNs += nanosSince(holdStartedAt);
|
|
1732
|
-
//
|
|
1733
|
-
// its place until the next read compacts
|
|
1694
|
+
// A HINT the detach path reads back, and appending past a hole is harmless: the hole keeps
|
|
1695
|
+
// its place until the next read compacts
|
|
1734
1696
|
child->slotInParent = parent->children.size();
|
|
1735
1697
|
parent->children.push_back(std::move(child));
|
|
1736
1698
|
markDirty(*parent);
|
|
@@ -1741,10 +1703,8 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1741
1703
|
const auto &parent = nodeAt(ops[at + 1]);
|
|
1742
1704
|
auto child = nodeAt(ops[at + 2]);
|
|
1743
1705
|
const auto &before = nodeAt(ops[at + 3]);
|
|
1744
|
-
// A MOVE WITHIN THE SAME PARENT ERASES RATHER THAN PUNCHING A HOLE,
|
|
1745
|
-
//
|
|
1746
|
-
// shift, which measured 2.4x worse on a 4 000-row reorder than simply erasing. A move to a
|
|
1747
|
-
// DIFFERENT parent holes the old one as usual — nothing is about to shift it.
|
|
1706
|
+
// A MOVE WITHIN THE SAME PARENT ERASES RATHER THAN PUNCHING A HOLE, 2.4x better on a
|
|
1707
|
+
// 4 000-row reorder. See the skill, "Child-vector maintenance"
|
|
1748
1708
|
if (child->parent == parent.get()) {
|
|
1749
1709
|
auto &standing = parent->children;
|
|
1750
1710
|
const size_t hinted = child->slotInParent;
|
|
@@ -1770,60 +1730,34 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1770
1730
|
std::find(siblings.begin(), siblings.end(), before) - siblings.begin());
|
|
1771
1731
|
siblings.insert(siblings.begin() + static_cast<std::ptrdiff_t>(index), std::move(child));
|
|
1772
1732
|
siblings[index]->slotInParent = index;
|
|
1773
|
-
// THE TAIL'S HINTS ARE NOW ONE TOO LOW
|
|
1774
|
-
//
|
|
1775
|
-
// Renumbering them is O(width) per insert, which was tried and made a reorder of 4 000 rows
|
|
1776
|
-
// 34.6 ms against 6.7 — five times worse, to keep a hint exact that nothing requires to be.
|
|
1777
|
-
// `detachFromParent` validates before it believes (`siblings[hinted] == child`) and falls
|
|
1778
|
-
// back to a scan, so a stale hint costs one detach its old price and never costs correctness.
|
|
1779
|
-
//
|
|
1780
|
-
// What IS still linear here is the vector insert itself. Finding the anchor is a load now;
|
|
1781
|
-
// making the insert a load needs a different container, not a different search.
|
|
1733
|
+
// THE TAIL'S HINTS ARE NOW ONE TOO LOW AND ARE LEFT THAT WAY: renumbering was five times
|
|
1734
|
+
// worse on a 4 000-row reorder. See the skill, "Child-vector maintenance"
|
|
1782
1735
|
markDirty(*parent);
|
|
1783
1736
|
break;
|
|
1784
1737
|
}
|
|
1785
1738
|
case kOpRemoveChild: {
|
|
1786
1739
|
const auto &parent = nodeAt(ops[at + 1]);
|
|
1787
1740
|
auto child = nodeAt(ops[at + 2]);
|
|
1788
|
-
//
|
|
1789
|
-
//
|
|
1790
|
-
// remove-then-insert and can arrive here after the insert already re-parented the node.
|
|
1741
|
+
// The child's OWN parent pointer, which is the truth even when the adapter names a stale
|
|
1742
|
+
// one. See the skill, "Child-vector maintenance"
|
|
1791
1743
|
if (child->parent == parent.get()) {
|
|
1792
1744
|
detachFromParent(child);
|
|
1793
|
-
//
|
|
1794
|
-
//
|
|
1795
|
-
// pin there would leave a window, mid-batch, where the node is in no tree and a
|
|
1796
|
-
// collection could take the handle a re-attach is about to need.
|
|
1745
|
+
// Released HERE and not inside `detachFromParent`, which the attach ops also call to
|
|
1746
|
+
// spell a MOVE. Why that window would matter: the skill
|
|
1797
1747
|
child->attachedHandle.reset();
|
|
1798
1748
|
}
|
|
1799
1749
|
break;
|
|
1800
1750
|
}
|
|
1801
|
-
// Writing a value the node already holds
|
|
1802
|
-
//
|
|
1803
|
-
//
|
|
1804
|
-
// of them of the reuse fast path, so an otherwise untouched subtree gets rebuilt purely to
|
|
1805
|
-
// prove it is untouched. Measured: Angular's Pressable host bag pushed 104 000 setProp calls
|
|
1806
|
-
// for a screen Solid built in 12 000, 90 000 of them writing `undefined` over an absent key.
|
|
1807
|
-
//
|
|
1808
|
-
// The guard lives HERE and not in the engine's `setProp` because it needs the value the node
|
|
1809
|
-
// already holds — a read JS would have to make over the wire, ~44 001 times on a 1 000-row
|
|
1810
|
-
// create, which is exactly the traffic this design removes.
|
|
1811
|
-
//
|
|
1812
|
-
// ONE DELIBERATE ASYMMETRY with the reference applier, and it is in the safe direction. TS
|
|
1813
|
-
// compares with `Object.is`, so for a style object or a handler the guard simply never fires:
|
|
1814
|
-
// an adapter may hand back the SAME reference with mutated contents, and identity cannot see
|
|
1815
|
-
// that. Here the value is a fresh `folly::dynamic` copied off the JSI value, so nothing can
|
|
1816
|
-
// mutate it behind us and a deep compare is both available and correct. It therefore turns
|
|
1817
|
-
// away strictly MORE writes than TS does. That changes the work, never the committed tree —
|
|
1818
|
-
// `diffProps` drops an unchanged key either way — so the two still agree on output.
|
|
1751
|
+
// Writing a value the node already holds returns before `markDirty`, т.к. the MARK is what
|
|
1752
|
+
// costs: it strips every ancestor of the reuse fast path. Why the guard is here and not in
|
|
1753
|
+
// JS, and the one asymmetry with the TS applier: the skill, "The two guards"
|
|
1819
1754
|
case kOpSetProp: {
|
|
1820
1755
|
const auto setPropStartedAt = ISteadyClock::now();
|
|
1821
1756
|
const auto &node = nodeAt(ops[at + 1]);
|
|
1822
1757
|
const auto &key = stringAt(ops[at + 2]);
|
|
1823
1758
|
if (ops[at + 3] == kNoValue) {
|
|
1824
|
-
// An absent key is not a key holding null
|
|
1825
|
-
//
|
|
1826
|
-
// vanished key has to go out as an explicit null.
|
|
1759
|
+
// An absent key is not a key holding null, and a vanished one goes out as an explicit
|
|
1760
|
+
// null. See the skill, "The two guards"
|
|
1827
1761
|
if (node->props.get_ptr(key) == nullptr) {
|
|
1828
1762
|
walkCost_.deletesOfAbsent += 1;
|
|
1829
1763
|
break;
|
|
@@ -1838,9 +1772,7 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1838
1772
|
walkCost_.writesOfUnchanged += 1;
|
|
1839
1773
|
break;
|
|
1840
1774
|
}
|
|
1841
|
-
// A COPY
|
|
1842
|
-
// handed to, so it has to survive this op. One `folly::dynamic` copy against one JS ->
|
|
1843
|
-
// dynamic conversion, and the conversion is the JSI crossing.
|
|
1775
|
+
// A COPY and not a move: the entry is shared by every node the same object was handed to
|
|
1844
1776
|
node->props[key] = value;
|
|
1845
1777
|
}
|
|
1846
1778
|
markDirty(*node);
|
|
@@ -1848,14 +1780,8 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1848
1780
|
walkCost_.setProps += 1;
|
|
1849
1781
|
break;
|
|
1850
1782
|
}
|
|
1851
|
-
// The
|
|
1852
|
-
//
|
|
1853
|
-
// hands back an unchanged label — every list row whose text did not move, on every update —
|
|
1854
|
-
// stops dirtying its ancestors.
|
|
1855
|
-
// The one op that changes what a node IS rather than what it holds. `materialize`'s
|
|
1856
|
-
// `needsFreshFamily` already covers the consequence — a name differing from
|
|
1857
|
-
// `committedViewName` re-creates the node and re-parents its children — so this only moves the
|
|
1858
|
-
// name and marks. `TextInput`'s `multiline` flip is the whole reason it exists.
|
|
1783
|
+
// The one op that changes what a node IS rather than what it holds, and `materialize`'s
|
|
1784
|
+
// `needsFreshFamily` covers the consequence. `TextInput`'s `multiline` flip is why it exists
|
|
1859
1785
|
case kOpSetComponent: {
|
|
1860
1786
|
const auto &node = nodeAt(ops[at + 1]);
|
|
1861
1787
|
const auto &viewName = stringAt(ops[at + 2]);
|
|
@@ -1864,19 +1790,15 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1864
1790
|
markDirty(*node);
|
|
1865
1791
|
break;
|
|
1866
1792
|
}
|
|
1867
|
-
// No `markDirty`: this arrives at `createElement`, before
|
|
1868
|
-
//
|
|
1793
|
+
// No `markDirty`: this arrives at `createElement`, before the node's first commit, so there
|
|
1794
|
+
// is no payload yet. The table of which ops mark and why is in the skill
|
|
1869
1795
|
case kOpSetTag: {
|
|
1870
1796
|
const auto &node = nodeAt(ops[at + 1]);
|
|
1871
1797
|
node->tagName = stringAt(ops[at + 2]);
|
|
1872
1798
|
break;
|
|
1873
1799
|
}
|
|
1874
|
-
// `markDirty`, unlike `kOpSetTag
|
|
1875
|
-
//
|
|
1876
|
-
// there is no payload yet to invalidate. A listener can flip at any point in a screen's life —
|
|
1877
|
-
// a row that becomes pressable once its data loads — and the key it decides is already
|
|
1878
|
-
// committed by then. Without this the control renders permanently unfocusable while visibly
|
|
1879
|
-
// interactive, and nothing else in the batch would mark it: a listener is not a prop write.
|
|
1800
|
+
// `markDirty`, unlike `kOpSetTag`, т.к. a listener can flip at any point in a screen's life
|
|
1801
|
+
// and the key it decides is committed by then. See the skill
|
|
1880
1802
|
case kOpSetOwnedListener: {
|
|
1881
1803
|
const auto &node = nodeAt(ops[at + 1]);
|
|
1882
1804
|
// Only the names a platform rule actually reads. Anything else is a JS-side concern that
|
|
@@ -1898,13 +1820,8 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1898
1820
|
if (node->underlayShown == shown) break;
|
|
1899
1821
|
node->underlayShown = shown;
|
|
1900
1822
|
markDirty(*node);
|
|
1901
|
-
// AND THE CHILD,
|
|
1902
|
-
//
|
|
1903
|
-
// rule runs when ITS node is dirty, so without this the child would freeze in its unpressed
|
|
1904
|
-
// shape and never dim — the hazard `ownerProps` already carries for ScrollView's content.
|
|
1905
|
-
//
|
|
1906
|
-
// FIRST child and no walk: RN takes `React.Children.only` (`TouchableHighlight.js:306`), so
|
|
1907
|
-
// one is the whole population rather than a simplification. Twice per tap, not per frame.
|
|
1823
|
+
// AND THE CHILD, т.к. the bit drives a rule on BOTH (`foldTouchableHighlightChild`), and
|
|
1824
|
+
// the FIRST child is the whole population since RN takes `React.Children.only`
|
|
1908
1825
|
if (!node->children.empty()) {
|
|
1909
1826
|
compactChildren(*node);
|
|
1910
1827
|
if (!node->children.empty() && node->children.front() != nullptr)
|
|
@@ -1917,16 +1834,8 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1917
1834
|
const auto &text = stringAt(ops[at + 2]);
|
|
1918
1835
|
const auto *existing = node->props.get_ptr("text");
|
|
1919
1836
|
if (existing != nullptr && existing->isString() && existing->asString() == text) break;
|
|
1920
|
-
// Read BEFORE the write, and only a FLIP marks the parent
|
|
1921
|
-
//
|
|
1922
|
-
// A write to or from '' takes this node out of its parent's renderable child list or puts it
|
|
1923
|
-
// back, which is a structural change to the PARENT that nothing else here would record.
|
|
1924
|
-
// Marking unconditionally made every ordinary relabel do it too, and `markDirty` sets the
|
|
1925
|
-
// parent's SELF-dirty bit — which forces a full `fabricProps` + `diffProps` on a node whose
|
|
1926
|
-
// own props did not move. Counted through three adapters on a 1 000-row relabel
|
|
1927
|
-
// (`adapters/*/src/work-ledger.probe.test.*`): 3 000 payload keys rebuilt to send 1 000.
|
|
1928
|
-
// The walk still reaches this node either way, because `markDirty(*node)` raises
|
|
1929
|
-
// `pathDirty` on every ancestor.
|
|
1837
|
+
// Read BEFORE the write, and only a FLIP to or from `''` marks the parent, т.к. that is
|
|
1838
|
+
// what moves the node in or out of its parent's renderable list. See the skill
|
|
1930
1839
|
const bool wasEmpty =
|
|
1931
1840
|
existing == nullptr || !existing->isString() || existing->asString().empty();
|
|
1932
1841
|
node->props["text"] = text;
|
|
@@ -1938,43 +1847,25 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1938
1847
|
const auto surfaceId = static_cast<react::SurfaceId>(ops[at + 1]);
|
|
1939
1848
|
const auto &surface = nodeAt(ops[at + 2]);
|
|
1940
1849
|
auto childSet = std::make_shared<ChildSet>();
|
|
1941
|
-
// The surface NODE is contributed, not its children
|
|
1942
|
-
//
|
|
1943
|
-
// call keeps the two shapes one path: an ANCHOR in this position hoists its children.
|
|
1944
|
-
//
|
|
1945
|
-
// `nullptr` as the Fabric parent: the root CHILD SET is not a node. So a top-level node
|
|
1946
|
-
// moving between two surfaces is NOT caught by the parent comparison — both sides are
|
|
1947
|
-
// `nullptr` — and the surface id is what separates them, which is why `materialize`
|
|
1948
|
-
// compares that too.
|
|
1949
|
-
// The root child set goes to `completeSurface`, which commits it through a transaction we
|
|
1950
|
-
// never see the result of — so there is nothing to adopt back here, and these owners are
|
|
1951
|
-
// collected only because `appendRenderable` needs somewhere to put them.
|
|
1850
|
+
// The surface NODE is contributed, not its children, and `nullptr` stands for the root
|
|
1851
|
+
// child set т.к. it is not a node. What that costs a cross-surface move: the skill
|
|
1952
1852
|
IOwnerTally rootOwners;
|
|
1953
|
-
// THE ONE TIMER THAT IS NOT PER NODE,
|
|
1954
|
-
// `materialize
|
|
1955
|
-
// every ancestor's time again for every descendant. This is the walk's single entry point.
|
|
1853
|
+
// THE ONE TIMER THAT IS NOT PER NODE, т.к. the walk is recursive and a timer inside
|
|
1854
|
+
// `materialize` would count every ancestor again for every descendant
|
|
1956
1855
|
const auto walkStartedAt = ISteadyClock::now();
|
|
1957
1856
|
appendRenderable(
|
|
1958
1857
|
runtime, uiManager, *childSet, rootOwners, *surface, false, surfaceId, nullptr);
|
|
1959
1858
|
walkCost_.walkNs += nanosSince(walkStartedAt);
|
|
1960
|
-
// SKIPPED when the root child set comes back identical
|
|
1961
|
-
//
|
|
1962
|
-
// `completeSurface` on them is a full `ShadowTree::commit`, with layout and a mount pass,
|
|
1963
|
-
// for no change at all.
|
|
1964
|
-
//
|
|
1965
|
-
// This is what makes the JS side's commit fan-out free: every commit names every live root,
|
|
1966
|
-
// because a cross-surface mutation dirties a surface whose renderer nobody is holding
|
|
1967
|
-
// (`commitSurfaceOps` in tree-host.ts). An untouched root reaches here with an identical
|
|
1968
|
-
// list and stops.
|
|
1859
|
+
// SKIPPED when the root child set comes back identical, which is what makes the JS side's
|
|
1860
|
+
// commit fan-out free. See the skill, "kOpCommit"
|
|
1969
1861
|
if (surface->hasCommittedRenderable &&
|
|
1970
1862
|
sameNodes(surface->committedRenderable, *childSet)) {
|
|
1971
1863
|
break;
|
|
1972
1864
|
}
|
|
1973
1865
|
surface->committedRenderable = *childSet;
|
|
1974
1866
|
surface->hasCommittedRenderable = true;
|
|
1975
|
-
// `completeSurface` runs `ShadowTree::commit`
|
|
1976
|
-
// children
|
|
1977
|
-
// rebase. That is why this needs neither a commit hook nor a retained pending root.
|
|
1867
|
+
// `completeSurface` runs `ShadowTree::commit` with a lambda that REPLACES the root's
|
|
1868
|
+
// children, so this needs neither a commit hook nor a retained pending root
|
|
1978
1869
|
uiManager.completeSurface(
|
|
1979
1870
|
surfaceId,
|
|
1980
1871
|
childSet,
|
|
@@ -1983,9 +1874,8 @@ jsi::Value Tree::applyOps(jsi::Runtime &runtime, const jsi::Value *arguments, si
|
|
|
1983
1874
|
.source = react::ShadowTree::CommitSource::React});
|
|
1984
1875
|
uiManager.getShadowTreeRegistry().visit(
|
|
1985
1876
|
surfaceId, [&rootOwners](const react::ShadowTree &shadowTree) {
|
|
1986
|
-
// THE REPAIR,
|
|
1987
|
-
//
|
|
1988
|
-
// holds once `completeSurface` has returned. See `adoptCommitted`.
|
|
1877
|
+
// THE REPAIR, here and not in `materialize` т.к. substitution happens INSIDE the
|
|
1878
|
+
// commit. See `adoptCommitted`
|
|
1989
1879
|
const ChildSet &landedRoot =
|
|
1990
1880
|
shadowTree.getCurrentRevision().rootShadowNode->getChildren();
|
|
1991
1881
|
const size_t rootCount =
|
|
@@ -2408,6 +2298,10 @@ jsi::Value Tree::measureLayout(jsi::Runtime &runtime, const jsi::Value *, size_t
|
|
|
2408
2298
|
throw jsi::JSError(runtime, "symbiote engine: measureLayout is not built on this platform");
|
|
2409
2299
|
}
|
|
2410
2300
|
|
|
2301
|
+
jsi::Value Tree::getBoundingClientRect(jsi::Runtime &runtime, const jsi::Value *, size_t) {
|
|
2302
|
+
throw jsi::JSError(runtime, "symbiote engine: getBoundingClientRect is not built on this platform");
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2411
2305
|
#else
|
|
2412
2306
|
|
|
2413
2307
|
jsi::Value Tree::measure(jsi::Runtime &runtime, const jsi::Value *arguments, size_t count) {
|
|
@@ -2471,6 +2365,37 @@ jsi::Value Tree::measureInWindow(
|
|
|
2471
2365
|
return jsi::Value::undefined();
|
|
2472
2366
|
}
|
|
2473
2367
|
|
|
2368
|
+
// No callback, unlike its siblings above - the JS caller (imperative.ts) expects a direct return.
|
|
2369
|
+
// `undefined` (no revision yet) reads the same as an uncommitted node to that caller.
|
|
2370
|
+
jsi::Value Tree::getBoundingClientRect(
|
|
2371
|
+
jsi::Runtime &runtime,
|
|
2372
|
+
const jsi::Value *arguments,
|
|
2373
|
+
size_t count) {
|
|
2374
|
+
if (count < 2) {
|
|
2375
|
+
throw jsi::JSError(
|
|
2376
|
+
runtime, "symbiote engine: expected getBoundingClientRect(handle, includeTransform)");
|
|
2377
|
+
}
|
|
2378
|
+
const auto node = nodeFrom(runtime, arguments[0].asObject(runtime), "getBoundingClientRect");
|
|
2379
|
+
const bool includeTransform = arguments[1].getBool();
|
|
2380
|
+
|
|
2381
|
+
auto revision = node->committed == nullptr
|
|
2382
|
+
? nullptr
|
|
2383
|
+
: uiManagerFor(runtime, "getBoundingClientRect")
|
|
2384
|
+
.getShadowTreeRevisionProvider()
|
|
2385
|
+
->getCurrentRevision(node->committed->getSurfaceId());
|
|
2386
|
+
if (revision == nullptr) {
|
|
2387
|
+
return jsi::Value::undefined();
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
auto rect = react::dom::getBoundingClientRect(revision, *node->committed, includeTransform);
|
|
2391
|
+
auto result = jsi::Object(runtime);
|
|
2392
|
+
result.setProperty(runtime, "x", jsi::Value(rect.x));
|
|
2393
|
+
result.setProperty(runtime, "y", jsi::Value(rect.y));
|
|
2394
|
+
result.setProperty(runtime, "width", jsi::Value(rect.width));
|
|
2395
|
+
result.setProperty(runtime, "height", jsi::Value(rect.height));
|
|
2396
|
+
return result;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2474
2399
|
jsi::Value Tree::measureLayout(
|
|
2475
2400
|
jsi::Runtime &runtime,
|
|
2476
2401
|
const jsi::Value *arguments,
|
package/cpp/SymbioteTree.h
CHANGED
|
@@ -239,6 +239,12 @@ class Tree {
|
|
|
239
239
|
facebook::jsi::Runtime &runtime,
|
|
240
240
|
const facebook::jsi::Value *arguments,
|
|
241
241
|
size_t count);
|
|
242
|
+
// Synchronous, unlike the four above - no callback. Throws where SYMBIOTE_HAS_DOM_MEASURE is
|
|
243
|
+
// undetected, same as they do; JS callers must catch it (see imperative.ts).
|
|
244
|
+
facebook::jsi::Value getBoundingClientRect(
|
|
245
|
+
facebook::jsi::Runtime &runtime,
|
|
246
|
+
const facebook::jsi::Value *arguments,
|
|
247
|
+
size_t count);
|
|
242
248
|
facebook::jsi::Value setIsJSResponder(
|
|
243
249
|
facebook::jsi::Runtime &runtime,
|
|
244
250
|
const facebook::jsi::Value *arguments,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "SymbioteNative's retained shadow-tree engine — clone-on-write commit path + event normalization over React Native Fabric, shared by every framework adapter.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
"peerDependenciesMeta": {},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@symbiote-native/test-utils": "0.4.
|
|
74
|
+
"@symbiote-native/test-utils": "0.4.5"
|
|
75
75
|
},
|
|
76
76
|
"scripts": {
|
|
77
77
|
"typecheck": "tsc --build",
|