@lucent-lang/lucent 0.0.4 → 0.1.1

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-eEez4jhQ.js} +10 -6
  4. package/dist/{build-CAIrRSYA.js → build-CpUjeHFz.js} +1 -1
  5. package/dist/{build-check-QozuNJm5.js → build-check-BA1ZEp9s.js} +28 -13
  6. package/dist/{check-1SCmYlqA.js → check-BZx3FgI6.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-CC-HXhcd.js} +2 -2
  11. package/dist/{dev-BYWMP7oC.js → dev-DI5yL8zx.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-BM_VM2X_.js +700 -0
  18. package/dist/project-BKNoTEEp.js +342 -0
  19. package/dist/sdk-coverage-iiv3JqMp.js +113 -0
  20. package/dist/sdk-diff-ByJ1Gw-F.js +109 -0
  21. package/dist/sdk-lock-BQn7IyRs.js +38 -0
  22. package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-Bv2pNega.js} +2 -2
  23. package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-CPGJm_Ne.js} +2 -2
  24. package/dist/{sdk-show-CkrdlqlP.js → sdk-show-BUkLqRnA.js} +5 -4
  25. package/dist/sdk-usage-0ZFMAYg1.js +95 -0
  26. package/dist/src-ChnmWLwn.js +40851 -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 +4 -3
  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 +85 -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,109 @@
