@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,455 @@
1
+ // Lucent runtime — isolated compute.
2
+ //
3
+ // `compute(entry, input)` runs a compiled function on a bounded pool of
4
+ // worker threads and returns a promise of its result to the calling
5
+ // context. The input is copied at submission (transport.h), so the task
6
+ // shares nothing mutable with its caller; the result is moved back, and the
7
+ // promise settles on the context that submitted it, whatever thread the task
8
+ // ran on. Workers never take the Lucent lock or wait for the UI loop, so a
9
+ // task neither waits behind module code nor holds it up.
10
+ //
11
+ // The pool is bounded twice: a fixed number of workers (one fewer than the
12
+ // cores, at least one), and a queue of tasks waiting for one (1024). A
13
+ // submission that finds the queue full is rejected at once with a
14
+ // QuotaExceededError: the caller never blocks.
15
+ //
16
+ // A task ends at the first of:
17
+ // - its result or error, posted to its owner (the promise's context) and
18
+ // delivered unless its scope was disposed by then;
19
+ // - cancellation: its signal aborting, its scope being disposed, or the pool
20
+ // shutting down. The promise rejects at once. A task still queued leaves
21
+ // the queue; a running one is asked to stop, and stops at its next
22
+ // safepoint (TaskContext::checkCancelled). Native code between safepoints
23
+ // is not interrupted: the task runs until it returns, and what it returns
24
+ // is released on the worker.
25
+ // Either way it settles exactly once (an Operation, scope.h), and whatever
26
+ // loses (a result after cancellation, a result for a disposed scope) is
27
+ // released, never delivered: on the worker, or on the owner if it had
28
+ // already been posted there. What must be released on a particular thread
29
+ // routes its own release (Resource, NativeRef).
30
+ #pragma once
31
+
32
+ #include <atomic>
33
+ #include <chrono>
34
+ #include <cstdint>
35
+ #include <deque>
36
+ #include <functional>
37
+ #include <memory>
38
+ #include <mutex>
39
+ #include <optional>
40
+ #include <utility>
41
+ #include <vector>
42
+
43
+ #include "abort.h"
44
+ #include "async.h"
45
+ #include "execution.h"
46
+ #include "jserror.h"
47
+ #include "scope.h"
48
+ #include "trace.h"
49
+ #include "transport.h"
50
+
51
+ namespace lucent {
52
+
53
+ class ComputePool;
54
+
55
+ namespace detail {
56
+ class ComputeTask;
57
+ }
58
+
59
+ /// What a running task sees of itself. Only its worker uses it.
60
+ class TaskContext {
61
+ public:
62
+ TaskContext(const TaskContext&) = delete;
63
+ TaskContext& operator=(const TaskContext&) = delete;
64
+
65
+ /// Whether the task's owner no longer wants its result.
66
+ bool cancelled() const noexcept { return cancelled_.load(std::memory_order_relaxed); }
67
+
68
+ /// A safepoint: throws an AbortError if the task was cancelled. One
69
+ /// relaxed load when it was not, cheap enough for a loop's back edge.
70
+ void checkCancelled() const {
71
+ if (cancelled()) [[unlikely]]
72
+ throwCancelled();
73
+ }
74
+
75
+ /// The task running on this thread; null elsewhere.
76
+ static TaskContext* current() noexcept;
77
+
78
+ /// The task's own scope, under its worker's root: made on first use, and
79
+ /// disposed on the worker when the task returns, before its result
80
+ /// leaves.
81
+ const std::shared_ptr<Scope>& scope();
82
+
83
+ private:
84
+ friend class ComputePool;
85
+ friend class detail::ComputeTask;
86
+
87
+ TaskContext() = default;
88
+
89
+ [[noreturn]] static void throwCancelled();
90
+
91
+ void cancel() { cancelled_.store(true, std::memory_order_relaxed); }
92
+
93
+ /// Disposes the scope, if it was made.
94
+ void end();
95
+
96
+ std::atomic<bool> cancelled_{false};
97
+ std::shared_ptr<Scope> scope_;
98
+ };
99
+
100
+ /// A compiled function a task runs: what the compiler emits for each
101
+ /// function passed to compute(). `run` takes the task's own copy of the
102
+ /// input.
103
+ template <class In, class Out>
104
+ struct TaskEntry {
105
+ /// For timings and reports.
106
+ const char* name;
107
+ Out (*run)(In&&, TaskContext&);
108
+ };
109
+
110
+ /// How long one task spent where, when the pool reports timings.
111
+ struct TaskTiming {
112
+ const char* entry = "";
113
+ /// Succeeded or Failed once delivered; Cancelled if it never was.
114
+ OperationState outcome = OperationState::Cancelled;
115
+ bool started = false;
116
+ /// Taking the pool's lock at submission.
117
+ std::chrono::nanoseconds admitted{0};
118
+ /// From submission until a worker started it.
119
+ std::chrono::nanoseconds waited{0};
120
+ /// Running on the worker.
121
+ std::chrono::nanoseconds ran{0};
122
+ /// From its end until its outcome settled on the owner.
123
+ std::chrono::nanoseconds delivered{0};
124
+ };
125
+
126
+ /// Receives each task's timing once it ends, on the thread it ends on.
127
+ using TimingSink = std::function<void(const TaskTiming&)>;
128
+
129
+ /// A pool's counters since it was made.
130
+ struct ComputeStats {
131
+ size_t workers = 0;
132
+ size_t capacity = 0;
133
+ /// Now: waiting, and handed to a worker.
134
+ size_t queued = 0;
135
+ size_t running = 0;
136
+ /// High-water marks.
137
+ size_t peakQueued = 0;
138
+ size_t peakRunning = 0;
139
+
140
+ uint64_t submitted = 0;
141
+ /// Refused because the queue was full.
142
+ uint64_t rejected = 0;
143
+ /// Queued because every worker was busy.
144
+ uint64_t saturated = 0;
145
+ /// Handed to a worker.
146
+ uint64_t started = 0;
147
+ uint64_t finished = 0;
148
+ /// Cancelled while queued.
149
+ uint64_t cancelledQueued = 0;
150
+ /// Finished, but the outcome was released instead of posted: the task
151
+ /// was cancelled, or its owner had gone.
152
+ uint64_t abandoned = 0;
153
+ };
154
+
155
+ /// The QuotaExceededError of a submission that found the queue full.
156
+ Error computeQueueFullError(size_t capacity);
157
+
158
+ /// The InvalidStateError of a submission to a pool that has shut down.
159
+ Error computePoolClosedError();
160
+
161
+ /// The AbortError of a task that was queued or running when its pool shut
162
+ /// down.
163
+ Error computePoolShutdownError();
164
+
165
+ namespace detail {
166
+
167
+ /// One submission, as the pool sees it whatever its types.
168
+ class ComputeTask : public std::enable_shared_from_this<ComputeTask> {
169
+ public:
170
+ using Time = std::chrono::steady_clock::time_point;
171
+
172
+ ComputeTask(const ComputeTask&) = delete;
173
+ ComputeTask& operator=(const ComputeTask&) = delete;
174
+
175
+ /// Reports the timing, if the task was timed.
176
+ virtual ~ComputeTask();
177
+
178
+ /// On a worker: runs the entry, then posts the outcome to the owner, or
179
+ /// releases it here if the task was cancelled (and returns false).
180
+ virtual bool run() = 0;
181
+
182
+ /// Settles the task as cancelled by `reason`, if it has not settled.
183
+ virtual void abandon(const Error& reason) = 0;
184
+
185
+ protected:
186
+ explicit ComputeTask(const char* name) : name_(name) {}
187
+
188
+ static Time now() { return std::chrono::steady_clock::now(); }
189
+
190
+ bool timed() const { return sink_ != nullptr; }
191
+
192
+ /// Records a delivered outcome (on the owner).
193
+ void delivered(OperationState outcome);
194
+
195
+ /// Ends the run: disposes the task's scope, notes the time.
196
+ void finishRun();
197
+
198
+ /// Tracing: ends the span of the phase `ended` and begins `next`'s (none:
199
+ /// the task is done). Nothing unless the task was traced.
200
+ void tracePhase(const char* ended, const char* next) noexcept;
201
+
202
+ TaskContext context_;
203
+
204
+ private:
205
+ friend class lucent::ComputePool;
206
+
207
+ const char* const name_;
208
+
209
+ // Timing: written by the pool and the task in the order the task moves,
210
+ // each handoff a lock or a post, so read without further locking.
211
+ std::shared_ptr<const TimingSink> sink_;
212
+ std::chrono::nanoseconds admitted_{0};
213
+ Time submitted_{};
214
+ Time started_{};
215
+ Time finished_{};
216
+ Time settled_{};
217
+ std::atomic<OperationState> outcome_{OperationState::Cancelled};
218
+
219
+ /// Tracing, when on at submission: the task's id and its phase's span,
220
+ /// handed along with the task.
221
+ uint64_t traceId_ = 0;
222
+ trace::Mark phase_;
223
+
224
+ /// Guarded by the pool's lock.
225
+ enum class Place : uint8_t { None, Queued, Assigned } place_ = Place::None;
226
+ };
227
+
228
+ template <class In, class Out>
229
+ class TypedTask final : public ComputeTask {
230
+ public:
231
+ using Value = Stored<Out>;
232
+
233
+ TypedTask(const TaskEntry<In, Out>& entry, In&& input, ContextRef owner, const std::shared_ptr<Scope>& scope)
234
+ : ComputeTask(entry.name), entry_(entry), input_(std::move(input)), owner_(std::move(owner)), scope_(scope) {}
235
+
236
+ void bind(const std::shared_ptr<Operation<Out>>& operation) { operation_ = operation; }
237
+
238
+ bool run() override {
239
+ std::optional<Value> value;
240
+ Error error;
241
+
242
+ try {
243
+ if constexpr (std::is_void_v<Out>) {
244
+ entry_.run(std::move(*input_), context_);
245
+ value.emplace(undefined);
246
+ } else {
247
+ value.emplace(entry_.run(std::move(*input_), context_));
248
+ }
249
+ } catch (...) {
250
+ error = currentError(std::current_exception());
251
+ }
252
+
253
+ input_.reset();
254
+ finishRun();
255
+
256
+ // Nobody wants it: released here, on the worker.
257
+ std::shared_ptr<Scope> scope = context_.cancelled() ? nullptr : scope_.lock();
258
+ if (!scope) {
259
+ tracePhase("compute.run", nullptr);
260
+ return false;
261
+ }
262
+
263
+ auto self = std::static_pointer_cast<TypedTask>(shared_from_this());
264
+
265
+ // Before the post: once posted, the owner may settle at once.
266
+ tracePhase("compute.run", "compute.deliver");
267
+
268
+ // Dropped with what it carries if the scope is disposed first: then
269
+ // destroyed on the owner, or here if the owner takes no more work.
270
+ bool posted = ExecutionContext::of(owner_).post(
271
+ [self, value = std::move(value), error = std::move(error)]() mutable { self->settle(std::move(value), std::move(error)); },
272
+ scope);
273
+
274
+ if (!posted) tracePhase("compute.deliver", nullptr);
275
+
276
+ return posted;
277
+ }
278
+
279
+ void abandon(const Error& reason) override {
280
+ if (auto operation = operation_.lock()) operation->cancel(reason);
281
+ }
282
+
283
+ private:
284
+ /// On the owner. Loses to a cancellation that came first: the value is
285
+ /// then released here.
286
+ void settle(std::optional<Value>&& value, Error&& error) {
287
+ auto operation = operation_.lock();
288
+ if (!operation) return;
289
+
290
+ if (error) {
291
+ operation->fail(std::move(error));
292
+ } else if constexpr (std::is_void_v<Out>) {
293
+ operation->succeed();
294
+ } else {
295
+ operation->succeed(std::move(*value));
296
+ }
297
+
298
+ delivered(operation->state());
299
+ tracePhase("compute.deliver", nullptr);
300
+ }
301
+
302
+ const TaskEntry<In, Out> entry_;
303
+ std::optional<In> input_;
304
+ const ContextRef owner_;
305
+ const std::weak_ptr<Scope> scope_;
306
+ std::weak_ptr<Operation<Out>> operation_;
307
+ };
308
+
309
+ } // namespace detail
310
+
311
+ struct ComputePoolOptions {
312
+ /// 0: one fewer than the cores, at least one.
313
+ size_t workers = 0;
314
+ /// Tasks that may wait for a worker; 0: ComputePool::kDefaultCapacity.
315
+ size_t capacity = 0;
316
+ };
317
+
318
+ /// A bounded set of worker threads, each an isolated execution context, and
319
+ /// the queue of tasks waiting for one.
320
+ class ComputePool : public std::enable_shared_from_this<ComputePool> {
321
+ public:
322
+ static constexpr size_t kDefaultCapacity = 1024;
323
+
324
+ using Options = ComputePoolOptions;
325
+
326
+ static std::shared_ptr<ComputePool> create(Options options = {});
327
+
328
+ /// The process's pool, made on first use; it lives as long as the process.
329
+ static const std::shared_ptr<ComputePool>& shared();
330
+
331
+ /// Shuts down.
332
+ ~ComputePool();
333
+
334
+ ComputePool(const ComputePool&) = delete;
335
+ ComputePool& operator=(const ComputePool&) = delete;
336
+
337
+ size_t workers() const { return workers_.size(); }
338
+
339
+ size_t capacity() const { return capacity_; }
340
+
341
+ /// Any thread, idempotent, returns at once: refuses new tasks
342
+ /// (InvalidStateError), cancels those queued or running
343
+ /// (computePoolShutdownError), and stops the workers once their running
344
+ /// tasks return.
345
+ void shutdown();
346
+
347
+ /// For tests: blocks until every worker has stopped, or the timeout
348
+ /// elapses. Never call it from a worker.
349
+ bool waitStopped(double timeoutMs);
350
+
351
+ ComputeStats stats() const;
352
+
353
+ /// Reports each task submitted from now on, when it ends; null stops.
354
+ /// Off by default: untimed tasks read no clock.
355
+ void setTimingSink(TimingSink sink);
356
+
357
+ /// Queues `task` (or hands it to an idle worker), and follows `signal`.
358
+ /// Throws the submission's failure: a full queue, or a closed pool.
359
+ /// Returns the operation's cleanup: whatever settled the task, the pool
360
+ /// stops it and forgets it.
361
+ std::function<void()> admit(const std::shared_ptr<detail::ComputeTask>& task, const AbortSignal& signal);
362
+
363
+ private:
364
+ explicit ComputePool(Options options);
365
+
366
+ using TaskRef = std::shared_ptr<detail::ComputeTask>;
367
+
368
+ /// Posts `task` as a turn of worker `index`.
369
+ void dispatch(size_t index, TaskRef task);
370
+
371
+ /// On worker `index`: runs `task`, then takes the next one, if any.
372
+ void runOn(size_t index, TaskRef task);
373
+
374
+ /// Takes `task` out of the queue if it is still there.
375
+ void withdraw(const TaskRef& task);
376
+
377
+ std::vector<std::shared_ptr<IsolatedContext>> workers_;
378
+ const size_t capacity_;
379
+
380
+ std::atomic<bool> timing_{false};
381
+
382
+ mutable std::mutex m_;
383
+ bool closed_ = false;
384
+ std::vector<size_t> idle_;
385
+ std::deque<TaskRef> queue_;
386
+ /// Handed to a worker: running, or posted to it.
387
+ std::vector<TaskRef> assigned_;
388
+ std::shared_ptr<const TimingSink> sink_;
389
+ ComputeStats stats_;
390
+ };
391
+
392
+ struct ComputeOptions {
393
+ /// Aborting it cancels the task.
394
+ Opt<AbortSignal> signal;
395
+ /// What the task belongs to: disposing it cancels the task. Default: the
396
+ /// calling context's root.
397
+ std::shared_ptr<Scope> scope;
398
+ /// Default: ComputePool::shared().
399
+ std::shared_ptr<ComputePool> pool;
400
+ };
401
+
402
+ /// Submits `owned`, an input only the task holds (already copied, or moved
403
+ /// to it), and returns the promise of the result, settled on the calling
404
+ /// context. Failures reject the promise: a full queue, a closed pool, an
405
+ /// aborted signal or an inactive scope.
406
+ template <class In, class Out>
407
+ Promise<Out> submit(const TaskEntry<In, Out>& entry, In&& owned, ComputeOptions options = {}) {
408
+ std::shared_ptr<ComputePool> pool = options.pool ? std::move(options.pool) : ComputePool::shared();
409
+ ContextRef owner = ExecutionContext::currentRef();
410
+ std::shared_ptr<Scope> scope = options.scope ? std::move(options.scope) : ExecutionContext::of(owner).root();
411
+ AbortSignal signal = options.signal.has() ? options.signal.get() : nullptr;
412
+
413
+ Promise<Out> promise;
414
+ auto task = std::make_shared<detail::TypedTask<In, Out>>(entry, std::move(owned), owner, scope);
415
+
416
+ Opt<Error> abortedBy;
417
+ if (signal && signal->aborted.load()) abortedBy = signal->reason;
418
+
419
+ auto operation = Operation<Out>::start(
420
+ scope,
421
+ [&](const std::shared_ptr<Operation<Out>>& op) {
422
+ task->bind(op);
423
+ return pool->admit(task, signal);
424
+ },
425
+ abortedBy);
426
+
427
+ operation->onSettled([promise](const typename Operation<Out>::Outcome& outcome) {
428
+ if (outcome.state == OperationState::Succeeded) {
429
+ promise.resolve(*outcome.value);
430
+ } else {
431
+ promise.reject(outcome.error);
432
+ }
433
+ });
434
+
435
+ return promise;
436
+ }
437
+
438
+ /// `compute(entry, input)`: copies `input` (a snapshot: later changes by the
439
+ /// caller do not reach the task) and submits it. A copy refused
440
+ /// (DataCloneError) rejects the promise; nothing is submitted.
441
+ template <class In, class Out>
442
+ Promise<Out> compute(const TaskEntry<In, Out>& entry, const In& input, ComputeOptions options = {}) {
443
+ static_assert(Transportable<In>, "A compute task's input must cross to it: every part needs a lucent::Transport");
444
+
445
+ std::optional<In> owned;
446
+ try {
447
+ owned.emplace(transportCopy(input));
448
+ } catch (...) {
449
+ return Promise<Out>::rejected(currentError(std::current_exception()));
450
+ }
451
+
452
+ return submit(entry, std::move(*owned), std::move(options));
453
+ }
454
+
455
+ } // namespace lucent
@@ -0,0 +1,111 @@
1
+ // Lucent runtime — Number(x) and BigInt(x) of a value whose type is a union
2
+ // (bigint | number, possibly absent, …): each alternative converted as
3
+ // JavaScript converts it. The compiler converts a value of one known type
4
+ // inline; these take the value as it is held (Opt, std::variant).
5
+ #pragma once
6
+
7
+ #include <compare>
8
+ #include <type_traits>
9
+ #include <variant>
10
+
11
+ #include "bigint.h"
12
+ #include "core.h"
13
+ #include "number.h"
14
+ #include "jsstring.h"
15
+
16
+ namespace lucent {
17
+
18
+ // --- Number(x) -------------------------------------------------------------------------
19
+
20
+ inline double toNumber(double x) { return x; }
21
+ inline double toNumber(bool x) { return x ? 1.0 : 0.0; }
22
+ inline double toNumber(const String& x) { return stringToNumber(x); }
23
+ /// The nearest double, ties to even.
24
+ inline double toNumber(const BigInt& x) { return x.toDouble(); }
25
+ inline double toNumber(Undefined) { return kNaN; }
26
+ inline double toNumber(Null) { return 0.0; }
27
+
28
+ template <class T>
29
+ double toNumber(const Opt<T>& x);
30
+ template <class... Ts>
31
+ double toNumber(const std::variant<Ts...>& x);
32
+
33
+ template <class T>
34
+ double toNumber(const Opt<T>& x) {
35
+ if (x.isUndefined()) return kNaN;
36
+ if (x.isNull()) return 0.0;
37
+
38
+ return toNumber(x.get());
39
+ }
40
+
41
+ template <class... Ts>
42
+ double toNumber(const std::variant<Ts...>& x) {
43
+ return std::visit([](const auto& v) { return toNumber(v); }, x);
44
+ }
45
+
46
+ // --- BigInt(x) -------------------------------------------------------------------------
47
+
48
+ inline BigInt toBigInt(const BigInt& x) { return x; }
49
+ /// A RangeError unless `x` is an integer.
50
+ inline BigInt toBigInt(double x) { return BigInt::fromDouble(x); }
51
+ /// A SyntaxError unless `x` is an integer literal.
52
+ inline BigInt toBigInt(const String& x) { return BigInt::parse(x); }
53
+ inline BigInt toBigInt(bool x) { return BigInt::fromInt64(x ? 1 : 0); }
54
+
55
+ template <class... Ts>
56
+ BigInt toBigInt(const std::variant<Ts...>& x) {
57
+ return std::visit([](const auto& v) { return toBigInt(v); }, x);
58
+ }
59
+
60
+ // --- comparison ----------------------------------------------------------------------
61
+
62
+ /**
63
+ * The order of two numeric values, each a number, a bigint, or a union of
64
+ * them (`bigint | number`): exact across kinds, as JavaScript's relational
65
+ * operators compare; unordered when either is NaN.
66
+ */
67
+ template <class A, class B>
68
+ std::partial_ordering compareNumeric(const A& a, const B& b) {
69
+ if constexpr (IsVariant<A>::value)
70
+ return std::visit([&](const auto& x) { return compareNumeric(x, b); }, a);
71
+ else if constexpr (IsVariant<B>::value)
72
+ return std::visit([&](const auto& y) { return compareNumeric(a, y); }, b);
73
+ else
74
+ return a <=> b;
75
+ }
76
+
77
+ // --- unary operators on a number | bigint --------------------------------------------
78
+
79
+ /// `-x`, on the value's own kind.
80
+ template <class... Ts>
81
+ std::variant<Ts...> negateNumeric(const std::variant<Ts...>& x) {
82
+ return std::visit([](const auto& v) -> std::variant<Ts...> { return -v; }, x);
83
+ }
84
+
85
+ /// `~x`: of a number, ~ToInt32; of a bigint, its own.
86
+ template <class... Ts>
87
+ std::variant<Ts...> bitNotNumeric(const std::variant<Ts...>& x) {
88
+ return std::visit(
89
+ [](const auto& v) -> std::variant<Ts...> {
90
+ if constexpr (std::is_same_v<std::decay_t<decltype(v)>, double>)
91
+ return static_cast<double>(~toInt32(v));
92
+ else
93
+ return ~v;
94
+ },
95
+ x);
96
+ }
97
+
98
+ /// `x + 1` (`step` 1) or `x - 1` (-1), for `++` and `--`, on the value's own kind.
99
+ template <class... Ts>
100
+ std::variant<Ts...> stepNumeric(const std::variant<Ts...>& x, int step) {
101
+ return std::visit(
102
+ [step](const auto& v) -> std::variant<Ts...> {
103
+ if constexpr (std::is_same_v<std::decay_t<decltype(v)>, double>)
104
+ return v + step;
105
+ else
106
+ return v + BigInt::fromInt64(step);
107
+ },
108
+ x);
109
+ }
110
+
111
+ } // namespace lucent
@@ -28,9 +28,6 @@ using Ref = std::shared_ptr<T>;
28
28
  /// Base of every struct and class instance.
