@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,345 @@
|
|
|
1
|
+
// Lucent runtime — how Fabric sizes a Lucent component (lucent/sizing.h has
|
|
2
|
+
// the exchange itself).
|
|
3
|
+
//
|
|
4
|
+
// A component with a size of its own (explicit, or given by flex) fills it:
|
|
5
|
+
// the platform hosts lay the setup's view out in the host's content box (its
|
|
6
|
+
// frame less border and padding) at every layout. A component without one is
|
|
7
|
+
// sized by its content: its shadow node is a measurable Yoga leaf that lays
|
|
8
|
+
// out at the latest measurement its state holds (none before the first: zero),
|
|
9
|
+
// and records in its state the constraints its layout asked for. The host
|
|
10
|
+
// (HostSizing, main thread) measures its view under them, and again after
|
|
11
|
+
// the mount's code ran (it may have changed the view: lucent/view.h's
|
|
12
|
+
// Content), and posts the result as a state update, which the renderer
|
|
13
|
+
// applies on its own thread if the result is still current. No thread
|
|
14
|
+
// waits for another: the first frame shows the component at its last size
|
|
15
|
+
// (zero on mount), and the next commit at its measured one. The renderer
|
|
16
|
+
// applies state updates on the JavaScript thread, so while that thread is
|
|
17
|
+
// busy a measured size waits for it (committing them from the main thread
|
|
18
|
+
// instead, unstable_Immediate, cost up to 20 ms of it per update on an
|
|
19
|
+
// Android emulator).
|
|
20
|
+
#pragma once
|
|
21
|
+
|
|
22
|
+
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
23
|
+
#include <react/renderer/core/ConcreteState.h>
|
|
24
|
+
#include <react/renderer/core/LayoutConstraints.h>
|
|
25
|
+
#include <react/renderer/core/LayoutContext.h>
|
|
26
|
+
|
|
27
|
+
#ifdef RN_SERIALIZABLE_STATE
|
|
28
|
+
#include <folly/dynamic.h>
|
|
29
|
+
#endif
|
|
30
|
+
|
|
31
|
+
#if defined(__ANDROID__)
|
|
32
|
+
#include <sys/system_properties.h>
|
|
33
|
+
#endif
|
|
34
|
+
|
|
35
|
+
#include <lucent/report.h>
|
|
36
|
+
#include <lucent/sizing.h>
|
|
37
|
+
#include <lucent/slots.h>
|
|
38
|
+
#include <lucent/view.h>
|
|
39
|
+
|
|
40
|
+
#include <atomic>
|
|
41
|
+
#include <chrono>
|
|
42
|
+
#include <cstdarg>
|
|
43
|
+
#include <cstdio>
|
|
44
|
+
#include <cstdlib>
|
|
45
|
+
#include <cstring>
|
|
46
|
+
#include <functional>
|
|
47
|
+
#include <memory>
|
|
48
|
+
#include <optional>
|
|
49
|
+
#include <utility>
|
|
50
|
+
|
|
51
|
+
namespace lucent::views {
|
|
52
|
+
|
|
53
|
+
/// Whether sizing traces its steps (LUCENT_SIZING_TRACE=1; on Android the
|
|
54
|
+
/// debug.lucent.sizing system property): measurements, requests and
|
|
55
|
+
/// verdicts, as LUCENT_SIZING lines in the platform's log.
|
|
56
|
+
inline bool sizingTraced() {
|
|
57
|
+
static const bool traced = [] {
|
|
58
|
+
#if defined(__ANDROID__)
|
|
59
|
+
char property[PROP_VALUE_MAX] = {};
|
|
60
|
+
|
|
61
|
+
if (__system_property_get("debug.lucent.sizing", property) > 0) return std::strcmp(property, "1") == 0;
|
|
62
|
+
#endif
|
|
63
|
+
const char* variable = std::getenv("LUCENT_SIZING_TRACE");
|
|
64
|
+
|
|
65
|
+
return variable != nullptr && std::strcmp(variable, "1") == 0;
|
|
66
|
+
}();
|
|
67
|
+
|
|
68
|
+
return traced;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// One LUCENT_SIZING line (printf format), stamped with the time since the first, when sizing is traced.
|
|
72
|
+
inline void traceSizing(const char* format, ...) {
|
|
73
|
+
if (!sizingTraced()) return;
|
|
74
|
+
|
|
75
|
+
static const auto start = std::chrono::steady_clock::now();
|
|
76
|
+
auto ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
|
77
|
+
char line[256];
|
|
78
|
+
va_list args;
|
|
79
|
+
|
|
80
|
+
va_start(args, format);
|
|
81
|
+
std::vsnprintf(line, sizeof line, format, args);
|
|
82
|
+
va_end(args);
|
|
83
|
+
|
|
84
|
+
char stamped[288];
|
|
85
|
+
|
|
86
|
+
std::snprintf(stamped, sizeof stamped, "LUCENT_SIZING %.1f ms %s", ms, line);
|
|
87
|
+
logError(stamped);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** A component's size state, as the renderer keeps it with its shadow node. */
|
|
91
|
+
struct SizeStateData : sizing::SizeState {
|
|
92
|
+
/// A component taking React children: where its host last reported its
|
|
93
|
+
/// slot (LucentViewSlots.h). None before the first report.
|
|
94
|
+
std::optional<slots::Placement> slot;
|
|
95
|
+
|
|
96
|
+
SizeStateData() = default;
|
|
97
|
+
|
|
98
|
+
#ifdef RN_SERIALIZABLE_STATE
|
|
99
|
+
// Android's renderer can update states from Java; nothing on the Java
|
|
100
|
+
// side does for this one: the host posts its results from C++.
|
|
101
|
+
SizeStateData(const SizeStateData& previous, folly::dynamic /*data*/) : SizeStateData(previous) {}
|
|
102
|
+
|
|
103
|
+
folly::dynamic getDynamic() const { return folly::dynamic::object(); }
|
|
104
|
+
#endif
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A component's shadow node: a measurable leaf, sized by its host's
|
|
109
|
+
* measurement when its style and flex leave it a size to find.
|
|
110
|
+
*/
|
|
111
|
+
template <const char* Name, typename PropsT, typename EmitterT>
|
|
112
|
+
class HostShadowNode final
|
|
113
|
+
: public facebook::react::ConcreteViewShadowNode<Name, PropsT, EmitterT, SizeStateData> {
|
|
114
|
+
using Base = facebook::react::ConcreteViewShadowNode<Name, PropsT, EmitterT, SizeStateData>;
|
|
115
|
+
|
|
116
|
+
public:
|
|
117
|
+
using Base::Base;
|
|
118
|
+
|
|
119
|
+
static facebook::react::ShadowNodeTraits BaseTraits() {
|
|
120
|
+
auto traits = Base::BaseTraits();
|
|
121
|
+
|
|
122
|
+
traits.set(facebook::react::ShadowNodeTraits::Trait::LeafYogaNode);
|
|
123
|
+
traits.set(facebook::react::ShadowNodeTraits::Trait::MeasurableYogaNode);
|
|
124
|
+
|
|
125
|
+
return traits;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// The latest measurement, within `constraints`; never waits for the host.
|
|
129
|
+
facebook::react::Size measureContent(
|
|
130
|
+
const facebook::react::LayoutContext& /*context*/,
|
|
131
|
+
const facebook::react::LayoutConstraints& constraints) const override {
|
|
132
|
+
asked_ = sizing::Constraints{
|
|
133
|
+
static_cast<float>(constraints.maximumSize.width), static_cast<float>(constraints.maximumSize.height)};
|
|
134
|
+
|
|
135
|
+
auto size = sizing::layoutSize(this->getStateData());
|
|
136
|
+
|
|
137
|
+
return constraints.clamp(facebook::react::Size{size.width, size.height});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Laid out: asks the host to measure under the constraints of the
|
|
141
|
+
/// layout's latest measure, with its font scale and direction, unless
|
|
142
|
+
/// the measurement the state holds answers them (sizing::request).
|
|
143
|
+
void layout(facebook::react::LayoutContext context) override {
|
|
144
|
+
Base::layout(context);
|
|
145
|
+
|
|
146
|
+
if (!asked_) return;
|
|
147
|
+
|
|
148
|
+
auto asked = *asked_;
|
|
149
|
+
|
|
150
|
+
asked.fontScale = static_cast<float>(context.fontSizeMultiplier);
|
|
151
|
+
asked.direction = this->getLayoutMetrics().layoutDirection == facebook::react::LayoutDirection::RightToLeft
|
|
152
|
+
? sizing::Direction::RightToLeft
|
|
153
|
+
: sizing::Direction::LeftToRight;
|
|
154
|
+
|
|
155
|
+
const auto& state = this->getStateData();
|
|
156
|
+
auto requested = sizing::request(state, asked, static_cast<float>(context.pointScaleFactor));
|
|
157
|
+
|
|
158
|
+
if (!requested) return;
|
|
159
|
+
|
|
160
|
+
auto next = state;
|
|
161
|
+
|
|
162
|
+
next.requested = requested;
|
|
163
|
+
traceSizing(
|
|
164
|
+
"%d request %gx%g x%g%s",
|
|
165
|
+
this->getTag(),
|
|
166
|
+
requested->maxWidth,
|
|
167
|
+
requested->maxHeight,
|
|
168
|
+
requested->fontScale,
|
|
169
|
+
requested->direction == sizing::Direction::RightToLeft ? " rtl" : "");
|
|
170
|
+
this->setStateData(std::move(next));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private:
|
|
174
|
+
/// The bounds of this node's latest measure in its layout.
|
|
175
|
+
mutable std::optional<sizing::Constraints> asked_;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* A host's side of sizing, for one mount (main thread): the state of the
|
|
180
|
+
* latest commit it mounted, its Measurer, and the mount's Content, which
|
|
181
|
+
* the host enters to call the mount (setup, commits, commands) and the
|
|
182
|
+
* setup's functions enter when they run. Once any entry ends, the content
|
|
183
|
+
* counts as changed: it is measured again if the shadow tree needs its
|
|
184
|
+
* size, as it is when a commit's state asks for other constraints.
|
|
185
|
+
*/
|
|
186
|
+
class HostSizing {
|
|
187
|
+
public:
|
|
188
|
+
using State = facebook::react::ConcreteState<SizeStateData>;
|
|
189
|
+
|
|
190
|
+
/// The content's size within the constraints (unbounded axes: none), or
|
|
191
|
+
/// nothing when there is no content to measure. Main thread.
|
|
192
|
+
using Measure = std::function<std::optional<sizing::Size>(sizing::Constraints)>;
|
|
193
|
+
|
|
194
|
+
HostSizing() = default;
|
|
195
|
+
|
|
196
|
+
// Its Content calls back into it where it is.
|
|
197
|
+
HostSizing(const HostSizing&) = delete;
|
|
198
|
+
HostSizing& operator=(const HostSizing&) = delete;
|
|
199
|
+
|
|
200
|
+
/// A mount starts on the view `tag` names, at `scale` pixels per point,
|
|
201
|
+
/// its content measured by `measure`: revision 1, measured from scratch.
|
|
202
|
+
void start(float scale, int tag, Measure measure) {
|
|
203
|
+
measurer_ = sizing::Measurer(scale);
|
|
204
|
+
lastPost_.reset();
|
|
205
|
+
tag_ = tag;
|
|
206
|
+
measure_ = std::move(measure);
|
|
207
|
+
content_ = ui::Content::create([this] { contentChanged(); });
|
|
208
|
+
traceSizing("%d start", tag_);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// The mount ends: nothing it does reaches the host any more.
|
|
212
|
+
void stop() {
|
|
213
|
+
if (content_) traceSizing("%d stop", tag_);
|
|
214
|
+
|
|
215
|
+
content_.reset();
|
|
216
|
+
measure_ = nullptr;
|
|
217
|
+
state_.reset();
|
|
218
|
+
lastPost_.reset();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// What the host enters to call the mount (ui::ContentEntry).
|
|
222
|
+
std::weak_ptr<ui::Content> content() const { return content_; }
|
|
223
|
+
|
|
224
|
+
/// The state of the commit being mounted.
|
|
225
|
+
void setState(const facebook::react::State::Shared& state) {
|
|
226
|
+
state_ = std::static_pointer_cast<const State>(state);
|
|
227
|
+
|
|
228
|
+
if (!state_) return;
|
|
229
|
+
|
|
230
|
+
const auto& data = state_->getData();
|
|
231
|
+
|
|
232
|
+
measurer_.observe(data);
|
|
233
|
+
|
|
234
|
+
if (data.measured)
|
|
235
|
+
traceSizing(
|
|
236
|
+
"%d mounted rev %llu: %gx%g",
|
|
237
|
+
tag_,
|
|
238
|
+
static_cast<unsigned long long>(data.measured->revision),
|
|
239
|
+
data.measured->size.width,
|
|
240
|
+
data.measured->size.height);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/// The content changed (its code ran, or it asked for a layout): a new
|
|
244
|
+
/// revision, measured if the shadow tree needs its size.
|
|
245
|
+
void contentChanged() {
|
|
246
|
+
measurer_.contentChanged();
|
|
247
|
+
traceSizing("%d content rev %llu", tag_, static_cast<unsigned long long>(measurer_.revision()));
|
|
248
|
+
measure();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// Measures if the state asks for constraints this content revision has
|
|
252
|
+
/// not been measured under, and posts a result the state lacks.
|
|
253
|
+
void measure() {
|
|
254
|
+
if (!state_ || !measure_) return;
|
|
255
|
+
|
|
256
|
+
const auto& data = state_->getData();
|
|
257
|
+
auto constraints = measurer_.due(data);
|
|
258
|
+
|
|
259
|
+
if (!constraints) return;
|
|
260
|
+
|
|
261
|
+
auto size = measure_(*constraints);
|
|
262
|
+
|
|
263
|
+
if (!size) return;
|
|
264
|
+
|
|
265
|
+
std::optional<sizing::Measurement> pending;
|
|
266
|
+
|
|
267
|
+
if (lastPost_ && !lastPost_->judged->load(std::memory_order_acquire)) pending = lastPost_->measurement;
|
|
268
|
+
|
|
269
|
+
auto result = measurer_.record(data, *constraints, *size, pending);
|
|
270
|
+
|
|
271
|
+
traceSizing(
|
|
272
|
+
"%d measure rev %llu under %gx%g x%g%s: %gx%g %s",
|
|
273
|
+
tag_,
|
|
274
|
+
static_cast<unsigned long long>(measurer_.revision()),
|
|
275
|
+
constraints->maxWidth,
|
|
276
|
+
constraints->maxHeight,
|
|
277
|
+
constraints->fontScale,
|
|
278
|
+
constraints->direction == sizing::Direction::RightToLeft ? " rtl" : "",
|
|
279
|
+
size->width,
|
|
280
|
+
size->height,
|
|
281
|
+
result ? "posted" : measurer_.gaveUp() ? "gave up" : "held");
|
|
282
|
+
|
|
283
|
+
if (measurer_.gaveUp()) {
|
|
284
|
+
char line[160];
|
|
285
|
+
|
|
286
|
+
std::snprintf(
|
|
287
|
+
line,
|
|
288
|
+
sizeof line,
|
|
289
|
+
"[lucent] view %d stopped sizing to its content after %d results: its size and its layout's constraints "
|
|
290
|
+
"keep changing each other",
|
|
291
|
+
tag_,
|
|
292
|
+
sizing::Measurer::kMaxUnsettledPosts);
|
|
293
|
+
logError(line);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!result) return;
|
|
297
|
+
|
|
298
|
+
auto scale = measurer_.scale();
|
|
299
|
+
auto tag = tag_;
|
|
300
|
+
auto judged = std::make_shared<std::atomic<bool>>(false);
|
|
301
|
+
|
|
302
|
+
lastPost_ = Post{*result, judged};
|
|
303
|
+
|
|
304
|
+
// Judged against the state current when the renderer applies it, on its thread.
|
|
305
|
+
auto update = [result = *result, scale, tag, judged](const SizeStateData& current) -> facebook::react::StateData::Shared {
|
|
306
|
+
auto verdict = sizing::judge(current, result, scale);
|
|
307
|
+
|
|
308
|
+
judged->store(true, std::memory_order_release);
|
|
309
|
+
|
|
310
|
+
traceSizing(
|
|
311
|
+
"%d result rev %llu under %gx%g: %s",
|
|
312
|
+
tag,
|
|
313
|
+
static_cast<unsigned long long>(result.revision),
|
|
314
|
+
result.constraints.maxWidth,
|
|
315
|
+
result.constraints.maxHeight,
|
|
316
|
+
sizing::verdictName(verdict));
|
|
317
|
+
|
|
318
|
+
if (verdict != sizing::Verdict::Accepted) return nullptr;
|
|
319
|
+
|
|
320
|
+
auto next = current;
|
|
321
|
+
|
|
322
|
+
next.measured = result;
|
|
323
|
+
|
|
324
|
+
return std::make_shared<const SizeStateData>(std::move(next));
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
state_->updateState(std::move(update));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private:
|
|
331
|
+
/// A result posted, and whether the renderer has judged it (its thread sets it).
|
|
332
|
+
struct Post {
|
|
333
|
+
sizing::Measurement measurement;
|
|
334
|
+
std::shared_ptr<std::atomic<bool>> judged;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
std::shared_ptr<const State> state_;
|
|
338
|
+
sizing::Measurer measurer_;
|
|
339
|
+
std::optional<Post> lastPost_;
|
|
340
|
+
Measure measure_;
|
|
341
|
+
std::shared_ptr<ui::Content> content_;
|
|
342
|
+
int tag_ = 0;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
} // namespace lucent::views
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// Lucent runtime — how Fabric lays out a Lucent component taking React
|
|
2
|
+
// children (a `children: Children` prop, lucent:ui's slot), in its slot's
|
|
3
|
+
// rectangle (lucent/slots.h has the exchange itself).
|
|
4
|
+
//
|
|
5
|
+
// Its shadow node is a Yoga container, as a View's is: Yoga sizes it by its
|
|
6
|
+
// style, flex and children, and lays the children out in its own
|
|
7
|
+
// coordinates. Its host never measures its content (the node is no
|
|
8
|
+
// measurable leaf, so its state never asks for a size), and mounts each
|
|
9
|
+
// child React Native gives it in the slot its setup placed, where the child
|
|
10
|
+
// shows at the frame Yoga gave it.
|
|
11
|
+
//
|
|
12
|
+
// Where the children go: the host reports, from the main thread, how far
|
|
13
|
+
// the slot lies in from each edge of the component's content box (HostSlot),
|
|
14
|
+
// as a state update the renderer applies on its own thread if it is newer
|
|
15
|
+
// and moves the children. The shadow node then has Yoga lay its children
|
|
16
|
+
// out as if its border were wider by those insets: a child that fills its
|
|
17
|
+
// parent fills the slot, and a card sized by its children grows by what
|
|
18
|
+
// its native views take around the slot. Before the first report the
|
|
19
|
+
// children lay out in the content box, which a slot filling its view (the
|
|
20
|
+
// default) needs no report to match. The wider border stays the node's
|
|
21
|
+
// own: the host's layout metrics keep the style's border and padding, so
|
|
22
|
+
// the setup's view stays in the real content box.
|
|
23
|
+
#pragma once
|
|
24
|
+
|
|
25
|
+
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
26
|
+
#include <react/renderer/core/LayoutContext.h>
|
|
27
|
+
|
|
28
|
+
#include <lucent/slots.h>
|
|
29
|
+
|
|
30
|
+
#include "LucentViewSizing.h"
|
|
31
|
+
|
|
32
|
+
#include <memory>
|
|
33
|
+
#include <optional>
|
|
34
|
+
#include <utility>
|
|
35
|
+
|
|
36
|
+
namespace lucent::views {
|
|
37
|
+
|
|
38
|
+
/// The border widths `style` sets, edge by edge.
|
|
39
|
+
inline slots::BorderEdges borderEdges(const facebook::yoga::Style& style) {
|
|
40
|
+
using facebook::yoga::Edge;
|
|
41
|
+
|
|
42
|
+
// A width not in points resolves to none, as Yoga resolves it, and still takes precedence.
|
|
43
|
+
auto edge = [&](Edge e) -> std::optional<float> {
|
|
44
|
+
auto length = style.border(e);
|
|
45
|
+
|
|
46
|
+
if (!length.isDefined()) return std::nullopt;
|
|
47
|
+
|
|
48
|
+
return length.isPoints() ? length.value().unwrap() : 0.0f;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
edge(Edge::All),
|
|
53
|
+
edge(Edge::Horizontal),
|
|
54
|
+
edge(Edge::Vertical),
|
|
55
|
+
edge(Edge::Left),
|
|
56
|
+
edge(Edge::Top),
|
|
57
|
+
edge(Edge::Right),
|
|
58
|
+
edge(Edge::Bottom),
|
|
59
|
+
edge(Edge::Start),
|
|
60
|
+
edge(Edge::End),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A component's shadow node when it takes React children: a Yoga container
|
|
66
|
+
* laying them out in its slot, once its host has reported where that is.
|
|
67
|
+
* Its state is a leaf's (SizeStateData), so both kinds share their hosts'
|
|
68
|
+
* code; nothing asks it for a measurement.
|
|
69
|
+
*/
|
|
70
|
+
template <const char* Name, typename PropsT, typename EmitterT>
|
|
71
|
+
class SlotShadowNode final
|
|
72
|
+
: public facebook::react::ConcreteViewShadowNode<Name, PropsT, EmitterT, SizeStateData> {
|
|
73
|
+
using Base = facebook::react::ConcreteViewShadowNode<Name, PropsT, EmitterT, SizeStateData>;
|
|
74
|
+
|
|
75
|
+
public:
|
|
76
|
+
SlotShadowNode(
|
|
77
|
+
const facebook::react::ShadowNodeFragment& fragment,
|
|
78
|
+
const facebook::react::ShadowNodeFamily::Shared& family,
|
|
79
|
+
facebook::react::ShadowNodeTraits traits)
|
|
80
|
+
: Base(fragment, family, traits) {
|
|
81
|
+
placeChildren();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
SlotShadowNode(const facebook::react::ShadowNode& source, const facebook::react::ShadowNodeFragment& fragment)
|
|
85
|
+
: Base(source, fragment) {
|
|
86
|
+
placeChildren();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Laid out: the host sees the style's own border and content box, never the slot's insets.
|
|
90
|
+
void layout(facebook::react::LayoutContext context) override {
|
|
91
|
+
Base::layout(context);
|
|
92
|
+
|
|
93
|
+
if (!this->getStateData().slot) return;
|
|
94
|
+
|
|
95
|
+
auto metrics = this->getLayoutMetrics();
|
|
96
|
+
auto border = slots::resolve(
|
|
97
|
+
borderEdges(this->getConcreteProps().yogaStyle),
|
|
98
|
+
metrics.layoutDirection == facebook::react::LayoutDirection::RightToLeft,
|
|
99
|
+
metrics.wasLeftAndRightSwapped);
|
|
100
|
+
auto& insets = metrics.contentInsets;
|
|
101
|
+
auto& width = metrics.borderWidth;
|
|
102
|
+
|
|
103
|
+
insets = {
|
|
104
|
+
insets.left - width.left + border.left,
|
|
105
|
+
insets.top - width.top + border.top,
|
|
106
|
+
insets.right - width.right + border.right,
|
|
107
|
+
insets.bottom - width.bottom + border.bottom,
|
|
108
|
+
};
|
|
109
|
+
width = {border.left, border.top, border.right, border.bottom};
|
|
110
|
+
this->setLayoutMetrics(metrics);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private:
|
|
114
|
+
/**
|
|
115
|
+
* The Yoga style: the props' border widened by the slot's insets, as the
|
|
116
|
+
* host last reported them. Written as start and end (the report's
|
|
117
|
+
* direction says which is left), which React Native's left-and-right
|
|
118
|
+
* swap leaves alone. Untouched before the first report.
|
|
119
|
+
*/
|
|
120
|
+
void placeChildren() {
|
|
121
|
+
using facebook::yoga::Edge;
|
|
122
|
+
using facebook::yoga::StyleLength;
|
|
123
|
+
|
|
124
|
+
const auto& slot = this->getStateData().slot;
|
|
125
|
+
|
|
126
|
+
if (!slot) return;
|
|
127
|
+
|
|
128
|
+
auto border = slots::resolve(borderEdges(this->getConcreteProps().yogaStyle), slot->rtl, slot->swapped);
|
|
129
|
+
const auto& in = slot->insets;
|
|
130
|
+
float left = border.left + in.left;
|
|
131
|
+
float right = border.right + in.right;
|
|
132
|
+
auto style = this->yogaNode_.style();
|
|
133
|
+
|
|
134
|
+
for (auto edge : {Edge::All, Edge::Horizontal, Edge::Vertical, Edge::Left, Edge::Right})
|
|
135
|
+
style.setBorder(edge, StyleLength::undefined());
|
|
136
|
+
|
|
137
|
+
style.setBorder(Edge::Top, StyleLength::points(border.top + in.top));
|
|
138
|
+
style.setBorder(Edge::Bottom, StyleLength::points(border.bottom + in.bottom));
|
|
139
|
+
style.setBorder(Edge::Start, StyleLength::points(slot->rtl ? right : left));
|
|
140
|
+
style.setBorder(Edge::End, StyleLength::points(slot->rtl ? left : right));
|
|
141
|
+
|
|
142
|
+
if (style == this->yogaNode_.style()) return;
|
|
143
|
+
|
|
144
|
+
this->yogaNode_.setStyle(style);
|
|
145
|
+
this->yogaNode_.setDirty(true);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* A host's side of its slot's place, for one mount (main thread): the
|
|
151
|
+
* state of the latest commit it mounted, where it last measured the slot,
|
|
152
|
+
* and its Reporter. The host tells it where the slot is after each layout
|
|
153
|
+
* of the slot, and when the mount's native views changed; it posts what
|
|
154
|
+
* the shadow tree lacks.
|
|
155
|
+
*/
|
|
156
|
+
class HostSlot {
|
|
157
|
+
public:
|
|
158
|
+
using State = facebook::react::ConcreteState<SizeStateData>;
|
|
159
|
+
|
|
160
|
+
HostSlot() = default;
|
|
161
|
+
|
|
162
|
+
/// A mount starts on the view `tag` names, at `scale` pixels per point.
|
|
163
|
+
void start(float scale, int tag) {
|
|
164
|
+
reporter_ = slots::Reporter(scale);
|
|
165
|
+
measured_.reset();
|
|
166
|
+
tag_ = tag;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// The state of the commit being mounted.
|
|
170
|
+
void setState(const facebook::react::State::Shared& state) {
|
|
171
|
+
state_ = std::static_pointer_cast<const State>(state);
|
|
172
|
+
report();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// The mount's native views changed (a commit, a command): reports go again, past the bound.
|
|
176
|
+
void contentChanged() {
|
|
177
|
+
reporter_.contentChanged();
|
|
178
|
+
report();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// The slot lies `insets` in from the content box, the component laid out as `rtl` and `swapped` say.
|
|
182
|
+
void place(slots::Insets insets, bool rtl, bool swapped) {
|
|
183
|
+
measured_ = Measured{insets, rtl, swapped};
|
|
184
|
+
report();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private:
|
|
188
|
+
struct Measured {
|
|
189
|
+
slots::Insets insets;
|
|
190
|
+
bool rtl;
|
|
191
|
+
bool swapped;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/// Posts where the slot was last measured, if the shadow tree lacks it.
|
|
195
|
+
void report() {
|
|
196
|
+
if (!state_ || !measured_) return;
|
|
197
|
+
|
|
198
|
+
bool gaveUp = reporter_.gaveUp();
|
|
199
|
+
auto posted = reporter_.record(state_->getData().slot, measured_->insets, measured_->rtl, measured_->swapped);
|
|
200
|
+
|
|
201
|
+
if (!posted) {
|
|
202
|
+
if (!gaveUp && reporter_.gaveUp()) traceSizing("%d slot gave up", tag_);
|
|
203
|
+
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
traceSizing(
|
|
208
|
+
"%d slot report %llu: %g,%g,%g,%g%s posted",
|
|
209
|
+
tag_,
|
|
210
|
+
static_cast<unsigned long long>(posted->revision),
|
|
211
|
+
posted->insets.left,
|
|
212
|
+
posted->insets.top,
|
|
213
|
+
posted->insets.right,
|
|
214
|
+
posted->insets.bottom,
|
|
215
|
+
posted->rtl ? " rtl" : "");
|
|
216
|
+
|
|
217
|
+
auto scale = reporter_.scale();
|
|
218
|
+
auto tag = tag_;
|
|
219
|
+
|
|
220
|
+
// Judged against the state current when the renderer applies it, on its thread.
|
|
221
|
+
state_->updateState([report = *posted, scale, tag](const SizeStateData& current) -> facebook::react::StateData::Shared {
|
|
222
|
+
auto verdict = slots::judge(current.slot, report, scale);
|
|
223
|
+
|
|
224
|
+
traceSizing("%d slot result %llu: %s", tag, static_cast<unsigned long long>(report.revision), slots::verdictName(verdict));
|
|
225
|
+
|
|
226
|
+
if (verdict != slots::Verdict::Accepted) return nullptr;
|
|
227
|
+
|
|
228
|
+
auto next = current;
|
|
229
|
+
|
|
230
|
+
next.slot = report;
|
|
231
|
+
|
|
232
|
+
return std::make_shared<const SizeStateData>(std::move(next));
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
std::shared_ptr<const State> state_;
|
|
237
|
+
slots::Reporter reporter_;
|
|
238
|
+
std::optional<Measured> measured_;
|
|
239
|
+
int tag_ = 0;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
} // namespace lucent::views
|