1
+ // Lucent runtime — presentations: UI shown in a scene until a result.
2
+ //
3
+ // Presenting a view controller, or a system sheet, and waiting for what
4
+ // the person does there is a one-shot operation (scope.h) under the
5
+ // caller's scope, tied to the scene it is shown in. It settles exactly
6
+ // once, at the first of: a result or an error, the caller's signal, the
7
+ // disposal of the caller's scope, or the scene disconnecting. Whatever
8
+ // settles it, what it showed is then dismissed, on the main thread.
9
+ //
10
+ // The platform adapter (lucent/platform/ios_ui.h) shows and dismisses;
11
+ // this part knows nothing of UIKit.
12
+ #pragma once
13
+
14
+ #include <functional>
15
+ #include <memory>
16
+ #include <stdexcept>
17
+
18
+ #include "abort.h"
19
+ #include "execution.h"
20
+ #include "lifecycle.h"
21
+ #include "scope.h"
22
+
23
+ namespace lucent {
24
+
25
+ /// What cancels a presentation whose scene disconnected: an AbortError.
26
+ Error sceneGoneError();
27
+
28
+ /// What fails a presentation with no scene to present from: an
29
+ /// InvalidStateError.
30
+ Error noSceneError();
31
+
32
+ /// Shows the UI of `operation`, and returns what dismisses it (or an empty
33
+ /// function). It may settle the operation before returning; what it throws
34
+ /// fails it.
35
+ template <class T>
36
+ using Show = std::function<std::function<void()>(const std::shared_ptr<Operation<T>>&)>;
37
+
38
+ /**
39
+ * Presents UI in `scene` (a scene `lifecycle` knows, else it fails with
40
+ * noSceneError()) as an operation under `scope`. On the main thread only
41
+ * (std::logic_error elsewhere): `show` runs here, now.
42
+ *
43
+ * Already aborted, or under a scope that has ended, it starts cancelled
44
+ * and shows nothing. Afterwards, aborting `signal` cancels it with the
45
+ * signal's reason, disposing `scope` or the scene's disconnecting cancels
46
+ * it with an AbortError. Once it settles, the dismissal `show` returned
47
+ * runs on the main thread (there and then if it settles there, else
48
+ * posted), and nothing refers to the scene or the signal any more.
49
+ */
50
+ template <class T>
51
+ std::shared_ptr<Operation<T>> presentIn(Lifecycle& lifecycle, SceneId scene, const std::shared_ptr<Scope>& scope, Opt<AbortSignal> signal,
52
+ const Show<T>& show) {
53
+ if (!onMainThread()) throw std::logic_error("presentIn runs on the main thread");
54
+ if (!show) throw std::invalid_argument("A presentation's show must be a function");
55
+
56
+ AbortSignal abort = signal.has() ? signal.get() : nullptr;
57
+ Opt<Error> abortedBy;
58
+ if (abort && abort->aborted.load()) abortedBy = abort->reason;
59
+
60
+ std::shared_ptr<Scope> sceneScope = lifecycle.sceneScope(scene);
61
+
62
+ auto registration = [&](const std::shared_ptr<Operation<T>>& op) -> std::function<void()> {
63
+ if (!sceneScope) throwError(noSceneError());
64
+
65
+ std::weak_ptr<Operation<T>> weak = op;
66
+
67
+ // The scene going cancels it; so does the signal, on the signal's owner.
68
+ Scope::CleanupId gone = sceneScope->onDispose([weak] {
69
+ if (auto op = weak.lock()) op->cancel(sceneGoneError());
70
+ });
71
+
72
+ std::weak_ptr<AbortSignalObject> weakSignal = abort;
73
+ uint64_t aborted = abort ? abort->add([weak, weakSignal] {
74
+ auto op = weak.lock();
75
+ auto signal = weakSignal.lock();
76
+ if (op && signal) op->cancel(signal->reason);
77
+ })
78
+ : 0;
79
+
80
+ std::weak_ptr<Scope> weakScene = sceneScope;
81
+ auto unlink = [weakScene, gone, weakSignal, aborted] {
82
+ if (auto s = weakScene.lock(); s && gone) s->remove(gone);
83
+ if (auto s = weakSignal.lock(); s && aborted) s->remove(aborted);
84
+ };
85
+
86
+ // The scene went while it was being linked: nothing to show.
87
+ if (op->state() != OperationState::Pending) {
88
+ unlink();
89
+ return {};
90
+ }
91
+
92
+ std::function<void()> dismiss;
93
+ try {
94
+ dismiss = show(op);
95
+ } catch (...) {
96
+ unlink();
97
+ throw;
98
+ }
99
+
100
+ return [unlink, dismiss = std::move(dismiss)]() mutable {
101
+ unlink();
102
+ if (dismiss) detail::runOn(&ExecutionContext::main(), std::move(dismiss));
103
+ };
104
+ };
105
+
106
+ return Operation<T>::start(scope, registration, abortedBy);
107
+ }
108
+
109
+ } // namespace lucent
@@ -0,0 +1,406 @@
1
+ #include "reactive.h"
2
+
3
+ #include <algorithm>
4
+
5
+ #include "report.h"
6
+
7
+ namespace lucent::ui {
8
+
9
+ namespace detail {
10
+
11
+ namespace {
12
+
13
+ void unsubscribe(Node& source, Observer* observer) {
14
+ auto& list = source.observers_;
15
+ list.erase(std::remove(list.begin(), list.end(), observer), list.end());
16
+ }
17
+
18
+ } // namespace
19
+
20
+ bool Observer::Edges::has(const Node* node) const {
21
+ if (list_.size() <= kLinear)
22
+ return std::any_of(list_.begin(), list_.end(), [node](const Edge& e) { return e.source.get() == node; });
23
+
24
+ return index_.count(node) > 0;
25
+ }
26
+
27
+ void Observer::Edges::add(Edge edge) {
28
+ list_.push_back(std::move(edge));
29
+ if (list_.size() <= kLinear) return;
30
+
31
+ if (index_.empty())
32
+ for (auto& e : list_) index_.insert(e.source.get());
33
+ else
34
+ index_.insert(list_.back().source.get());
35
+ }
36
+
37
+ void Observer::Edges::clear() {
38
+ list_.clear();
39
+ index_.clear();
40
+ }
41
+
42
+ Observer::~Observer() { unlinkAll(); }
43
+
44
+ bool Observer::stale() {
45
+ if (fresh_) return true;
46
+
47
+ for (auto& e : sources_) {
48
+ e.source->refresh();
49
+ if (e.source->version_ != e.version) return true;
50
+ }
51
+
52
+ return false;
53
+ }
54
+
55
+ void Observer::track(Node& source) {
56
+ if (next_.has(&source)) return;
57
+
58
+ next_.add({source.shared_from_this(), source.version_});
59
+
60
+ // Subscribed at once, so a write later in this run reaches it.
61
+ if (!sources_.has(&source)) source.observers_.push_back(this);
62
+ }
63
+
64
+ void Observer::finish() {
65
+ for (auto& e : sources_)
66
+ if (!next_.has(e.source.get())) unsubscribe(*e.source, this);
67
+
68
+ sources_ = std::move(next_);
69
+ next_.clear();
70
+ }
71
+
72
+ void Observer::unlinkAll() {
73
+ for (auto& e : sources_) unsubscribe(*e.source, this);
74
+ for (auto& e : next_) unsubscribe(*e.source, this);
75
+
76
+ sources_.clear();
77
+ next_.clear();
78
+ }
79
+
80
+ } // namespace detail
81
+
82
+ std::shared_ptr<Graph> Graph::create(ExecutionContext& owner, GraphOptions options) {
83
+ return std::make_shared<Graph>(Token{}, owner, std::move(options));
84
+ }
85
+
86
+ Graph::Graph(Token, ExecutionContext& owner, GraphOptions options) : owner_(owner), options_(std::move(options)) {
87
+ // A context made by make_shared owns the scopes (their disposal runs there).
88
+ ownerRef_ = std::weak_ptr<Scope::Owner>(owner.weak_from_this().lock());
89
+ root_ = Scope::createRoot(0, ownerRef_);
90
+ scope_ = root_;
91
+ }
92
+
93
+ void Graph::checkOwner() const {
94
+ if (!owner_.isCurrent()) throw std::logic_error("A UI reactive graph is used only on the context that owns it");
95
+ }
96
+
97
+ void Graph::checkWritable() const {
98
+ if (computing_ > 0) throwError(String::fromLatin1("InvalidStateError"), String::fromLatin1("A computed value cannot write a signal"));
99
+ }
100
+
101
+ void Graph::onCleanup(std::function<void()> cleanup) {
102
+ checkOwner();
103
+ scope_->onDispose(std::move(cleanup));
104
+ }
105
+
106
+ void Graph::dispose() {
107
+ checkOwner();
108
+ Batch batch(*this);
109
+ Tracking none(*this, nullptr);
110
+
111
+ report(root_->dispose(), "effect cleanup");
112
+ }
113
+
114
+ void Graph::read(detail::Node& node) {
115
+ if (tracker_) tracker_->track(node);
116
+ }
117
+
118
+ void Graph::changed(detail::Node& node) {
119
+ node.version_++;
120
+
121
+ epoch_++;
122
+ mark(node);
123
+
124
+ if (batch_ == 0) flushPending();
125
+ }
126
+
127
+ void Graph::mark(detail::Node& node) {
128
+ // By index: the list does not change while marking, but stays valid if it did.
129
+ for (size_t i = 0; i < node.observers_.size(); i++) {
130
+ detail::Observer* o = node.observers_[i];
131
+ if (o->mark_ == epoch_) continue;
132
+
133
+ o->mark_ = epoch_;
134
+ o->maybeStale_ = true;
135
+
136
+ if (o->effect_)
137
+ enqueue(static_cast<detail::EffectNode&>(*o));
138
+ else
139
+ mark(*o);
140
+ }
141
+ }
142
+
143
+ void Graph::enqueue(detail::EffectNode& effect) {
144
+ if (effect.queued_ || effect.disposed_) return;
145
+
146
+ effect.queued_ = true;
147
+ effect.cause_ = running_ ? std::static_pointer_cast<detail::EffectNode>(running_->shared_from_this()) : nullptr;
148
+ queue_[effect.order_] = std::static_pointer_cast<detail::EffectNode>(effect.shared_from_this());
149
+ }
150
+
151
+ void Graph::flushPending() {
152
+ if (flushing_ || queue_.empty()) return;
153
+
154
+ struct Updating {
155
+ explicit Updating(Graph& g) : g(g) {
156
+ g.flushing_ = true;
157
+ g.batch_++;
158
+ g.updates_++;
159
+ }
160
+ ~Updating() {
161
+ g.batch_--;
162
+ g.flushing_ = false;
163
+ }
164
+ Graph& g;
165
+ } updating(*this);
166
+
167
+ while (!queue_.empty()) {
168
+ auto effect = queue_.begin()->second;
169
+ queue_.erase(queue_.begin());
170
+ effect->queued_ = false;
171
+
172
+ if (effect->disposed_) continue;
173
+
174
+ if (!effect->stale()) {
175
+ effect->maybeStale_ = false;
176
+ continue;
177
+ }
178
+
179
+ if (effect->update_ == updates_ && effect->runs_ >= options_.loopLimit) {
180
+ loop(*effect);
181
+
182
+ // The rest wait for their next change.
183
+ for (auto& [order, pending] : queue_) pending->queued_ = false;
184
+ queue_.clear();
185
+ break;
186
+ }
187
+
188
+ runEffect(*effect);
189
+ }
190
+ }
191
+
192
+ void Graph::runEffect(detail::EffectNode& e) {
193
+ auto keep = e.shared_from_this();
194
+ Batch batch(*this);
195
+
196
+ struct Running {
197
+ Running(Graph& g, detail::EffectNode* e) : g(g), outer(g.running_) { g.running_ = e; }
198
+ ~Running() { g.running_ = outer; }
199
+ Graph& g;
200
+ detail::EffectNode* outer;
201
+ } running(*this, &e);
202
+
203
+ if (e.run_) {
204
+ Tracking none(*this, nullptr);
205
+ report(e.run_->dispose(), "effect cleanup");
206
+ }
207
+
208
+ if (e.disposed_) return;
209
+
210
+ e.run_ = Scope::createRoot(e.runtime_, ownerRef_);
211
+ e.fresh_ = false;
212
+ e.maybeStale_ = false;
213
+
214
+ if (flushing_) {
215
+ if (e.update_ != updates_) {
216
+ e.update_ = updates_;
217
+ e.runs_ = 0;
218
+ }
219
+ e.runs_++;
220
+ }
221
+
222
+ e.begin();
223
+ {
224
+ Tracking tracking(*this, &e);
225
+ ScopeSwap swap(*this, e.run_);
226
+
227
+ try {
228
+ e.fn_();
229
+ } catch (...) {
230
+ report(std::current_exception(), "effect");
231
+ }
232
+ }
233
+
234
+ if (e.disposed_)
235
+ e.unlinkAll();
236
+ else
237
+ e.finish();
238
+ }
239
+
240
+ void Graph::disposeEffect(detail::EffectNode& e) {
241
+ if (e.disposed_) return;
242
+
243
+ auto keep = e.shared_from_this();
244
+ e.disposed_ = true;
245
+
246
+ if (auto owner = e.owner_.lock()) owner->remove(e.registration_);
247
+ e.unlinkAll();
248
+
249
+ Batch batch(*this);
250
+ if (e.run_) {
251
+ Tracking none(*this, nullptr);
252
+ report(e.run_->dispose(), "effect cleanup");
253
+ }
254
+ }
255
+
256
+ void Graph::loop(detail::EffectNode& e) {
257
+ // Back from `e` through the effects that made each pending, to the first repeat.
258
+ std::vector<std::shared_ptr<detail::EffectNode>> chain;
259
+ auto x = std::static_pointer_cast<detail::EffectNode>(e.shared_from_this());
260
+
261
+ while (x && std::find(chain.begin(), chain.end(), x) == chain.end()) {
262
+ chain.push_back(x);
263
+ x = x->cause_.lock();
264
+ }
265
+
266
+ std::string names = x ? x->name_ : "";
267
+ for (auto it = chain.rbegin(); it != chain.rend(); ++it) names += (names.empty() ? "" : " -> ") + (*it)->name_;
268
+
269
+ std::string message = "Effects kept rerunning: " + names + " (" + e.name_ + " ran " + std::to_string(options_.loopLimit) +
270
+ " times in one update)";
271
+
272
+ report(std::make_exception_ptr(Exception(makeError(String::fromLatin1("RangeError"), String::fromUtf8(message)))), "effect loop");
273
+ }
274
+
275
+ void Graph::report(std::exception_ptr error, const char* where) {
276
+ if (!error) return;
277
+
278
+ try {
279
+ if (options_.onError)
280
+ options_.onError(error, where);
281
+ else
282
+ reportUncaught(error, where);
283
+ } catch (...) {
284
+ reportUncaught(std::current_exception(), "a UI reactive graph's error handler");
285
+ }
286
+ }
287
+
288
+ Effect effect(const std::shared_ptr<Graph>& graph, std::function<void()> fn, std::string name) {
289
+ Graph& g = *graph;
290
+ g.checkOwner();
291
+
292
+ auto node = std::make_shared<detail::EffectNode>(graph, std::move(fn), std::move(name), ++g.made_);
293
+ const std::shared_ptr<Scope>& scope = g.scope_;
294
+
295
+ if (scope->state() != Scope::State::Active) {
296
+ node->disposed_ = true;
297
+ return Effect(node);
298
+ }
299
+
300
+ node->runtime_ = scope->runtime();
301
+ node->owner_ = scope;
302
+ node->registration_ = scope->onDispose([node] { node->graph_->disposeEffect(*node); });
303
+
304
+ // 0: the scope stopped taking cleanups, and ran this one at once.
305
+ if (node->registration_ == 0) return Effect(node);
306
+
307
+ g.runEffect(*node);
308
+ return Effect(node);
309
+ }
310
+
311
+ void Effect::dispose() const {
312
+ if (!node_) return;
313
+
314
+ Graph& g = *node_->graph_;
315
+ g.checkOwner();
316
+ g.disposeEffect(*node_);
317
+ }
318
+
319
+ std::shared_ptr<PropInbox> PropInbox::create(std::shared_ptr<Graph> graph, const std::shared_ptr<Scope>& mount) {
320
+ graph->checkOwner();
321
+
322
+ auto inbox = std::make_shared<PropInbox>(Token{}, std::move(graph), mount);
323
+ std::weak_ptr<PropInbox> weak = inbox;
324
+
325
+ // Run at once, closing it, if the mount is already gone.
326
+ mount->onDispose([weak] {
327
+ if (auto self = weak.lock()) self->close();
328
+ });
329
+
330
+ return inbox;
331
+ }
332
+
333
+ PropInbox::PropInbox(Token, std::shared_ptr<Graph> graph, const std::shared_ptr<Scope>& mount)
334
+ : graph_(std::move(graph)), mount_(mount) {}
335
+
336
+ bool PropInbox::post(Commit commit) {
337
+ bool schedule;
338
+ {
339
+ std::lock_guard<std::mutex> g(m_);
340
+ if (closed_) return false;
341
+
342
+ for (auto& change : commit) pending_[change.field] = std::move(change.write);
343
+ stats_.commits++;
344
+
345
+ schedule = !scheduled_;
346
+ scheduled_ = true;
347
+ }
348
+
349
+ if (!schedule) return true;
350
+
351
+ // Owned by the mount: dropped if it is disposed first.
352
+ auto mount = mount_.lock();
353
+ std::weak_ptr<PropInbox> weak = weak_from_this();
354
+ bool posted = mount && graph_->owner().post(
355
+ [weak] {
356
+ if (auto self = weak.lock()) self->drain();
357
+ },
358
+ mount);
359
+
360
+ if (!posted) {
361
+ std::lock_guard<std::mutex> g(m_);
362
+ scheduled_ = false;
363
+ }
364
+
365
+ return true;
366
+ }
367
+
368
+ bool PropInbox::drain() {
369
+ graph_->checkOwner();
370
+
371
+ std::map<uint32_t, std::function<void()>> commit;
372
+ {
373
+ std::lock_guard<std::mutex> g(m_);
374
+ commit.swap(pending_);
375
+ scheduled_ = false;
376
+
377
+ if (commit.empty()) return false;
378
+ stats_.applied++;
379
+ }
380
+
381
+ graph_->transaction([&] {
382
+ for (auto& [field, write] : commit) write();
383
+ });
384
+
385
+ return true;
386
+ }
387
+
388
+ void PropInbox::close() {
389
+ std::map<uint32_t, std::function<void()>> dropped;
390
+
391
+ std::lock_guard<std::mutex> g(m_);
392
+ closed_ = true;
393
+ dropped.swap(pending_);
394
+ }
395
+
396
+ bool PropInbox::closed() const {
397
+ std::lock_guard<std::mutex> g(m_);
398
+ return closed_;
399
+ }
400
+
401
+ PropInbox::Stats PropInbox::stats() const {
402
+ std::lock_guard<std::mutex> g(m_);
403
+ return stats_;
404
+ }
405
+
406
+ } // namespace lucent::ui