@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.
- package/README.md +97 -0
- package/app.plugin.js +46 -10
- package/dist/{bench-CR3E7e2e.js → bench-eEez4jhQ.js} +10 -6
- package/dist/{build-CAIrRSYA.js → build-CpUjeHFz.js} +1 -1
- package/dist/{build-check-QozuNJm5.js → build-check-BA1ZEp9s.js} +28 -13
- package/dist/{check-1SCmYlqA.js → check-BZx3FgI6.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-CC-HXhcd.js} +2 -2
- package/dist/{dev-BYWMP7oC.js → dev-DI5yL8zx.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-BM_VM2X_.js +700 -0
- package/dist/project-BKNoTEEp.js +342 -0
- package/dist/sdk-coverage-iiv3JqMp.js +113 -0
- package/dist/sdk-diff-ByJ1Gw-F.js +109 -0
- package/dist/sdk-lock-BQn7IyRs.js +38 -0
- package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-Bv2pNega.js} +2 -2
- package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-CPGJm_Ne.js} +2 -2
- package/dist/{sdk-show-CkrdlqlP.js → sdk-show-BUkLqRnA.js} +5 -4
- package/dist/sdk-usage-0ZFMAYg1.js +95 -0
- package/dist/src-ChnmWLwn.js +40851 -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 +4 -3
- 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 +85 -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,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
|