@lucent-lang/lucent 0.0.4 → 0.1.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/README.md +97 -0
- package/app.plugin.js +46 -10
- package/dist/{bench-CR3E7e2e.js → bench-B555m0wU.js} +10 -6
- package/dist/{build-CAIrRSYA.js → build-UZ5Zm5m6.js} +1 -1
- package/dist/{build-check-QozuNJm5.js → build-check-Dmf8_yiw.js} +28 -13
- package/dist/{check-1SCmYlqA.js → check-D-H9WLG4.js} +1 -1
- package/dist/cli.js +62 -13
- package/dist/{codes-CIuwXt1S.js → codes-D-6LxUqD.js} +163 -7
- package/dist/compiler.js +4 -4
- package/dist/{dashboard-B-NzjjaN.js → dashboard-C8iF5-6T.js} +2 -2
- package/dist/{dev-BYWMP7oC.js → dev-B3lSx5CF.js} +62 -23
- package/dist/{diagnostic-BM9sXmwl.js → diagnostic-BvwLZQS1.js} +3 -1
- package/dist/{doctor-C0PrfWT6.js → doctor-CTAX3euc.js} +1 -1
- package/dist/{doctor-DdR4JnVc.js → doctor-CrgOAV5L.js} +1 -1
- package/dist/{explain--vV1oaRl.js → explain-Byit07F-.js} +1 -1
- package/dist/{packages-HyskKIsY.js → packages-CSmKeaAh.js} +2 -33
- package/dist/pipeline-BBY0df_2.js +700 -0
- package/dist/project-BLhWwnns.js +342 -0
- package/dist/sdk-coverage-DFGiDOzR.js +113 -0
- package/dist/sdk-diff-DpPYWzrl.js +109 -0
- package/dist/sdk-lock-QOxEFD1o.js +38 -0
- package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-DX6ztlPN.js} +2 -2
- package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-BShBL1LL.js} +2 -2
- package/dist/{sdk-show-CkrdlqlP.js → sdk-show-B3bI-0M7.js} +5 -4
- package/dist/sdk-usage-ljX5bTuZ.js +95 -0
- package/dist/src-DlICbK91.js +39682 -0
- package/dist/trace-Bi4hkmPx.js +139 -0
- package/lib/core.cjs +368 -1
- package/lib/sdk/android.d.ts +54 -1
- package/lib/sdk/compose.d.ts +94 -0
- package/lib/sdk/compose.schemas.json.gz +0 -0
- package/lib/sdk/core.d.ts +201 -0
- package/lib/sdk/ios.d.ts +75 -2
- package/lib/sdk/ui.d.ts +129 -0
- package/metro/transformer.cjs +11 -2
- package/package.json +2 -1
- package/runtime/cpp/lucent/abort.cpp +59 -11
- package/runtime/cpp/lucent/abort.h +22 -6
- package/runtime/cpp/lucent/array.h +12 -2
- package/runtime/cpp/lucent/async.h +101 -21
- package/runtime/cpp/lucent/bigint.cpp +898 -0
- package/runtime/cpp/lucent/bigint.h +302 -0
- package/runtime/cpp/lucent/buffer.cpp +228 -0
- package/runtime/cpp/lucent/buffer.h +328 -0
- package/runtime/cpp/lucent/builtins.cpp +4 -1
- package/runtime/cpp/lucent/bytes.h +21 -2
- package/runtime/cpp/lucent/callback.cpp +29 -0
- package/runtime/cpp/lucent/callback.h +230 -0
- package/runtime/cpp/lucent/compute.cpp +370 -0
- package/runtime/cpp/lucent/compute.h +455 -0
- package/runtime/cpp/lucent/conversions.h +111 -0
- package/runtime/cpp/lucent/core.h +16 -5
- package/runtime/cpp/lucent/equality.h +107 -28
- package/runtime/cpp/lucent/execution.cpp +416 -0
- package/runtime/cpp/lucent/execution.h +270 -0
- package/runtime/cpp/lucent/extension.cpp +97 -0
- package/runtime/cpp/lucent/extension.h +210 -0
- package/runtime/cpp/lucent/helpers.h +12 -5
- package/runtime/cpp/lucent/items.h +52 -0
- package/runtime/cpp/lucent/jserror.cpp +9 -0
- package/runtime/cpp/lucent/jserror.h +11 -0
- package/runtime/cpp/lucent/jsi/convert.cpp +138 -0
- package/runtime/cpp/lucent/jsi/convert.h +128 -59
- package/runtime/cpp/lucent/jsi/host.cpp +213 -35
- package/runtime/cpp/lucent/jsi/host.h +116 -16
- package/runtime/cpp/lucent/json.h +19 -1
- package/runtime/cpp/lucent/lifecycle.cpp +260 -0
- package/runtime/cpp/lucent/lifecycle.h +124 -0
- package/runtime/cpp/lucent/lucent.h +7 -0
- package/runtime/cpp/lucent/map.h +41 -7
- package/runtime/cpp/lucent/native.cpp +35 -47
- package/runtime/cpp/lucent/native.h +116 -26
- package/runtime/cpp/lucent/number.cpp +38 -38
- package/runtime/cpp/lucent/number.h +33 -0
- package/runtime/cpp/lucent/operation.h +65 -0
- package/runtime/cpp/lucent/ops.h +14 -6
- package/runtime/cpp/lucent/platform/android.cpp +223 -10
- package/runtime/cpp/lucent/platform/android.h +227 -4
- package/runtime/cpp/lucent/platform/android_activity.cpp +258 -0
- package/runtime/cpp/lucent/platform/android_requests.h +140 -0
- package/runtime/cpp/lucent/platform/compose.h +256 -0
- package/runtime/cpp/lucent/platform/ios.h +325 -25
- package/runtime/cpp/lucent/platform/ios.mm +1 -14
- package/runtime/cpp/lucent/platform/ios_ui.h +192 -0
- package/runtime/cpp/lucent/platform/ios_ui.mm +454 -0
- package/runtime/cpp/lucent/platform/swiftui.h +190 -0
- package/runtime/cpp/lucent/presentation.cpp +11 -0
- package/runtime/cpp/lucent/presentation.h +109 -0
- package/runtime/cpp/lucent/reactive.cpp +406 -0
- package/runtime/cpp/lucent/reactive.h +566 -0
- package/runtime/cpp/lucent/report.cpp +40 -0
- package/runtime/cpp/lucent/report.h +16 -0
- package/runtime/cpp/lucent/resource.cpp +83 -0
- package/runtime/cpp/lucent/resource.h +75 -0
- package/runtime/cpp/lucent/scheduler.cpp +38 -124
- package/runtime/cpp/lucent/scheduler.h +102 -83
- package/runtime/cpp/lucent/scope.cpp +308 -0
- package/runtime/cpp/lucent/scope.h +452 -0
- package/runtime/cpp/lucent/sizing.h +263 -0
- package/runtime/cpp/lucent/slots.h +231 -0
- package/runtime/cpp/lucent/trace.cpp +539 -0
- package/runtime/cpp/lucent/trace.h +251 -0
- package/runtime/cpp/lucent/transport.cpp +9 -0
- package/runtime/cpp/lucent/transport.h +341 -0
- package/runtime/cpp/lucent/view.cpp +123 -0
- package/runtime/cpp/lucent/view.h +157 -0
- package/runtime/cpp/rn/LucentComponentView.h +121 -0
- package/runtime/cpp/rn/LucentComponentView.mm +479 -0
- package/runtime/cpp/rn/LucentModule.cpp +37 -7
- package/runtime/cpp/rn/LucentModule.h +3 -0
- package/runtime/cpp/rn/LucentViewChildren.h +80 -0
- package/runtime/cpp/rn/LucentViewChildren.mm +211 -0
- package/runtime/cpp/rn/LucentViewRegistry.h +62 -0
- package/runtime/cpp/rn/LucentViewRequests.cpp +91 -0
- package/runtime/cpp/rn/LucentViewRequests.h +104 -0
- package/runtime/cpp/rn/LucentViewSizing.h +345 -0
- package/runtime/cpp/rn/LucentViewSlots.h +242 -0
- package/runtime/cpp/rn/LucentViewValues.h +125 -0
- package/runtime/cpp/rn/LucentViews.h +440 -0
- package/runtime/cpp/rn/LucentViewsAndroid.cpp +439 -0
- package/runtime/cpp/rn/LucentViewsAndroid.h +138 -0
- package/runtime/cpp/third_party/dragonbox/LICENSE-Boost +23 -0
- package/runtime/cpp/third_party/dragonbox/README.md +16 -0
- package/runtime/cpp/third_party/dragonbox/dragonbox.h +4440 -0
- package/runtime/js/index.d.ts +11 -1
- package/runtime/js/index.js +84 -8
- package/runtime/js/views.d.ts +38 -0
- package/runtime/js/views.js +226 -0
- package/runtime/native/android/CMakeLists.txt +4 -1
- package/runtime/native/android/src/compose/java/dev/lucent/compose/CompositionRules.kt +44 -0
- package/runtime/native/android/src/compose/java/dev/lucent/compose/LucentComposition.kt +161 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentActivities.java +292 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentChildren.java +174 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentHostView.java +232 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentInitializer.java +46 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +4 -3
- package/runtime/native/android/src/main/java/dev/lucent/LucentRequestActivity.java +92 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentSlotView.java +102 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViewManager.java +149 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViewManagers.java +16 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViews.java +44 -0
- package/runtime/test/jsi/harness.cpp +9 -0
- package/schemas/build.schema.json +96 -1
- package/schemas/check.schema.json +56 -1
- package/schemas/lucent.schema.json +495 -9
- package/schemas/sdk-diff.schema.json +70 -0
- package/schemas/sdk-lock.schema.json +67 -0
- package/ts-plugin/index.js +77 -4
- package/dist/pipeline-CH7-2cxu.js +0 -232
- package/dist/project-D8tEet0k.js +0 -150
- package/dist/sdk-coverage-BoW_3Efd.js +0 -64
- package/dist/src-BD7f1_aZ.js +0 -11493
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// Lucent runtime — how a component's host sizes to its content.
|
|
2
|
+
//
|
|
3
|
+
// The renderer lays React's tree out on its own thread; a component's
|
|
4
|
+
// content is a platform view, which only the main thread may measure. So
|
|
5
|
+
// the two exchange results rather than calls: the shadow tree records the
|
|
6
|
+
// constraints its layout asks for (a SizeState's `requested`) when the
|
|
7
|
+
// measurement it holds does not answer them (request), the host measures
|
|
8
|
+
// its content under them on the main thread and posts the result, tagged
|
|
9
|
+
// with the constraints it used and the content's revision, and the shadow
|
|
10
|
+
// tree takes it only if it is still current (judge). The shadow side never
|
|
11
|
+
// waits for the main thread.
|
|
12
|
+
//
|
|
13
|
+
// A measurement answers the constraints it was made under (bounds, font
|
|
14
|
+
// scale, direction) and stricter bounds the size still fits within: the
|
|
15
|
+
// content's measure is assumed monotonic, as Yoga's measure cache assumes.
|
|
16
|
+
//
|
|
17
|
+
// Plain data and arithmetic: the React Native side (rn/LucentViewSizing.h)
|
|
18
|
+
// keeps a SizeState as a shadow node's state, and the platform hosts drive
|
|
19
|
+
// a Measurer.
|
|
20
|
+
#pragma once
|
|
21
|
+
|
|
22
|
+
#include <cmath>
|
|
23
|
+
#include <cstdint>
|
|
24
|
+
#include <limits>
|
|
25
|
+
#include <optional>
|
|
26
|
+
|
|
27
|
+
namespace lucent::sizing {
|
|
28
|
+
|
|
29
|
+
/// No bound on an axis.
|
|
30
|
+
inline constexpr float kUnbounded = std::numeric_limits<float>::infinity();
|
|
31
|
+
|
|
32
|
+
/// A size in points (Android: density-independent pixels).
|
|
33
|
+
struct Size {
|
|
34
|
+
float width = 0;
|
|
35
|
+
float height = 0;
|
|
36
|
+
|
|
37
|
+
friend bool operator==(const Size&, const Size&) = default;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
enum class Direction : std::uint8_t { LeftToRight, RightToLeft };
|
|
41
|
+
|
|
42
|
+
/// What a layout measures the content under: the largest size it offers
|
|
43
|
+
/// per axis, the text size multiplier it lays text out with (Dynamic Type,
|
|
44
|
+
/// Android's font scale) and its direction.
|
|
45
|
+
struct Constraints {
|
|
46
|
+
float maxWidth = kUnbounded;
|
|
47
|
+
float maxHeight = kUnbounded;
|
|
48
|
+
float fontScale = 1;
|
|
49
|
+
Direction direction = Direction::LeftToRight;
|
|
50
|
+
|
|
51
|
+
friend bool operator==(const Constraints&, const Constraints&) = default;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/// The content's size under `constraints`, at content revision `revision`.
|
|
55
|
+
struct Measurement {
|
|
56
|
+
std::uint64_t revision = 0;
|
|
57
|
+
Constraints constraints;
|
|
58
|
+
Size size;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/// What the shadow tree holds: the constraints its latest layout asked
|
|
62
|
+
/// for, and the measurement it sizes the content with.
|
|
63
|
+
struct SizeState {
|
|
64
|
+
std::optional<Constraints> requested;
|
|
65
|
+
std::optional<Measurement> measured;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/// `points` up to the next physical pixel at `scale` pixels per point,
|
|
69
|
+
/// ignoring a float's rounding error: a measured size must not cut the
|
|
70
|
+
/// content. An unbounded value stays so; none goes below zero.
|
|
71
|
+
inline float snap(float points, float scale) {
|
|
72
|
+
if (std::isinf(points)) return points;
|
|
73
|
+
|
|
74
|
+
auto snapped = std::ceil(points * scale - 1e-3f) / scale;
|
|
75
|
+
|
|
76
|
+
// Not -0, which 0 less the slack rounds up to.
|
|
77
|
+
return snapped > 0 ? snapped : 0.0f;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
inline Size snap(Size size, float scale) { return {snap(size.width, scale), snap(size.height, scale)}; }
|
|
81
|
+
|
|
82
|
+
/// Within a physical pixel of each other: what layout cannot tell apart.
|
|
83
|
+
inline bool same(float a, float b, float scale) {
|
|
84
|
+
if (std::isinf(a) || std::isinf(b)) return a == b;
|
|
85
|
+
|
|
86
|
+
return std::fabs(a - b) < 1 / scale;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
inline bool same(const Size& a, const Size& b, float scale) {
|
|
90
|
+
return same(a.width, b.width, scale) && same(a.height, b.height, scale);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
inline bool same(const Constraints& a, const Constraints& b, float scale) {
|
|
94
|
+
return same(a.maxWidth, b.maxWidth, scale) && same(a.maxHeight, b.maxHeight, scale) &&
|
|
95
|
+
a.fontScale == b.fontScale && a.direction == b.direction;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Whether content measured at `size` under `measuredBound` has that size
|
|
99
|
+
/// under `bound` too: the same bound, or a stricter one it still fits
|
|
100
|
+
/// within. A looser bound may not: the old one may have cut the content
|
|
101
|
+
/// (wrapped its text). Yoga's measure cache relies on the same rule.
|
|
102
|
+
inline bool fits(float size, float measuredBound, float bound, float scale) {
|
|
103
|
+
if (same(measuredBound, bound, scale)) return true;
|
|
104
|
+
|
|
105
|
+
return bound < measuredBound && (size < bound || same(size, bound, scale));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Whether `measurement` gives the content's size under `constraints`.
|
|
109
|
+
inline bool answers(const Measurement& measurement, const Constraints& constraints, float scale) {
|
|
110
|
+
const auto& measured = measurement.constraints;
|
|
111
|
+
|
|
112
|
+
return measured.fontScale == constraints.fontScale && measured.direction == constraints.direction &&
|
|
113
|
+
fits(measurement.size.width, measured.maxWidth, constraints.maxWidth, scale) &&
|
|
114
|
+
fits(measurement.size.height, measured.maxHeight, constraints.maxHeight, scale);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
enum class Verdict {
|
|
118
|
+
/// The shadow tree takes it.
|
|
119
|
+
Accepted,
|
|
120
|
+
/// The shadow tree holds a measurement of newer content.
|
|
121
|
+
StaleRevision,
|
|
122
|
+
/// Measured under constraints the layout no longer asks for.
|
|
123
|
+
StaleConstraints,
|
|
124
|
+
/// The shadow tree holds this size already: nothing to lay out again.
|
|
125
|
+
Unchanged,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
inline const char* verdictName(Verdict verdict) {
|
|
129
|
+
switch (verdict) {
|
|
130
|
+
case Verdict::Accepted:
|
|
131
|
+
return "accepted";
|
|
132
|
+
case Verdict::StaleRevision:
|
|
133
|
+
return "stale revision";
|
|
134
|
+
case Verdict::StaleConstraints:
|
|
135
|
+
return "stale constraints";
|
|
136
|
+
case Verdict::Unchanged:
|
|
137
|
+
return "unchanged";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return "?";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Whether `state`'s measurement gives the size for the constraints it asks for.
|
|
144
|
+
inline bool settled(const SizeState& state, float scale) {
|
|
145
|
+
return state.requested && state.measured && answers(*state.measured, *state.requested, scale);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Whether the shadow tree, holding `state`, takes `measurement`.
|
|
149
|
+
inline Verdict judge(const SizeState& state, const Measurement& measurement, float scale) {
|
|
150
|
+
if (state.measured && measurement.revision < state.measured->revision) return Verdict::StaleRevision;
|
|
151
|
+
|
|
152
|
+
if (!state.requested || !answers(measurement, *state.requested, scale)) return Verdict::StaleConstraints;
|
|
153
|
+
|
|
154
|
+
if (settled(state, scale) && same(state.measured->size, measurement.size, scale)) return Verdict::Unchanged;
|
|
155
|
+
|
|
156
|
+
return Verdict::Accepted;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// What the layout asks the host to measure under, having measured the
|
|
160
|
+
/// content under `asked`: nothing if `state` asks for it already, or if
|
|
161
|
+
/// its measurement answers it and whatever `state` asks for (a stricter
|
|
162
|
+
/// height bound in a column); else `asked`, replacing a pending request.
|
|
163
|
+
inline std::optional<Constraints> request(const SizeState& state, const Constraints& asked, float scale) {
|
|
164
|
+
if (state.requested && same(*state.requested, asked, scale)) return std::nullopt;
|
|
165
|
+
|
|
166
|
+
if (state.measured && answers(*state.measured, asked, scale) &&
|
|
167
|
+
(!state.requested || answers(*state.measured, *state.requested, scale)))
|
|
168
|
+
return std::nullopt;
|
|
169
|
+
|
|
170
|
+
return asked;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// The size the shadow tree lays the content out at: its measurement,
|
|
174
|
+
/// even one for other constraints (until the host measures under the new
|
|
175
|
+
/// ones), or nothing before the first.
|
|
176
|
+
inline Size layoutSize(const SizeState& state) { return state.measured ? state.measured->size : Size{}; }
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The host's side, for one mount (main thread): the content's revision,
|
|
180
|
+
* what it measured last, and a bound on results whose layout asks for
|
|
181
|
+
* other constraints again, so that content whose size depends on the
|
|
182
|
+
* constraints it gets cannot make layout loop.
|
|
183
|
+
*/
|
|
184
|
+
class Measurer {
|
|
185
|
+
public:
|
|
186
|
+
/// Posts allowed until the state settles (holds a measurement that
|
|
187
|
+
/// answers the constraints it asks for) or the content changes.
|
|
188
|
+
static constexpr int kMaxUnsettledPosts = 3;
|
|
189
|
+
|
|
190
|
+
explicit Measurer(float scale = 1) : scale_(scale) {}
|
|
191
|
+
|
|
192
|
+
float scale() const { return scale_; }
|
|
193
|
+
|
|
194
|
+
/// The content's revision: 1 at mount, one more for each change.
|
|
195
|
+
std::uint64_t revision() const { return revision_; }
|
|
196
|
+
|
|
197
|
+
/// The content changed (a commit, a command, the view itself): measure again.
|
|
198
|
+
void contentChanged() {
|
|
199
|
+
++revision_;
|
|
200
|
+
unsettled_ = 0;
|
|
201
|
+
gaveUp_ = false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/// A state reached the host.
|
|
205
|
+
void observe(const SizeState& state) {
|
|
206
|
+
if (settled(state, scale_)) {
|
|
207
|
+
unsettled_ = 0;
|
|
208
|
+
gaveUp_ = false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// The constraints to measure under now, if `state` asks for any this
|
|
213
|
+
/// revision's last measurement does not answer.
|
|
214
|
+
std::optional<Constraints> due(const SizeState& state) const {
|
|
215
|
+
if (!state.requested || gaveUp_) return std::nullopt;
|
|
216
|
+
|
|
217
|
+
if (last_ && last_->revision == revision_ && answers(*last_, *state.requested, scale_)) return std::nullopt;
|
|
218
|
+
|
|
219
|
+
return state.requested;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/// The content measured `size` under `constraints`: the measurement to
|
|
223
|
+
/// post, unless `state` holds it already, `pending` (the last result
|
|
224
|
+
/// posted, while the renderer has not judged it) says the same size
|
|
225
|
+
/// under the same constraints, or the bound is reached.
|
|
226
|
+
std::optional<Measurement> record(
|
|
227
|
+
const SizeState& state,
|
|
228
|
+
Constraints constraints,
|
|
229
|
+
Size size,
|
|
230
|
+
const std::optional<Measurement>& pending = std::nullopt) {
|
|
231
|
+
Measurement measurement{revision_, constraints, snap(size, scale_)};
|
|
232
|
+
|
|
233
|
+
last_ = measurement;
|
|
234
|
+
|
|
235
|
+
if (state.measured && answers(*state.measured, constraints, scale_) &&
|
|
236
|
+
same(state.measured->size, measurement.size, scale_))
|
|
237
|
+
return std::nullopt;
|
|
238
|
+
|
|
239
|
+
if (pending && same(pending->constraints, constraints, scale_) && same(pending->size, measurement.size, scale_))
|
|
240
|
+
return std::nullopt;
|
|
241
|
+
|
|
242
|
+
if (unsettled_ >= kMaxUnsettledPosts) {
|
|
243
|
+
gaveUp_ = true;
|
|
244
|
+
return std::nullopt;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
++unsettled_;
|
|
248
|
+
|
|
249
|
+
return measurement;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/// Whether the bound stopped posting (until the state settles or the content changes).
|
|
253
|
+
bool gaveUp() const { return gaveUp_; }
|
|
254
|
+
|
|
255
|
+
private:
|
|
256
|
+
float scale_;
|
|
257
|
+
std::uint64_t revision_ = 1;
|
|
258
|
+
std::optional<Measurement> last_;
|
|
259
|
+
int unsettled_ = 0;
|
|
260
|
+
bool gaveUp_ = false;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
} // namespace lucent::sizing
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Lucent runtime — where a component lays out its React children: in its
|
|
2
|
+
// slot's rectangle.
|
|
3
|
+
//
|
|
4
|
+
// The slot is a platform view the component's setup places anywhere in its
|
|
5
|
+
// native views, which only the main thread may measure; the renderer lays
|
|
6
|
+
// React's children out on its own thread. So the host reports where the
|
|
7
|
+
// slot is rather than being asked: the slot's insets in the component's
|
|
8
|
+
// content box (its frame less border and padding), measured on the main
|
|
9
|
+
// thread and posted to the component's shadow tree, which takes a report
|
|
10
|
+
// only if it is newer and moves the children (judge). Yoga then lays the
|
|
11
|
+
// children out as if the component's border were wider by the insets. The
|
|
12
|
+
// shadow side never waits for the main thread.
|
|
13
|
+
//
|
|
14
|
+
// Plain data and arithmetic: the React Native side (rn/LucentViewSlots.h)
|
|
15
|
+
// keeps a Placement in a component's state, and the platform hosts drive a
|
|
16
|
+
// Reporter.
|
|
17
|
+
#pragma once
|
|
18
|
+
|
|
19
|
+
#include <algorithm>
|
|
20
|
+
#include <cmath>
|
|
21
|
+
#include <cstdint>
|
|
22
|
+
#include <initializer_list>
|
|
23
|
+
#include <optional>
|
|
24
|
+
|
|
25
|
+
namespace lucent::slots {
|
|
26
|
+
|
|
27
|
+
/// Distances in points (Android: density-independent pixels) in from each
|
|
28
|
+
/// edge of a box, as the screen shows them: left is left in either direction.
|
|
29
|
+
struct Insets {
|
|
30
|
+
float left = 0;
|
|
31
|
+
float top = 0;
|
|
32
|
+
float right = 0;
|
|
33
|
+
float bottom = 0;
|
|
34
|
+
|
|
35
|
+
friend bool operator==(const Insets&, const Insets&) = default;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/// A rectangle, in points.
|
|
39
|
+
struct Rect {
|
|
40
|
+
float x = 0;
|
|
41
|
+
float y = 0;
|
|
42
|
+
float width = 0;
|
|
43
|
+
float height = 0;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/// How far `slot` lies in from each edge of `content` (both in the host's
|
|
47
|
+
/// coordinates); nothing on an edge where the slot reaches past the box.
|
|
48
|
+
inline Insets insetsOf(const Rect& slot, const Rect& content) {
|
|
49
|
+
auto in = [](float distance) { return std::max(distance, 0.0f); };
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
in(slot.x - content.x),
|
|
53
|
+
in(slot.y - content.y),
|
|
54
|
+
in(content.x + content.width - (slot.x + slot.width)),
|
|
55
|
+
in(content.y + content.height - (slot.y + slot.height)),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// `points` to the nearest physical pixel at `scale` pixels per point.
|
|
60
|
+
inline float snap(float points, float scale) { return std::round(points * scale) / scale; }
|
|
61
|
+
|
|
62
|
+
inline Insets snap(const Insets& insets, float scale) {
|
|
63
|
+
return {snap(insets.left, scale), snap(insets.top, scale), snap(insets.right, scale), snap(insets.bottom, scale)};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Within a physical pixel of each other on every edge: what layout cannot tell apart.
|
|
67
|
+
inline bool same(const Insets& a, const Insets& b, float scale) {
|
|
68
|
+
auto near = [scale](float x, float y) { return std::fabs(x - y) < 1 / scale; };
|
|
69
|
+
|
|
70
|
+
return near(a.left, b.left) && near(a.top, b.top) && near(a.right, b.right) && near(a.bottom, b.bottom);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// The slot fills the content box.
|
|
74
|
+
inline bool none(const Insets& insets, float scale) { return same(insets, Insets{}, scale); }
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A host's report, as the shadow tree keeps it: the slot's insets, and the
|
|
78
|
+
* direction the component laid out in when the host measured them, which
|
|
79
|
+
* says what its style's edges mean.
|
|
80
|
+
*/
|
|
81
|
+
struct Placement {
|
|
82
|
+
/// The host's count of reports for this mount, from 1.
|
|
83
|
+
std::uint64_t revision = 0;
|
|
84
|
+
Insets insets;
|
|
85
|
+
/// The component laid out right to left.
|
|
86
|
+
bool rtl = false;
|
|
87
|
+
/// Its style's left and right edges meant start and end (React Native's
|
|
88
|
+
/// swapLeftAndRightInRTL).
|
|
89
|
+
bool swapped = false;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/// Whether a shadow tree holding `held` (none: no report yet) lays its
|
|
93
|
+
/// children out as `report` would have it.
|
|
94
|
+
inline bool holds(const std::optional<Placement>& held, const Placement& report, float scale) {
|
|
95
|
+
auto insets = held ? held->insets : Insets{};
|
|
96
|
+
|
|
97
|
+
if (!same(insets, report.insets, scale)) return false;
|
|
98
|
+
|
|
99
|
+
return none(report.insets, scale) || (held && held->rtl == report.rtl && held->swapped == report.swapped);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
enum class Verdict {
|
|
103
|
+
/// The shadow tree takes it.
|
|
104
|
+
Accepted,
|
|
105
|
+
/// The shadow tree holds a newer report.
|
|
106
|
+
StaleRevision,
|
|
107
|
+
/// The shadow tree lays its children out there already.
|
|
108
|
+
Unchanged,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
inline const char* verdictName(Verdict verdict) {
|
|
112
|
+
switch (verdict) {
|
|
113
|
+
case Verdict::Accepted:
|
|
114
|
+
return "accepted";
|
|
115
|
+
case Verdict::StaleRevision:
|
|
116
|
+
return "stale revision";
|
|
117
|
+
case Verdict::Unchanged:
|
|
118
|
+
return "unchanged";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return "?";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Whether the shadow tree, holding `held`, takes `report`.
|
|
125
|
+
inline Verdict judge(const std::optional<Placement>& held, const Placement& report, float scale) {
|
|
126
|
+
if (held && report.revision < held->revision) return Verdict::StaleRevision;
|
|
127
|
+
|
|
128
|
+
return holds(held, report, scale) ? Verdict::Unchanged : Verdict::Accepted;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// A border's widths as a style gives them: each edge's, if it sets one.
|
|
132
|
+
struct BorderEdges {
|
|
133
|
+
std::optional<float> all;
|
|
134
|
+
std::optional<float> horizontal;
|
|
135
|
+
std::optional<float> vertical;
|
|
136
|
+
std::optional<float> left;
|
|
137
|
+
std::optional<float> top;
|
|
138
|
+
std::optional<float> right;
|
|
139
|
+
std::optional<float> bottom;
|
|
140
|
+
std::optional<float> start;
|
|
141
|
+
std::optional<float> end;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/// The width layout gives each side of a border set by `edges`, laid out
|
|
145
|
+
/// right to left if `rtl`, with left and right meaning start and end if
|
|
146
|
+
/// `swapped`: Yoga's precedence, none negative.
|
|
147
|
+
inline Insets resolve(const BorderEdges& edges, bool rtl, bool swapped) {
|
|
148
|
+
auto first = [](std::initializer_list<std::optional<float>> candidates) {
|
|
149
|
+
for (const auto& candidate : candidates)
|
|
150
|
+
if (candidate) return std::max(*candidate, 0.0f);
|
|
151
|
+
|
|
152
|
+
return 0.0f;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// React Native's swap turns a style's left and right into start and end.
|
|
156
|
+
auto start = swapped && edges.left ? edges.left : edges.start;
|
|
157
|
+
auto end = swapped && edges.right ? edges.right : edges.end;
|
|
158
|
+
auto left = swapped ? std::nullopt : edges.left;
|
|
159
|
+
auto right = swapped ? std::nullopt : edges.right;
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
first({rtl ? end : start, left, edges.horizontal, edges.all}),
|
|
163
|
+
first({edges.top, edges.vertical, edges.all}),
|
|
164
|
+
first({rtl ? start : end, right, edges.horizontal, edges.all}),
|
|
165
|
+
first({edges.bottom, edges.vertical, edges.all}),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The host's side, for one mount (main thread): what it posted last, its
|
|
171
|
+
* count of reports, and a bound on reports that never settle, so that a
|
|
172
|
+
* slot whose place follows the component's size (a fixed slot in a
|
|
173
|
+
* component sized by its children) cannot make layout loop.
|
|
174
|
+
*/
|
|
175
|
+
class Reporter {
|
|
176
|
+
public:
|
|
177
|
+
/// Reports allowed until the shadow tree holds what the host measures,
|
|
178
|
+
/// or the mount's native views change.
|
|
179
|
+
static constexpr int kMaxUnsettledPosts = 3;
|
|
180
|
+
|
|
181
|
+
explicit Reporter(float scale = 1) : scale_(scale) {}
|
|
182
|
+
|
|
183
|
+
float scale() const { return scale_; }
|
|
184
|
+
|
|
185
|
+
/// The mount's native views changed (a commit, a command): report again.
|
|
186
|
+
void contentChanged() {
|
|
187
|
+
unsettled_ = 0;
|
|
188
|
+
gaveUp_ = false;
|
|
189
|
+
pending_.reset();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// The slot measured at `insets` (the component laid out as `rtl` and
|
|
193
|
+
/// `swapped` say) while the shadow tree holds `held`: the report to
|
|
194
|
+
/// post, unless the shadow tree holds it already, it is posted already,
|
|
195
|
+
/// or the bound is reached.
|
|
196
|
+
std::optional<Placement> record(const std::optional<Placement>& held, Insets insets, bool rtl, bool swapped) {
|
|
197
|
+
Placement report{revision_ + 1, snap(insets, scale_), rtl, swapped};
|
|
198
|
+
|
|
199
|
+
if (holds(held, report, scale_)) {
|
|
200
|
+
unsettled_ = 0;
|
|
201
|
+
gaveUp_ = false;
|
|
202
|
+
pending_.reset();
|
|
203
|
+
return std::nullopt;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (pending_ && holds(pending_, report, scale_)) return std::nullopt;
|
|
207
|
+
|
|
208
|
+
if (unsettled_ >= kMaxUnsettledPosts) {
|
|
209
|
+
gaveUp_ = true;
|
|
210
|
+
return std::nullopt;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
++unsettled_;
|
|
214
|
+
revision_ = report.revision;
|
|
215
|
+
pending_ = report;
|
|
216
|
+
|
|
217
|
+
return report;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// Whether the bound stopped reporting (until the shadow tree settles or the native views change).
|
|
221
|
+
bool gaveUp() const { return gaveUp_; }
|
|
222
|
+
|
|
223
|
+
private:
|
|
224
|
+
float scale_;
|
|
225
|
+
std::uint64_t revision_ = 0;
|
|
226
|
+
std::optional<Placement> pending_;
|
|
227
|
+
int unsettled_ = 0;
|
|
228
|
+
bool gaveUp_ = false;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
} // namespace lucent::slots
|