@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.
Files changed (152) hide show
  1. package/README.md +97 -0
  2. package/app.plugin.js +46 -10
  3. package/dist/{bench-CR3E7e2e.js → bench-B555m0wU.js} +10 -6
  4. package/dist/{build-CAIrRSYA.js → build-UZ5Zm5m6.js} +1 -1
  5. package/dist/{build-check-QozuNJm5.js → build-check-Dmf8_yiw.js} +28 -13
  6. package/dist/{check-1SCmYlqA.js → check-D-H9WLG4.js} +1 -1
  7. package/dist/cli.js +62 -13
  8. package/dist/{codes-CIuwXt1S.js → codes-D-6LxUqD.js} +163 -7
  9. package/dist/compiler.js +4 -4
  10. package/dist/{dashboard-B-NzjjaN.js → dashboard-C8iF5-6T.js} +2 -2
  11. package/dist/{dev-BYWMP7oC.js → dev-B3lSx5CF.js} +62 -23
  12. package/dist/{diagnostic-BM9sXmwl.js → diagnostic-BvwLZQS1.js} +3 -1
  13. package/dist/{doctor-C0PrfWT6.js → doctor-CTAX3euc.js} +1 -1
  14. package/dist/{doctor-DdR4JnVc.js → doctor-CrgOAV5L.js} +1 -1
  15. package/dist/{explain--vV1oaRl.js → explain-Byit07F-.js} +1 -1
  16. package/dist/{packages-HyskKIsY.js → packages-CSmKeaAh.js} +2 -33
  17. package/dist/pipeline-BBY0df_2.js +700 -0
  18. package/dist/project-BLhWwnns.js +342 -0
  19. package/dist/sdk-coverage-DFGiDOzR.js +113 -0
  20. package/dist/sdk-diff-DpPYWzrl.js +109 -0
  21. package/dist/sdk-lock-QOxEFD1o.js +38 -0
  22. package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-DX6ztlPN.js} +2 -2
  23. package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-BShBL1LL.js} +2 -2
  24. package/dist/{sdk-show-CkrdlqlP.js → sdk-show-B3bI-0M7.js} +5 -4
  25. package/dist/sdk-usage-ljX5bTuZ.js +95 -0
  26. package/dist/src-DlICbK91.js +39682 -0
  27. package/dist/trace-Bi4hkmPx.js +139 -0
  28. package/lib/core.cjs +368 -1
  29. package/lib/sdk/android.d.ts +54 -1
  30. package/lib/sdk/compose.d.ts +94 -0
  31. package/lib/sdk/compose.schemas.json.gz +0 -0
  32. package/lib/sdk/core.d.ts +201 -0
  33. package/lib/sdk/ios.d.ts +75 -2
  34. package/lib/sdk/ui.d.ts +129 -0
  35. package/metro/transformer.cjs +11 -2
  36. package/package.json +2 -1
  37. package/runtime/cpp/lucent/abort.cpp +59 -11
  38. package/runtime/cpp/lucent/abort.h +22 -6
  39. package/runtime/cpp/lucent/array.h +12 -2
  40. package/runtime/cpp/lucent/async.h +101 -21
  41. package/runtime/cpp/lucent/bigint.cpp +898 -0
  42. package/runtime/cpp/lucent/bigint.h +302 -0
  43. package/runtime/cpp/lucent/buffer.cpp +228 -0
  44. package/runtime/cpp/lucent/buffer.h +328 -0
  45. package/runtime/cpp/lucent/builtins.cpp +4 -1
  46. package/runtime/cpp/lucent/bytes.h +21 -2
  47. package/runtime/cpp/lucent/callback.cpp +29 -0
  48. package/runtime/cpp/lucent/callback.h +230 -0
  49. package/runtime/cpp/lucent/compute.cpp +370 -0
  50. package/runtime/cpp/lucent/compute.h +455 -0
  51. package/runtime/cpp/lucent/conversions.h +111 -0
  52. package/runtime/cpp/lucent/core.h +16 -5
  53. package/runtime/cpp/lucent/equality.h +107 -28
  54. package/runtime/cpp/lucent/execution.cpp +416 -0
  55. package/runtime/cpp/lucent/execution.h +270 -0
  56. package/runtime/cpp/lucent/extension.cpp +97 -0
  57. package/runtime/cpp/lucent/extension.h +210 -0
  58. package/runtime/cpp/lucent/helpers.h +12 -5
  59. package/runtime/cpp/lucent/items.h +52 -0
  60. package/runtime/cpp/lucent/jserror.cpp +9 -0
  61. package/runtime/cpp/lucent/jserror.h +11 -0
  62. package/runtime/cpp/lucent/jsi/convert.cpp +138 -0
  63. package/runtime/cpp/lucent/jsi/convert.h +128 -59
  64. package/runtime/cpp/lucent/jsi/host.cpp +213 -35
  65. package/runtime/cpp/lucent/jsi/host.h +116 -16
  66. package/runtime/cpp/lucent/json.h +19 -1
  67. package/runtime/cpp/lucent/lifecycle.cpp +260 -0
  68. package/runtime/cpp/lucent/lifecycle.h +124 -0
  69. package/runtime/cpp/lucent/lucent.h +7 -0
  70. package/runtime/cpp/lucent/map.h +41 -7
  71. package/runtime/cpp/lucent/native.cpp +35 -47
  72. package/runtime/cpp/lucent/native.h +116 -26
  73. package/runtime/cpp/lucent/number.cpp +38 -38
  74. package/runtime/cpp/lucent/number.h +33 -0
  75. package/runtime/cpp/lucent/operation.h +65 -0
  76. package/runtime/cpp/lucent/ops.h +14 -6
  77. package/runtime/cpp/lucent/platform/android.cpp +223 -10
  78. package/runtime/cpp/lucent/platform/android.h +227 -4
  79. package/runtime/cpp/lucent/platform/android_activity.cpp +258 -0
  80. package/runtime/cpp/lucent/platform/android_requests.h +140 -0
  81. package/runtime/cpp/lucent/platform/compose.h +256 -0
  82. package/runtime/cpp/lucent/platform/ios.h +325 -25
  83. package/runtime/cpp/lucent/platform/ios.mm +1 -14
  84. package/runtime/cpp/lucent/platform/ios_ui.h +192 -0
  85. package/runtime/cpp/lucent/platform/ios_ui.mm +454 -0
  86. package/runtime/cpp/lucent/platform/swiftui.h +190 -0
  87. package/runtime/cpp/lucent/presentation.cpp +11 -0
  88. package/runtime/cpp/lucent/presentation.h +109 -0
  89. package/runtime/cpp/lucent/reactive.cpp +406 -0
  90. package/runtime/cpp/lucent/reactive.h +566 -0
  91. package/runtime/cpp/lucent/report.cpp +40 -0
  92. package/runtime/cpp/lucent/report.h +16 -0
  93. package/runtime/cpp/lucent/resource.cpp +83 -0
  94. package/runtime/cpp/lucent/resource.h +75 -0
  95. package/runtime/cpp/lucent/scheduler.cpp +38 -124
  96. package/runtime/cpp/lucent/scheduler.h +102 -83
  97. package/runtime/cpp/lucent/scope.cpp +308 -0
  98. package/runtime/cpp/lucent/scope.h +452 -0
  99. package/runtime/cpp/lucent/sizing.h +263 -0
  100. package/runtime/cpp/lucent/slots.h +231 -0
  101. package/runtime/cpp/lucent/trace.cpp +539 -0
  102. package/runtime/cpp/lucent/trace.h +251 -0
  103. package/runtime/cpp/lucent/transport.cpp +9 -0
  104. package/runtime/cpp/lucent/transport.h +341 -0
  105. package/runtime/cpp/lucent/view.cpp +123 -0
  106. package/runtime/cpp/lucent/view.h +157 -0
  107. package/runtime/cpp/rn/LucentComponentView.h +121 -0
  108. package/runtime/cpp/rn/LucentComponentView.mm +479 -0
  109. package/runtime/cpp/rn/LucentModule.cpp +37 -7
  110. package/runtime/cpp/rn/LucentModule.h +3 -0
  111. package/runtime/cpp/rn/LucentViewChildren.h +80 -0
  112. package/runtime/cpp/rn/LucentViewChildren.mm +211 -0
  113. package/runtime/cpp/rn/LucentViewRegistry.h +62 -0
  114. package/runtime/cpp/rn/LucentViewRequests.cpp +91 -0
  115. package/runtime/cpp/rn/LucentViewRequests.h +104 -0
  116. package/runtime/cpp/rn/LucentViewSizing.h +345 -0
  117. package/runtime/cpp/rn/LucentViewSlots.h +242 -0
  118. package/runtime/cpp/rn/LucentViewValues.h +125 -0
  119. package/runtime/cpp/rn/LucentViews.h +440 -0
  120. package/runtime/cpp/rn/LucentViewsAndroid.cpp +439 -0
  121. package/runtime/cpp/rn/LucentViewsAndroid.h +138 -0
  122. package/runtime/cpp/third_party/dragonbox/LICENSE-Boost +23 -0
  123. package/runtime/cpp/third_party/dragonbox/README.md +16 -0
  124. package/runtime/cpp/third_party/dragonbox/dragonbox.h +4440 -0
  125. package/runtime/js/index.d.ts +11 -1
  126. package/runtime/js/index.js +84 -8
  127. package/runtime/js/views.d.ts +38 -0
  128. package/runtime/js/views.js +226 -0
  129. package/runtime/native/android/CMakeLists.txt +4 -1
  130. package/runtime/native/android/src/compose/java/dev/lucent/compose/CompositionRules.kt +44 -0
  131. package/runtime/native/android/src/compose/java/dev/lucent/compose/LucentComposition.kt +161 -0
  132. package/runtime/native/android/src/main/java/dev/lucent/LucentActivities.java +292 -0
  133. package/runtime/native/android/src/main/java/dev/lucent/LucentChildren.java +174 -0
  134. package/runtime/native/android/src/main/java/dev/lucent/LucentHostView.java +232 -0
  135. package/runtime/native/android/src/main/java/dev/lucent/LucentInitializer.java +46 -0
  136. package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +4 -3
  137. package/runtime/native/android/src/main/java/dev/lucent/LucentRequestActivity.java +92 -0
  138. package/runtime/native/android/src/main/java/dev/lucent/LucentSlotView.java +102 -0
  139. package/runtime/native/android/src/main/java/dev/lucent/LucentViewManager.java +149 -0
  140. package/runtime/native/android/src/main/java/dev/lucent/LucentViewManagers.java +16 -0
  141. package/runtime/native/android/src/main/java/dev/lucent/LucentViews.java +44 -0
  142. package/runtime/test/jsi/harness.cpp +9 -0
  143. package/schemas/build.schema.json +96 -1
  144. package/schemas/check.schema.json +56 -1
  145. package/schemas/lucent.schema.json +495 -9
  146. package/schemas/sdk-diff.schema.json +70 -0
  147. package/schemas/sdk-lock.schema.json +67 -0
  148. package/ts-plugin/index.js +77 -4
  149. package/dist/pipeline-CH7-2cxu.js +0 -232
  150. package/dist/project-D8tEet0k.js +0 -150
  151. package/dist/sdk-coverage-BoW_3Efd.js +0 -64
  152. 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