@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,452 @@
|
|
|
1
|
+
// Lucent runtime — lifetime scopes and one-shot operations.
|
|
2
|
+
//
|
|
3
|
+
// A Scope owns work that must end when its owner does (a module instance,
|
|
4
|
+
// a view mount, a subscription, a task): child scopes, pending operations
|
|
5
|
+
// and cleanups. Disposing it ends all of them, deterministically and once.
|
|
6
|
+
//
|
|
7
|
+
// An Operation is one pending native request under a scope: it settles
|
|
8
|
+
// exactly once, from any thread, and whatever loses the race is released,
|
|
9
|
+
// never delivered. Its token names it without a pointer, so a late native
|
|
10
|
+
// callback can be checked against the scope's current state.
|
|
11
|
+
//
|
|
12
|
+
// Neither knows about JavaScript, the scheduler or the Lucent lock: callbacks
|
|
13
|
+
// run on the thread that settles or disposes, except that a scope with an
|
|
14
|
+
// owner (Scope::Owner, an execution context) is disposed there.
|
|
15
|
+
#pragma once
|
|
16
|
+
|
|
17
|
+
#include <atomic>
|
|
18
|
+
#include <cstdint>
|
|
19
|
+
#include <exception>
|
|
20
|
+
#include <functional>
|
|
21
|
+
#include <map>
|
|
22
|
+
#include <memory>
|
|
23
|
+
#include <mutex>
|
|
24
|
+
#include <optional>
|
|
25
|
+
#include <stdexcept>
|
|
26
|
+
#include <type_traits>
|
|
27
|
+
#include <utility>
|
|
28
|
+
#include <vector>
|
|
29
|
+
|
|
30
|
+
#include "core.h"
|
|
31
|
+
#include "jserror.h"
|
|
32
|
+
|
|
33
|
+
namespace lucent {
|
|
34
|
+
|
|
35
|
+
/// 0: no JS runtime (headless or native-only work).
|
|
36
|
+
using RuntimeId = uint32_t;
|
|
37
|
+
/// Process-unique and never reused; 0 is none.
|
|
38
|
+
using ScopeId = uint64_t;
|
|
39
|
+
/// Per scope; changes when the scope is disposed.
|
|
40
|
+
using Generation = uint32_t;
|
|
41
|
+
/// Process-unique and never reused; 0 is none.
|
|
42
|
+
using OperationId = uint64_t;
|
|
43
|
+
|
|
44
|
+
/// Valid only while all four fields match a live scope and the operation is
|
|
45
|
+
/// still pending there (Scope::validates).
|
|
46
|
+
struct OperationToken {
|
|
47
|
+
RuntimeId runtime = 0;
|
|
48
|
+
ScopeId scope = 0;
|
|
49
|
+
Generation generation = 0;
|
|
50
|
+
OperationId operation = 0;
|
|
51
|
+
|
|
52
|
+
friend bool operator==(const OperationToken&, const OperationToken&) = default;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
enum class OperationState : uint8_t { Pending, Succeeded, Failed, Cancelled };
|
|
56
|
+
|
|
57
|
+
/// The AbortError a disposing scope cancels its pending operations with.
|
|
58
|
+
Error scopeDisposedError();
|
|
59
|
+
|
|
60
|
+
class Scope;
|
|
61
|
+
|
|
62
|
+
namespace detail {
|
|
63
|
+
|
|
64
|
+
/// Aggregates like `using`: `next`, thrown while `pending` was, becomes a
|
|
65
|
+
/// SuppressedError of both. Either may be null.
|
|
66
|
+
std::exception_ptr aggregateErrors(std::exception_ptr pending, std::exception_ptr next);
|
|
67
|
+
|
|
68
|
+
/// The part of an operation its scope uses, whatever the value type.
|
|
69
|
+
class OperationBase : public std::enable_shared_from_this<OperationBase> {
|
|
70
|
+
public:
|
|
71
|
+
OperationBase(const OperationBase&) = delete;
|
|
72
|
+
OperationBase& operator=(const OperationBase&) = delete;
|
|
73
|
+
virtual ~OperationBase() = default;
|
|
74
|
+
|
|
75
|
+
const OperationToken& token() const { return token_; }
|
|
76
|
+
|
|
77
|
+
OperationState state() const { return state_.load(std::memory_order_acquire); }
|
|
78
|
+
|
|
79
|
+
/// Outcomes that arrived after settlement and were dropped: repeated or
|
|
80
|
+
/// racing native callbacks.
|
|
81
|
+
uint64_t lateOutcomes() const { return late_.load(std::memory_order_relaxed); }
|
|
82
|
+
|
|
83
|
+
/// The registration's cleanup (unregistering the native listener, say).
|
|
84
|
+
/// It runs once, after settlement: then, or now if the operation already
|
|
85
|
+
/// settled (it completed during registration). An empty function removes
|
|
86
|
+
/// a cleanup that has not run; setting a second one is a caller bug.
|
|
87
|
+
void setCleanup(std::function<void()> cleanup);
|
|
88
|
+
|
|
89
|
+
protected:
|
|
90
|
+
OperationBase() = default;
|
|
91
|
+
|
|
92
|
+
/// Takes a token from `scope`, and joins its pending operations if
|
|
93
|
+
/// `pending` and the scope is active; false otherwise.
|
|
94
|
+
bool attach(const std::shared_ptr<Scope>& scope, bool pending);
|
|
95
|
+
|
|
96
|
+
/// Leaves the scope's pending operations, once settled.
|
|
97
|
+
void detach();
|
|
98
|
+
|
|
99
|
+
/// Runs the cleanup if the operation settled and it has not run.
|
|
100
|
+
std::exception_ptr runCleanup();
|
|
101
|
+
|
|
102
|
+
void countLate() { late_.fetch_add(1, std::memory_order_relaxed); }
|
|
103
|
+
|
|
104
|
+
/// Reports what an operation's callback threw (reportUncaught).
|
|
105
|
+
static void report(std::exception_ptr e);
|
|
106
|
+
|
|
107
|
+
/// Cancels for Scope::dispose, which runs the cleanups afterwards. Returns
|
|
108
|
+
/// what the listeners threw.
|
|
109
|
+
virtual std::exception_ptr cancelOwned(const Error& reason) = 0;
|
|
110
|
+
|
|
111
|
+
/// Guards the state transition and every callback slot.
|
|
112
|
+
mutable std::mutex m_;
|
|
113
|
+
std::atomic<OperationState> state_{OperationState::Pending};
|
|
114
|
+
|
|
115
|
+
private:
|
|
116
|
+
friend class lucent::Scope;
|
|
117
|
+
|
|
118
|
+
OperationToken token_;
|
|
119
|
+
std::weak_ptr<Scope> scope_;
|
|
120
|
+
std::function<void()> cleanup_;
|
|
121
|
+
std::atomic<uint64_t> late_{0};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
} // namespace detail
|
|
125
|
+
|
|
126
|
+
/// States only move forward: active, disposing, disposed.
|
|
127
|
+
///
|
|
128
|
+
/// Every method is safe from any thread. A scope may have an owner, the
|
|
129
|
+
/// execution context its work belongs to: then dispose() from any other
|
|
130
|
+
/// thread, and the last reference dropped there, post the disposal to the
|
|
131
|
+
/// owner and return at once. Otherwise, or once the owner takes no more
|
|
132
|
+
/// work, cleanups run on the thread that disposes. A dispose() that finds
|
|
133
|
+
/// the scope already disposing (reentrant, or on another thread) returns at
|
|
134
|
+
/// once without waiting for it.
|
|
135
|
+
class Scope : public std::enable_shared_from_this<Scope> {
|
|
136
|
+
public:
|
|
137
|
+
enum class State : uint8_t { Active, Disposing, Disposed };
|
|
138
|
+
using CleanupId = uint64_t;
|
|
139
|
+
|
|
140
|
+
/// Where a scope's disposal runs: an execution context (execution.h).
|
|
141
|
+
class Owner {
|
|
142
|
+
public:
|
|
143
|
+
virtual bool isCurrent() const = 0;
|
|
144
|
+
|
|
145
|
+
/// Runs `job` there later; false if it takes no more work.
|
|
146
|
+
virtual bool post(std::function<void()> job) = 0;
|
|
147
|
+
|
|
148
|
+
protected:
|
|
149
|
+
~Owner() = default;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/// A child has its parent's owner and belongs to its parent's runtime:
|
|
153
|
+
/// under a parent with a runtime, another `runtime` (0 included) throws
|
|
154
|
+
/// std::invalid_argument. A child of an inactive parent is created
|
|
155
|
+
/// disposed.
|
|
156
|
+
static std::shared_ptr<Scope> create(RuntimeId runtime, const std::shared_ptr<Scope>& parent = nullptr);
|
|
157
|
+
|
|
158
|
+
/// A scope without a parent, owned by `owner`.
|
|
159
|
+
static std::shared_ptr<Scope> createRoot(RuntimeId runtime, std::weak_ptr<Owner> owner);
|
|
160
|
+
|
|
161
|
+
/// The last reference to an active scope disposes it; errors go to
|
|
162
|
+
/// reportUncaught.
|
|
163
|
+
~Scope();
|
|
164
|
+
|
|
165
|
+
Scope(const Scope&) = delete;
|
|
166
|
+
Scope& operator=(const Scope&) = delete;
|
|
167
|
+
|
|
168
|
+
ScopeId id() const { return id_; }
|
|
169
|
+
|
|
170
|
+
RuntimeId runtime() const { return runtime_; }
|
|
171
|
+
|
|
172
|
+
Generation generation() const { return generation_.load(std::memory_order_acquire); }
|
|
173
|
+
|
|
174
|
+
State state() const { return state_.load(std::memory_order_acquire); }
|
|
175
|
+
|
|
176
|
+
/// Registers a cleanup. If the scope is not active, runs it now, on the
|
|
177
|
+
/// calling thread (its error reaches the caller), and returns 0.
|
|
178
|
+
CleanupId onDispose(std::function<void()> cleanup);
|
|
179
|
+
|
|
180
|
+
/// Removes a cleanup that has not run; false if it ran, was removed, or
|
|
181
|
+
/// disposal has begun.
|
|
182
|
+
bool remove(CleanupId id);
|
|
183
|
+
|
|
184
|
+
/// Idempotent. Changes the generation (invalidating tokens) and rejects
|
|
185
|
+
/// new registrations, then disposes the children (most recently created
|
|
186
|
+
/// first), cancels the pending operations (most recent first) and runs
|
|
187
|
+
/// their cleanups, then runs this scope's cleanups in reverse order.
|
|
188
|
+
/// Everything runs even if something throws; the errors come back
|
|
189
|
+
/// aggregated like `using`: the last one, with the earlier ones as
|
|
190
|
+
/// SuppressedError. Posted to the owner, the scope stays active until the
|
|
191
|
+
/// disposal runs there, and its errors go to reportUncaught.
|
|
192
|
+
std::exception_ptr dispose();
|
|
193
|
+
|
|
194
|
+
bool validates(const OperationToken& token) const;
|
|
195
|
+
|
|
196
|
+
/// What the scope holds now: live children, pending operations and
|
|
197
|
+
/// cleanups not yet run (for debug ownership reports).
|
|
198
|
+
size_t registrations() const;
|
|
199
|
+
|
|
200
|
+
private:
|
|
201
|
+
friend class detail::OperationBase;
|
|
202
|
+
|
|
203
|
+
Scope(RuntimeId runtime, const std::shared_ptr<Scope>& parent, std::weak_ptr<Owner> owner);
|
|
204
|
+
|
|
205
|
+
/// What disposal runs once the scope has stopped taking registrations.
|
|
206
|
+
struct Teardown {
|
|
207
|
+
std::map<ScopeId, std::shared_ptr<Scope>> children;
|
|
208
|
+
std::map<OperationId, std::shared_ptr<detail::OperationBase>> operations;
|
|
209
|
+
std::map<CleanupId, std::function<void()>> cleanups;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/// Moves the scope to disposing and takes its teardown; empty if it was
|
|
213
|
+
/// not active.
|
|
214
|
+
std::optional<Teardown> begin();
|
|
215
|
+
|
|
216
|
+
static std::exception_ptr finish(Teardown& teardown);
|
|
217
|
+
|
|
218
|
+
/// Disposes on the calling thread.
|
|
219
|
+
std::exception_ptr disposeHere();
|
|
220
|
+
|
|
221
|
+
bool adopt(const std::shared_ptr<detail::OperationBase>& op, bool pending);
|
|
222
|
+
void forget(OperationId id);
|
|
223
|
+
bool adoptChild(const std::shared_ptr<Scope>& child);
|
|
224
|
+
void forgetChild(ScopeId id);
|
|
225
|
+
|
|
226
|
+
const RuntimeId runtime_;
|
|
227
|
+
const ScopeId id_;
|
|
228
|
+
const std::weak_ptr<Scope> parent_;
|
|
229
|
+
const std::weak_ptr<Owner> owner_;
|
|
230
|
+
|
|
231
|
+
std::atomic<Generation> generation_{1};
|
|
232
|
+
std::atomic<State> state_{State::Active};
|
|
233
|
+
|
|
234
|
+
/// Guards the containers below. Never held while user code runs or while
|
|
235
|
+
/// a user callback is destroyed.
|
|
236
|
+
mutable std::mutex m_;
|
|
237
|
+
CleanupId nextCleanup_ = 1;
|
|
238
|
+
// Keyed by id, so iteration order is registration or creation order.
|
|
239
|
+
std::map<CleanupId, std::function<void()>> cleanups_;
|
|
240
|
+
std::map<ScopeId, std::shared_ptr<Scope>> children_;
|
|
241
|
+
std::map<OperationId, std::shared_ptr<detail::OperationBase>> operations_;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
/// One pending request with exactly one outcome: a value (none for void),
|
|
245
|
+
/// an error, or a cancellation. Settle it from any thread; only the first
|
|
246
|
+
/// call wins. Errors thrown by its listeners and cleanup are reported with
|
|
247
|
+
/// reportUncaught, except during Scope::dispose, which returns them.
|
|
248
|
+
template <class T>
|
|
249
|
+
class Operation final : public detail::OperationBase {
|
|
250
|
+
public:
|
|
251
|
+
using Value = std::conditional_t<std::is_void_v<T>, Undefined, T>;
|
|
252
|
+
|
|
253
|
+
struct Outcome {
|
|
254
|
+
OperationState state = OperationState::Pending;
|
|
255
|
+
/// Set when Succeeded.
|
|
256
|
+
std::optional<Value> value;
|
|
257
|
+
/// Set when Failed or Cancelled.
|
|
258
|
+
Error error;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
using Listener = std::function<void(const Outcome&)>;
|
|
262
|
+
|
|
263
|
+
/// Begins the native work and returns the cleanup that ends it (or an
|
|
264
|
+
/// empty function). It may settle the operation before returning.
|
|
265
|
+
using Registration = std::function<std::function<void()>(const std::shared_ptr<Operation>&)>;
|
|
266
|
+
|
|
267
|
+
/// Pending under `scope`; Cancelled if the scope is not active.
|
|
268
|
+
static std::shared_ptr<Operation> start(const std::shared_ptr<Scope>& scope) { return create(scope, nullptr); }
|
|
269
|
+
|
|
270
|
+
/// Starts an operation and runs `registration`. Already aborted (an
|
|
271
|
+
/// aborted signal gives `abortedBy`) or under an inactive scope, the
|
|
272
|
+
/// operation starts Cancelled and registration is not called. If
|
|
273
|
+
/// registration throws, the operation fails with that error.
|
|
274
|
+
static std::shared_ptr<Operation> start(const std::shared_ptr<Scope>& scope, const Registration& registration,
|
|
275
|
+
Opt<Error> abortedBy = {}) {
|
|
276
|
+
if (!registration) throw std::invalid_argument("An operation's registration must be a function");
|
|
277
|
+
|
|
278
|
+
auto op = create(scope, abortedBy.has() ? abortedBy.get() : nullptr);
|
|
279
|
+
|
|
280
|
+
if (op->state() != OperationState::Pending) return op;
|
|
281
|
+
|
|
282
|
+
std::function<void()> cleanup;
|
|
283
|
+
try {
|
|
284
|
+
cleanup = registration(op);
|
|
285
|
+
} catch (...) {
|
|
286
|
+
op->fail(currentError(std::current_exception()));
|
|
287
|
+
return op;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (cleanup) op->setCleanup(std::move(cleanup));
|
|
291
|
+
|
|
292
|
+
return op;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/// True only for the call that settles the operation.
|
|
296
|
+
bool succeed(Value value)
|
|
297
|
+
requires(!std::is_void_v<T>)
|
|
298
|
+
{
|
|
299
|
+
return complete(OperationState::Succeeded, std::optional<Value>(std::move(value)), nullptr);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
bool succeed()
|
|
303
|
+
requires std::is_void_v<T>
|
|
304
|
+
{
|
|
305
|
+
return complete(OperationState::Succeeded, std::optional<Value>(Value{}), nullptr);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
bool fail(Error error) { return complete(OperationState::Failed, std::nullopt, std::move(error)); }
|
|
309
|
+
|
|
310
|
+
bool cancel(Error reason) { return complete(OperationState::Cancelled, std::nullopt, std::move(reason)); }
|
|
311
|
+
|
|
312
|
+
/// Runs `listener` once with the outcome: at settlement, on the settling
|
|
313
|
+
/// thread, or now if already settled. Listeners run in the order added.
|
|
314
|
+
void onSettled(Listener listener) {
|
|
315
|
+
if (!listener) throw std::invalid_argument("An operation listener must be a function");
|
|
316
|
+
|
|
317
|
+
{
|
|
318
|
+
std::lock_guard<std::mutex> g(m_);
|
|
319
|
+
|
|
320
|
+
if (state_.load(std::memory_order_relaxed) == OperationState::Pending) {
|
|
321
|
+
listeners_.push_back(std::move(listener));
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
listener(outcome_);
|
|
328
|
+
} catch (...) {
|
|
329
|
+
report(std::current_exception());
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/// Receives the value of a success that lost (to a cancellation, say), so
|
|
334
|
+
/// its resources are released rather than delivered. Without a hook it is
|
|
335
|
+
/// destroyed. Runs on the losing caller's thread.
|
|
336
|
+
void onLateOutcome(std::function<void(Value&&)> release)
|
|
337
|
+
requires(!std::is_void_v<T>)
|
|
338
|
+
{
|
|
339
|
+
std::lock_guard<std::mutex> g(m_);
|
|
340
|
+
|
|
341
|
+
std::swap(lateRelease_, release);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private:
|
|
345
|
+
Operation() = default;
|
|
346
|
+
|
|
347
|
+
static std::shared_ptr<Operation> create(const std::shared_ptr<Scope>& scope, Error cancelledBy) {
|
|
348
|
+
if (!scope) throw std::invalid_argument("An operation needs a scope");
|
|
349
|
+
|
|
350
|
+
std::shared_ptr<Operation> op(new Operation());
|
|
351
|
+
|
|
352
|
+
if (!op->attach(scope, cancelledBy == nullptr)) {
|
|
353
|
+
std::optional<Value> none;
|
|
354
|
+
Error reason = cancelledBy ? std::move(cancelledBy) : scopeDisposedError();
|
|
355
|
+
std::vector<Listener> nobody;
|
|
356
|
+
|
|
357
|
+
op->transition(OperationState::Cancelled, none, reason, nobody);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return op;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/// The winning transition records the outcome and takes the listeners;
|
|
364
|
+
/// false if already settled.
|
|
365
|
+
bool transition(OperationState to, std::optional<Value>& value, Error& error, std::vector<Listener>& listeners) {
|
|
366
|
+
if (state_.load(std::memory_order_acquire) != OperationState::Pending) return false;
|
|
367
|
+
|
|
368
|
+
std::lock_guard<std::mutex> g(m_);
|
|
369
|
+
|
|
370
|
+
OperationState expected = OperationState::Pending;
|
|
371
|
+
if (!state_.compare_exchange_strong(expected, to, std::memory_order_acq_rel)) return false;
|
|
372
|
+
|
|
373
|
+
outcome_.state = to;
|
|
374
|
+
outcome_.value = std::move(value);
|
|
375
|
+
outcome_.error = std::move(error);
|
|
376
|
+
listeners.swap(listeners_);
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/// A settlement by a caller. A loser is counted and its value released.
|
|
381
|
+
bool complete(OperationState to, std::optional<Value> value, Error error) {
|
|
382
|
+
// Leaving the scope may drop its reference, the last but this one.
|
|
383
|
+
auto self = shared_from_this();
|
|
384
|
+
std::vector<Listener> listeners;
|
|
385
|
+
|
|
386
|
+
if (!transition(to, value, error, listeners)) {
|
|
387
|
+
countLate();
|
|
388
|
+
releaseLate(std::move(value));
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
detach();
|
|
393
|
+
|
|
394
|
+
for (auto& listener : listeners) {
|
|
395
|
+
try {
|
|
396
|
+
listener(outcome_);
|
|
397
|
+
} catch (...) {
|
|
398
|
+
report(std::current_exception());
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
report(runCleanup());
|
|
403
|
+
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
std::exception_ptr cancelOwned(const Error& reason) override {
|
|
408
|
+
std::optional<Value> none;
|
|
409
|
+
Error error = reason;
|
|
410
|
+
std::vector<Listener> listeners;
|
|
411
|
+
|
|
412
|
+
if (!transition(OperationState::Cancelled, none, error, listeners)) return nullptr;
|
|
413
|
+
|
|
414
|
+
std::exception_ptr errors;
|
|
415
|
+
for (auto& listener : listeners) {
|
|
416
|
+
try {
|
|
417
|
+
listener(outcome_);
|
|
418
|
+
} catch (...) {
|
|
419
|
+
errors = detail::aggregateErrors(errors, std::current_exception());
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return errors;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
void releaseLate(std::optional<Value>&& value) {
|
|
427
|
+
if constexpr (!std::is_void_v<T>) {
|
|
428
|
+
if (!value) return;
|
|
429
|
+
|
|
430
|
+
std::function<void(Value&&)> release;
|
|
431
|
+
{
|
|
432
|
+
std::lock_guard<std::mutex> g(m_);
|
|
433
|
+
release = lateRelease_;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (!release) return;
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
release(std::move(*value));
|
|
440
|
+
} catch (...) {
|
|
441
|
+
report(std::current_exception());
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/// Written once, by the winning transition under m_; read-only after.
|
|
447
|
+
Outcome outcome_;
|
|
448
|
+
std::vector<Listener> listeners_;
|
|
449
|
+
std::function<void(Value&&)> lateRelease_;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
} // namespace lucent
|