29
29
  struct Object : std::enable_shared_from_this<Object> {
30
30
  virtual ~Object() = default;
31
- /// Stable identity handed to JavaScript the first time the object crosses
32
- /// the boundary (0 = never crossed). Only touched on the JS thread.
33
- uint64_t jsIdentity = 0;
34
31
  };
35
32
 
36
33
  struct Undefined {
@@ -51,6 +48,10 @@ inline constexpr Null null{};
51
48
 
52
49
  /// `T | undefined | null`. Remembers which of the two absent values it holds
53
50
  /// so `x === null` and `x === undefined` behave as in JavaScript.
51
+ /// null and undefined: the values an Opt holds as its states, never as its value.
52
+ template <class T>
53
+ inline constexpr bool IsAbsent = std::is_same_v<T, Undefined> || std::is_same_v<T, Null>;
54
+
54
55
  template <class T>
55
56
  class Opt {
56
57
  public:
@@ -60,8 +61,13 @@ class Opt {
60
61
  Opt() = default;
61
62
  Opt(Undefined) {}
62
63
  Opt(Null) : state_(State::Null) {}
63
- Opt(const T& v) : value_(v), state_(State::Value) {}
64
- Opt(T&& v) : value_(std::move(v)), state_(State::Value) {}
64
+ // `null | undefined` is an Opt<Undefined>: absent either way, never a value.
65
+ Opt(const T& v)
66
+ requires(!IsAbsent<T>)
67
+ : value_(v), state_(State::Value) {}
68
+ Opt(T&& v)
69
+ requires(!IsAbsent<T>)
70
+ : value_(std::move(v)), state_(State::Value) {}
65
71
  template <class U,
66
72
  std::enable_if_t<!std::is_same_v<std::decay_t<U>, Opt> &&
67
73
  !std::is_same_v<std::decay_t<U>, T> &&
@@ -112,6 +118,11 @@ struct IsRef<std::shared_ptr<T>> : std::true_type {};
112
118
  template <class... Ts>
113
119
  using Union = std::variant<Ts...>;
114
120
 
121
+ template <class T>
122
+ struct IsVariant : std::false_type {};
123
+ template <class... Ts>
124
+ struct IsVariant<std::variant<Ts...>> : std::true_type {};
125
+
115
126
  /// Boxes a local that a closure captures and later mutates, so the closure and
116
127
  /// the enclosing function share one variable, as in JavaScript.
117
128
  template <class T>