@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,328 @@
|
|
|
1
|
+
// Lucent runtime — native buffers.
|
|
2
|
+
//
|
|
3
|
+
// A NativeBuffer is bytes that native code owns: allocated by the runtime,
|
|
4
|
+
// or adopted from native code with the release it requires and the
|
|
5
|
+
// executor that release must run on. Its handle is reference counted, but a
|
|
6
|
+
// reference keeps only the handle alive: access to the bytes is a borrow,
|
|
7
|
+
// granted for the length of one synchronous call. Read borrows share;
|
|
8
|
+
// a write borrow excludes every other. A borrow that conflicts is refused at
|
|
9
|
+
// once with an InvalidStateError, never waited for, whatever thread asks.
|
|
10
|
+
//
|
|
11
|
+
// The storage is released once: at close(), which a borrow refuses, or when
|
|
12
|
+
// the last reference goes, on the executor it requires. After that every
|
|
13
|
+
// alias refuses access. Copies are explicit (toBytes, fromBytes) and
|
|
14
|
+
// counted (stats), so a path that should not copy can prove it does not.
|
|
15
|
+
//
|
|
16
|
+
// A transfer moves the storage, uncopied, to a new handle and leaves every
|
|
17
|
+
// alias of the old one refusing reads, writes and transfers: the new handle
|
|
18
|
+
// is its only owner. This is how a buffer crosses to a compute task
|
|
19
|
+
// (transport.h): the move commits only once the task's whole input has been
|
|
20
|
+
// copied, and rolls back otherwise. Once committed, the storage follows the
|
|
21
|
+
// task's handle: a refused submission or a cancelled task releases it.
|
|
22
|
+
#pragma once
|
|
23
|
+
|
|
24
|
+
#include <algorithm>
|
|
25
|
+
#include <atomic>
|
|
26
|
+
#include <cmath>
|
|
27
|
+
#include <cstddef>
|
|
28
|
+
#include <cstdint>
|
|
29
|
+
#include <functional>
|
|
30
|
+
#include <memory>
|
|
31
|
+
#include <span>
|
|
32
|
+
#include <type_traits>
|
|
33
|
+
|
|
34
|
+
#include "bytes.h"
|
|
35
|
+
#include "core.h"
|
|
36
|
+
#include "execution.h"
|
|
37
|
+
#include "jserror.h"
|
|
38
|
+
#include "jsstring.h"
|
|
39
|
+
#include "transport.h"
|
|
40
|
+
|
|
41
|
+
namespace lucent {
|
|
42
|
+
|
|
43
|
+
class NativeBufferObject;
|
|
44
|
+
using NativeBuffer = Ref<NativeBufferObject>;
|
|
45
|
+
|
|
46
|
+
/// What every native buffer has done since the process started.
|
|
47
|
+
struct NativeBufferStats {
|
|
48
|
+
uint64_t allocated = 0;
|
|
49
|
+
uint64_t adopted = 0;
|
|
50
|
+
/// Storage released (closed, or its last reference gone).
|
|
51
|
+
uint64_t released = 0;
|
|
52
|
+
/// Storage moved to a new handle.
|
|
53
|
+
uint64_t transfers = 0;
|
|
54
|
+
/// Payload copies made (toBytes, fromBytes), and their bytes.
|
|
55
|
+
uint64_t copies = 0;
|
|
56
|
+
uint64_t bytesCopied = 0;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
class NativeBufferObject final : public Object {
|
|
60
|
+
public:
|
|
61
|
+
enum class State : uint8_t { Open, Transferring, Transferred, Closed };
|
|
62
|
+
|
|
63
|
+
/// Releases adopted storage.
|
|
64
|
+
using Destroy = std::function<void(uint8_t* data, size_t size)>;
|
|
65
|
+
|
|
66
|
+
/// `size` zeroed bytes, which any thread may free. `origin` names the
|
|
67
|
+
/// buffer in errors and reports.
|
|
68
|
+
static NativeBuffer allocate(double size, String origin = {});
|
|
69
|
+
|
|
70
|
+
/// Takes `size` bytes at `data`: `destroy` releases them, on `destroyOn`'s
|
|
71
|
+
/// thread if given (posted there from any other), else wherever the buffer
|
|
72
|
+
/// is released. `destroyOn` must outlive the buffer.
|
|
73
|
+
static NativeBuffer adopt(uint8_t* data, size_t size, Destroy destroy, ExecutionContext* destroyOn = nullptr,
|
|
74
|
+
String origin = {});
|
|
75
|
+
|
|
76
|
+
/// A new buffer holding a copy of `bytes`.
|
|
77
|
+
static NativeBuffer fromBytes(const Bytes& bytes, String origin = {});
|
|
78
|
+
|
|
79
|
+
/// Releases the storage if it still holds it; an error from the release
|
|
80
|
+
/// is reported.
|
|
81
|
+
~NativeBufferObject() override;
|
|
82
|
+
|
|
83
|
+
NativeBufferObject(const NativeBufferObject&) = delete;
|
|
84
|
+
NativeBufferObject& operator=(const NativeBufferObject&) = delete;
|
|
85
|
+
|
|
86
|
+
State state() const { return stateOf(word_.load(std::memory_order_acquire)); }
|
|
87
|
+
|
|
88
|
+
bool isOpen() const { return state() == State::Open; }
|
|
89
|
+
|
|
90
|
+
/// The bytes it holds: 0 once closed or transferred.
|
|
91
|
+
size_t size() const { return size_.load(std::memory_order_acquire); }
|
|
92
|
+
|
|
93
|
+
/// How many times its storage was transferred before reaching it.
|
|
94
|
+
uint32_t generation() const { return generation_; }
|
|
95
|
+
|
|
96
|
+
const String& origin() const { return origin_; }
|
|
97
|
+
|
|
98
|
+
/// Borrows now in progress.
|
|
99
|
+
size_t readers() const { return word_.load(std::memory_order_acquire) >> kReaderShift; }
|
|
100
|
+
bool writing() const { return (word_.load(std::memory_order_acquire) & kWriter) != 0; }
|
|
101
|
+
|
|
102
|
+
/// Calls `f(std::span<const uint8_t>)` while no one writes, and returns
|
|
103
|
+
/// what it returns. Throws InvalidStateError if the buffer is not open or
|
|
104
|
+
/// is being written.
|
|
105
|
+
template <class F>
|
|
106
|
+
decltype(auto) withRead(F&& f) {
|
|
107
|
+
Borrow borrow(*this, false);
|
|
108
|
+
return f(std::span<const uint8_t>(data_, size_.load(std::memory_order_relaxed)));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Calls `f(std::span<uint8_t>)` with the buffer to itself. Throws
|
|
112
|
+
/// InvalidStateError if it is not open or is borrowed.
|
|
113
|
+
template <class F>
|
|
114
|
+
decltype(auto) withWrite(F&& f) {
|
|
115
|
+
Borrow borrow(*this, true);
|
|
116
|
+
return f(std::span<uint8_t>(data_, size_.load(std::memory_order_relaxed)));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// A copy of the bytes (a Uint8Array snapshot), under a read borrow.
|
|
120
|
+
Bytes toBytes();
|
|
121
|
+
|
|
122
|
+
/// Releases the storage and returns true if it was open; false, at once,
|
|
123
|
+
/// if it was already closed or transferred. Throws InvalidStateError
|
|
124
|
+
/// while borrowed. Run here, the release's error reaches the caller;
|
|
125
|
+
/// posted to its executor, it is reported.
|
|
126
|
+
bool close();
|
|
127
|
+
|
|
128
|
+
/// Moves the storage to a new handle, one generation on, and returns it:
|
|
129
|
+
/// this one, and every alias of it, refuses access from now on. Throws
|
|
130
|
+
/// InvalidStateError if it is not open or is borrowed.
|
|
131
|
+
NativeBuffer transfer();
|
|
132
|
+
|
|
133
|
+
static NativeBufferStats stats();
|
|
134
|
+
|
|
135
|
+
/// Counted copies between borrowed bytes and a Uint8Array, for callers
|
|
136
|
+
/// that cannot be lent the storage itself (JavaScript): out of a read
|
|
137
|
+
/// borrow's bytes, and back into a write borrow's (what fits).
|
|
138
|
+
static Bytes copyOut(std::span<const uint8_t> bytes);
|
|
139
|
+
static void copyIn(std::span<uint8_t> to, const Bytes& from);
|
|
140
|
+
|
|
141
|
+
private:
|
|
142
|
+
friend struct Transport<NativeBuffer>;
|
|
143
|
+
|
|
144
|
+
// The word: state in the low two bits, then the write flag, then the
|
|
145
|
+
// number of readers. 0 is open with no borrow.
|
|
146
|
+
static constexpr uint32_t kStateMask = 3;
|
|
147
|
+
static constexpr uint32_t kWriter = 4;
|
|
148
|
+
static constexpr int kReaderShift = 3;
|
|
149
|
+
static constexpr uint32_t kReader = 1u << kReaderShift;
|
|
150
|
+
|
|
151
|
+
static State stateOf(uint32_t word) { return static_cast<State>(word & kStateMask); }
|
|
152
|
+
|
|
153
|
+
/// A borrow for one call; keeps the buffer alive while it lasts.
|
|
154
|
+
class Borrow {
|
|
155
|
+
public:
|
|
156
|
+
Borrow(NativeBufferObject& buffer, bool write);
|
|
157
|
+
~Borrow();
|
|
158
|
+
|
|
159
|
+
Borrow(const Borrow&) = delete;
|
|
160
|
+
Borrow& operator=(const Borrow&) = delete;
|
|
161
|
+
|
|
162
|
+
private:
|
|
163
|
+
std::shared_ptr<Object> keep_;
|
|
164
|
+
NativeBufferObject& buffer_;
|
|
165
|
+
const bool write_;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
NativeBufferObject(uint8_t* data, size_t size, Destroy destroy, ExecutionContext* destroyOn, String origin,
|
|
169
|
+
uint32_t generation);
|
|
170
|
+
|
|
171
|
+
/// Throws the InvalidStateError for a use the word refuses.
|
|
172
|
+
[[noreturn]] void refuse(uint32_t word) const;
|
|
173
|
+
|
|
174
|
+
/// Moves from open with no borrow to `to`; throws if it cannot.
|
|
175
|
+
void seize(State to);
|
|
176
|
+
|
|
177
|
+
/// A transfer in two steps, so a failed copy can undo it: the storage
|
|
178
|
+
/// moves to the returned handle at once, while this one refuses access;
|
|
179
|
+
/// commit leaves it transferred, abort takes the storage back.
|
|
180
|
+
NativeBuffer beginTransfer();
|
|
181
|
+
void commitTransfer();
|
|
182
|
+
void abortTransfer(const NativeBuffer& successor);
|
|
183
|
+
|
|
184
|
+
/// Releases storage taken from a buffer, on its executor.
|
|
185
|
+
static void release(uint8_t* data, size_t size, Destroy destroy, ExecutionContext* on);
|
|
186
|
+
|
|
187
|
+
std::atomic<uint32_t> word_{0};
|
|
188
|
+
|
|
189
|
+
// Changed only by whoever seized the word: read under a borrow.
|
|
190
|
+
uint8_t* data_;
|
|
191
|
+
std::atomic<size_t> size_;
|
|
192
|
+
Destroy destroy_;
|
|
193
|
+
ExecutionContext* destroyOn_;
|
|
194
|
+
|
|
195
|
+
const String origin_;
|
|
196
|
+
const uint32_t generation_;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/// The bytes a borrow lends compiled code, with a Uint8Array's element
|
|
200
|
+
/// semantics: reads out of range are undefined, writes out of range are
|
|
201
|
+
/// ignored, values wrap to 0–255. The compiler keeps a span inside the
|
|
202
|
+
/// callback it was lent to (it cannot be stored, returned or awaited
|
|
203
|
+
/// across), so it never outlives its borrow.
|
|
204
|
+
template <bool Writable>
|
|
205
|
+
class BasicByteSpan {
|
|
206
|
+
public:
|
|
207
|
+
using Byte = std::conditional_t<Writable, uint8_t, const uint8_t>;
|
|
208
|
+
|
|
209
|
+
/// No bytes: what a variable holds before a span is assigned to it.
|
|
210
|
+
BasicByteSpan() = default;
|
|
211
|
+
explicit BasicByteSpan(std::span<Byte> bytes) : bytes_(bytes) {}
|
|
212
|
+
|
|
213
|
+
size_t size() const { return bytes_.size(); }
|
|
214
|
+
double length() const { return static_cast<double>(bytes_.size()); }
|
|
215
|
+
const uint8_t* data() const { return bytes_.data(); }
|
|
216
|
+
|
|
217
|
+
Opt<double> get(double index) const {
|
|
218
|
+
if (!contains(index)) return undefined;
|
|
219
|
+
|
|
220
|
+
return static_cast<double>(bytes_[static_cast<size_t>(index)]);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
double at(size_t i) const { return static_cast<double>(bytes_[i]); }
|
|
224
|
+
|
|
225
|
+
void set(double index, double value) const
|
|
226
|
+
requires Writable
|
|
227
|
+
{
|
|
228
|
+
if (contains(index)) bytes_[static_cast<size_t>(index)] = Bytes::toUint8(value);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/// TypedArray.prototype.fill: `start` and `end` count from the end when negative.
|
|
232
|
+
void fill(double value, double start = 0) const
|
|
233
|
+
requires Writable
|
|
234
|
+
{
|
|
235
|
+
fill(value, start, length());
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
void fill(double value, double start, double end) const
|
|
239
|
+
requires Writable
|
|
240
|
+
{
|
|
241
|
+
size_t from = detail::relativeIndex(start, bytes_.size());
|
|
242
|
+
size_t to = detail::relativeIndex(end, bytes_.size());
|
|
243
|
+
|
|
244
|
+
if (from < to) std::fill(bytes_.begin() + from, bytes_.begin() + to, Bytes::toUint8(value));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// TypedArray.prototype.set with a Uint8Array.
|
|
248
|
+
void setFrom(const Bytes& source, double offset = 0) const
|
|
249
|
+
requires Writable
|
|
250
|
+
{
|
|
251
|
+
if (!(offset >= 0) || static_cast<double>(source.size()) + std::trunc(offset) > length())
|
|
252
|
+
throwRangeError("offset is out of bounds");
|
|
253
|
+
|
|
254
|
+
std::copy(source.data(), source.data() + source.size(), bytes_.begin() + static_cast<size_t>(offset));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private:
|
|
258
|
+
bool contains(double index) const {
|
|
259
|
+
return index >= 0 && index < length() && std::trunc(index) == index;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
std::span<Byte> bytes_;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/// The same bytes: two borrows of one buffer lend equal spans.
|
|
266
|
+
template <bool Writable>
|
|
267
|
+
bool strictEquals(const BasicByteSpan<Writable>& a, const BasicByteSpan<Writable>& b) {
|
|
268
|
+
return a.data() == b.data() && a.size() == b.size();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/// Like a Uint8Array's: the bytes joined with commas.
|
|
272
|
+
template <bool Writable>
|
|
273
|
+
String toJsString(const BasicByteSpan<Writable>& b) {
|
|
274
|
+
String out;
|
|
275
|
+
for (size_t i = 0; i < b.size(); i++) {
|
|
276
|
+
if (i > 0) out += String::fromLatin1(",");
|
|
277
|
+
out += numberToString(b.at(i));
|
|
278
|
+
}
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
using ByteSpan = BasicByteSpan<false>;
|
|
283
|
+
using MutableByteSpan = BasicByteSpan<true>;
|
|
284
|
+
|
|
285
|
+
/// `buffer.withRead(f)` as compiled: f is lent a ByteSpan.
|
|
286
|
+
template <class F>
|
|
287
|
+
decltype(auto) withRead(const NativeBuffer& buffer, F&& f) {
|
|
288
|
+
return buffer->withRead([&](std::span<const uint8_t> bytes) -> decltype(auto) { return f(ByteSpan(bytes)); });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/// `buffer.withWrite(f)` as compiled: f is lent a MutableByteSpan.
|
|
292
|
+
template <class F>
|
|
293
|
+
decltype(auto) withWrite(const NativeBuffer& buffer, F&& f) {
|
|
294
|
+
return buffer->withWrite([&](std::span<uint8_t> bytes) -> decltype(auto) { return f(MutableByteSpan(bytes)); });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/// `span[i]` read for a compound assignment: a RangeError out of range, as for a Uint8Array.
|
|
298
|
+
template <bool Writable>
|
|
299
|
+
double elementAt(const BasicByteSpan<Writable>& bytes, double i) {
|
|
300
|
+
Opt<double> v = bytes.get(i);
|
|
301
|
+
if (!v.has()) throwRangeError("Uint8Array index out of bounds");
|
|
302
|
+
|
|
303
|
+
return v.get();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
inline double setElement(const MutableByteSpan& bytes, double i, double v) {
|
|
307
|
+
bytes.set(i, v);
|
|
308
|
+
return v;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/// A buffer crosses to a task by moving: once per input however often it
|
|
312
|
+
/// appears, committed with the rest of the input.
|
|
313
|
+
template <>
|
|
314
|
+
struct Transport<NativeBuffer> {
|
|
315
|
+
static NativeBuffer copy(const NativeBuffer& source, CopyGraph& graph) {
|
|
316
|
+
if (!source) return source;
|
|
317
|
+
|
|
318
|
+
if (auto* moved = graph.find<NativeBuffer>(source.get())) return *moved;
|
|
319
|
+
|
|
320
|
+
NativeBuffer moved = source->beginTransfer();
|
|
321
|
+
graph.remember(source.get(), moved);
|
|
322
|
+
|
|
323
|
+
graph.onCommit([source] { source->commitTransfer(); }, [source, moved] { source->abortTransfer(moved); });
|
|
324
|
+
return moved;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
} // namespace lucent
|
|
@@ -63,7 +63,10 @@ String utf8Decode(const Bytes& b) {
|
|
|
63
63
|
|
|
64
64
|
Promise<void> delay(double ms) {
|
|
65
65
|
Promise<void> p;
|
|
66
|
-
|
|
66
|
+
|
|
67
|
+
// On the context the promise belongs to: the calling one.
|
|
68
|
+
ExecutionContext::of(ExecutionContext::currentRef()).postDelayed(ms, [p] { p.resolve(undefined); });
|
|
69
|
+
|
|
67
70
|
return p;
|
|
68
71
|
}
|
|
69
72
|
|
|
@@ -31,6 +31,16 @@ class Bytes {
|
|
|
31
31
|
}
|
|
32
32
|
static Bytes copy(const uint8_t* data, size_t n) { return Bytes(std::vector<uint8_t>(data, data + n)); }
|
|
33
33
|
|
|
34
|
+
/// A view of `length` bytes at `offset` in `buffer`, which must hold them.
|
|
35
|
+
static Bytes view(std::shared_ptr<std::vector<uint8_t>> buffer, size_t offset, size_t length) {
|
|
36
|
+
return Bytes(std::move(buffer), offset, length);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// The buffer this view is over, and where in it the view starts: what a
|
|
40
|
+
/// copy needs to keep views of one buffer sharing theirs (transport.h).
|
|
41
|
+
const std::shared_ptr<std::vector<uint8_t>>& buffer() const { return buf_; }
|
|
42
|
+
size_t offset() const { return off_; }
|
|
43
|
+
|
|
34
44
|
static uint8_t toUint8(double v) { return static_cast<uint8_t>(toUint32(v) & 0xFF); }
|
|
35
45
|
|
|
36
46
|
size_t size() const { return len_; }
|
|
@@ -113,15 +123,24 @@ class Bytes {
|
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
const void* identity() const { return this->buf_.get(); }
|
|
116
|
-
friend bool strictEquals(const Bytes& a, const Bytes& b) { return a.buf_ == b.buf_ && a.off_ == b.off_ && a.len_ == b.len_; }
|
|
117
|
-
friend String toJsString(const Bytes& b) { return b.join(); }
|
|
118
126
|
|
|
119
127
|
private:
|
|
128
|
+
Bytes(std::shared_ptr<std::vector<uint8_t>> buffer, size_t offset, size_t length)
|
|
129
|
+
: buf_(std::move(buffer)), off_(offset), len_(length) {}
|
|
130
|
+
|
|
120
131
|
std::shared_ptr<std::vector<uint8_t>> buf_;
|
|
121
132
|
size_t off_ = 0;
|
|
122
133
|
size_t len_ = 0;
|
|
123
134
|
};
|
|
124
135
|
|
|
136
|
+
/// A view is named by its buffer and range, not by the object: two
|
|
137
|
+
/// subarray() views of one range are the same view (docs/semantics.md).
|
|
138
|
+
inline bool strictEquals(const Bytes& a, const Bytes& b) {
|
|
139
|
+
return a.buffer() == b.buffer() && a.offset() == b.offset() && a.size() == b.size();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
inline String toJsString(const Bytes& b) { return b.join(); }
|
|
143
|
+
|
|
125
144
|
/// TextEncoder().encode / TextDecoder().decode for UTF-8.
|
|
126
145
|
Bytes utf8Encode(const String& s);
|
|
127
146
|
String utf8Decode(const Bytes& b);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#include "callback.h"
|
|
2
|
+
|
|
3
|
+
namespace lucent {
|
|
4
|
+
|
|
5
|
+
std::function<void()> detail::followSignal(const AbortSignal& signal, std::function<void(Error)> cancel) {
|
|
6
|
+
std::weak_ptr<AbortSignalObject> weak = signal;
|
|
7
|
+
|
|
8
|
+
uint64_t id = signal->add([weak, cancel] {
|
|
9
|
+
if (auto s = weak.lock()) cancel(s->reason);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Refused on the signal's context: it has aborted since it was checked.
|
|
13
|
+
if (id == 0) {
|
|
14
|
+
cancel(signal->reason);
|
|
15
|
+
return [] {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return [weak, id] {
|
|
19
|
+
if (auto s = weak.lock()) s->remove(id);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Opt<Error> detail::abortedBy(const Opt<AbortSignal>& signal) {
|
|
24
|
+
if (!signal.has() || !signal.get() || !signal.get()->aborted.load()) return undefined;
|
|
25
|
+
|
|
26
|
+
return signal.get()->reason;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
} // namespace lucent
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// Lucent runtime — composing callbacks into promises and subscriptions.
|
|
2
|
+
//
|
|
3
|
+
// fromCallback turns an API that reports once through callbacks into a
|
|
4
|
+
// promise; subscribe delivers what a listener reports until it ends. Neither
|
|
5
|
+
// knows what it listens to: Lucent code starts listening (the registration)
|
|
6
|
+
// and returns what stops it (the cleanup).
|
|
7
|
+
//
|
|
8
|
+
// Each is one Operation (scope.h) under the calling context's root scope.
|
|
9
|
+
// It settles exactly once, at the first of its callbacks, its signal
|
|
10
|
+
// aborting and the scope's disposal; whatever comes later is dropped. Its
|
|
11
|
+
// cleanup runs exactly once, as soon as it settles, or as soon as the
|
|
12
|
+
// registration returns it if it settled during registration.
|
|
13
|
+
//
|
|
14
|
+
// Everything it runs belongs to its owner, the context it was called on:
|
|
15
|
+
// the registration, the value handler, the cleanup and the promise. The
|
|
16
|
+
// functions it hands to Lucent code may be called from any thread: from
|
|
17
|
+
// another, a call is posted to the owner, after those posted before it,
|
|
18
|
+
// and takes effect when it runs there.
|
|
19
|
+
#pragma once
|
|
20
|
+
|
|
21
|
+
#include <exception>
|
|
22
|
+
#include <functional>
|
|
23
|
+
#include <memory>
|
|
24
|
+
#include <tuple>
|
|
25
|
+
#include <utility>
|
|
26
|
+
|
|
27
|
+
#include "abort.h"
|
|
28
|
+
#include "async.h"
|
|
29
|
+
#include "core.h"
|
|
30
|
+
#include "execution.h"
|
|
31
|
+
#include "function.h"
|
|
32
|
+
#include "jserror.h"
|
|
33
|
+
#include "scope.h"
|
|
34
|
+
|
|
35
|
+
namespace lucent {
|
|
36
|
+
|
|
37
|
+
namespace detail {
|
|
38
|
+
|
|
39
|
+
/// Cancels with the signal's reason when it aborts, until the returned
|
|
40
|
+
/// function stops following it. `cancel` runs on the signal's context.
|
|
41
|
+
std::function<void()> followSignal(const AbortSignal& signal, std::function<void(Error)> cancel);
|
|
42
|
+
|
|
43
|
+
/// The reason an already aborted signal gives; none otherwise.
|
|
44
|
+
Opt<Error> abortedBy(const Opt<AbortSignal>& signal);
|
|
45
|
+
|
|
46
|
+
/// Makes the function of type `F` handed to Lucent code, passing what it
|
|
47
|
+
/// is called with to `sink`.
|
|
48
|
+
template <class F>
|
|
49
|
+
struct Handout;
|
|
50
|
+
|
|
51
|
+
template <class... A>
|
|
52
|
+
struct Handout<Fn<void(A...)>> {
|
|
53
|
+
template <class Sink>
|
|
54
|
+
static Fn<void(A...)> make(Sink sink) {
|
|
55
|
+
return Fn<void(A...)>([sink = std::move(sink)](A... args) mutable { sink(std::move(args)...); });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/// One composition's operation and owner, shared by what it hands out.
|
|
60
|
+
template <class T>
|
|
61
|
+
class Composition : public std::enable_shared_from_this<Composition<T>> {
|
|
62
|
+
public:
|
|
63
|
+
using Op = Operation<T>;
|
|
64
|
+
using Value = typename Op::Value;
|
|
65
|
+
|
|
66
|
+
Composition(ContextRef owner, std::shared_ptr<Op> op) : owner_(std::move(owner)), op_(std::move(op)) {}
|
|
67
|
+
|
|
68
|
+
/// Settles on the owner; the first settlement wins.
|
|
69
|
+
void succeed(Value value) {
|
|
70
|
+
if constexpr (std::is_void_v<T>) {
|
|
71
|
+
onOwner([](Op& op) { op.succeed(); });
|
|
72
|
+
} else {
|
|
73
|
+
onOwner([value = std::move(value)](Op& op) mutable { op.succeed(std::move(value)); });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
void fail(Error error) {
|
|
78
|
+
onOwner([error = std::move(error)](Op& op) mutable { op.fail(std::move(error)); });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Runs `deliver` on the owner while the operation is pending. What it
|
|
82
|
+
/// throws settles the operation as failed.
|
|
83
|
+
template <class Deliver>
|
|
84
|
+
void deliver(Deliver deliver) {
|
|
85
|
+
onOwner([deliver = std::move(deliver)](Op& op) mutable {
|
|
86
|
+
if (op.state() != OperationState::Pending) return;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
deliver();
|
|
90
|
+
} catch (...) {
|
|
91
|
+
op.fail(currentError(std::current_exception()));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Cancels with `signal`'s reason when it aborts, until settlement.
|
|
97
|
+
void follow(const Opt<AbortSignal>& signal) {
|
|
98
|
+
if (!signal.has() || !signal.get()) return;
|
|
99
|
+
|
|
100
|
+
auto self = this->shared_from_this();
|
|
101
|
+
auto unfollow = followSignal(signal.get(), [self](Error reason) {
|
|
102
|
+
self->onOwner([reason = std::move(reason)](Op& op) mutable { op.cancel(std::move(reason)); });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
op_->onSettled([unfollow = std::move(unfollow)](const typename Op::Outcome&) { unfollow(); });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private:
|
|
109
|
+
/// Runs `f` with the operation on the owner: now, on the owner's thread,
|
|
110
|
+
/// else posted there (and dropped if the owner takes no more work).
|
|
111
|
+
template <class F>
|
|
112
|
+
void onOwner(F f) {
|
|
113
|
+
ExecutionContext& owner = ExecutionContext::of(owner_);
|
|
114
|
+
|
|
115
|
+
if (owner.isCurrent()) {
|
|
116
|
+
f(*op_);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
owner.post([self = this->shared_from_this(), f = std::move(f)]() mutable { f(*self->op_); });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const ContextRef owner_;
|
|
124
|
+
const std::shared_ptr<Op> op_;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/// The cleanup a registration returned (a function, maybe absent), as the
|
|
128
|
+
/// operation runs it; what the cleanup returns is ignored.
|
|
129
|
+
template <class Returned>
|
|
130
|
+
std::function<void()> cleanupOf(const Returned& returned) {
|
|
131
|
+
if constexpr (IsOpt<Returned>::value) {
|
|
132
|
+
if (!returned.has()) return {};
|
|
133
|
+
|
|
134
|
+
return cleanupOf(returned.get());
|
|
135
|
+
} else {
|
|
136
|
+
if (!returned) return {};
|
|
137
|
+
|
|
138
|
+
return [f = returned] { f(); };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// Calls the registration and takes its cleanup, if it returns one.
|
|
143
|
+
template <class Returned, class... Handed>
|
|
144
|
+
std::function<void()> registerWith(const Fn<Returned(Handed...)>& registration, Handed... handed) {
|
|
145
|
+
if constexpr (std::is_void_v<Returned>) {
|
|
146
|
+
registration(std::move(handed)...);
|
|
147
|
+
return {};
|
|
148
|
+
} else {
|
|
149
|
+
return cleanupOf(registration(std::move(handed)...));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Starts an operation of the calling context that `start` registers (with
|
|
154
|
+
/// the composition), following `signal`, and the promise it settles.
|
|
155
|
+
template <class T, class Start>
|
|
156
|
+
Promise<T> compose(const Opt<AbortSignal>& signal, Start start) {
|
|
157
|
+
using Op = Operation<T>;
|
|
158
|
+
|
|
159
|
+
ContextRef owner = ExecutionContext::currentRef();
|
|
160
|
+
Promise<T> promise;
|
|
161
|
+
|
|
162
|
+
auto op = Op::start(
|
|
163
|
+
ExecutionContext::of(owner).root(),
|
|
164
|
+
[&](const std::shared_ptr<Op>& op) {
|
|
165
|
+
auto composition = std::make_shared<Composition<T>>(owner, op);
|
|
166
|
+
|
|
167
|
+
composition->follow(signal);
|
|
168
|
+
|
|
169
|
+
return start(composition);
|
|
170
|
+
},
|
|
171
|
+
abortedBy(signal));
|
|
172
|
+
|
|
173
|
+
op->onSettled([promise](const typename Op::Outcome& outcome) {
|
|
174
|
+
if (outcome.state == OperationState::Succeeded) {
|
|
175
|
+
promise.resolve(*outcome.value);
|
|
176
|
+
} else {
|
|
177
|
+
promise.reject(outcome.error);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return promise;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
} // namespace detail
|
|
185
|
+
|
|
186
|
+
/// `fromCallback(register, signal)` (lucent:core): calls `registration`
|
|
187
|
+
/// with `resolve` and `reject`, and returns the promise the first of them,
|
|
188
|
+
/// or the signal, settles. A registration that throws rejects it; it
|
|
189
|
+
/// returns its cleanup (a Fn taking nothing, maybe in an Opt) or nothing.
|
|
190
|
+
/// `Resolve` is `Fn<void(T)>`, or `Fn<void()>` when T is void.
|
|
191
|
+
template <class T, class Resolve, class Returned>
|
|
192
|
+
Promise<T> fromCallback(const Fn<Returned(Resolve, Fn<void(Error)>)>& registration, Opt<AbortSignal> signal = {}) {
|
|
193
|
+
using Composition = detail::Composition<T>;
|
|
194
|
+
|
|
195
|
+
return detail::compose<T>(signal, [&](const std::shared_ptr<Composition>& c) {
|
|
196
|
+
auto resolve = detail::Handout<Resolve>::make([c](auto... value) {
|
|
197
|
+
static_assert(sizeof...(value) <= 1, "resolve takes one value");
|
|
198
|
+
|
|
199
|
+
c->succeed(typename Composition::Value(std::move(value)...));
|
|
200
|
+
});
|
|
201
|
+
Fn<void(Error)> reject = [c](Error error) { c->fail(std::move(error)); };
|
|
202
|
+
|
|
203
|
+
return detail::registerWith(registration, std::move(resolve), std::move(reject));
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// `subscribe(register, onValue, signal)` (lucent:core): calls
|
|
208
|
+
/// `registration` with `next`, `end` and `fail`; `next` calls `onValue`
|
|
209
|
+
/// until the subscription ends, at the first of `end`, `fail`, `onValue`
|
|
210
|
+
/// throwing and the signal. Returns the promise of that end. What
|
|
211
|
+
/// `onValue` returns is ignored.
|
|
212
|
+
template <class Next, class Returned, class OnValue>
|
|
213
|
+
Promise<void> subscribe(const Fn<Returned(Next, Fn<void()>, Fn<void(Error)>)>& registration, OnValue onValue,
|
|
214
|
+
Opt<AbortSignal> signal = {}) {
|
|
215
|
+
using Composition = detail::Composition<void>;
|
|
216
|
+
|
|
217
|
+
return detail::compose<void>(signal, [&](const std::shared_ptr<Composition>& c) {
|
|
218
|
+
auto next = detail::Handout<Next>::make([c, onValue](auto... value) {
|
|
219
|
+
c->deliver([onValue, value = std::make_tuple(std::move(value)...)]() mutable {
|
|
220
|
+
std::apply([&](auto&&... v) { onValue(std::forward<decltype(v)>(v)...); }, std::move(value));
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
Fn<void()> end = [c] { c->succeed(undefined); };
|
|
224
|
+
Fn<void(Error)> fail = [c](Error error) { c->fail(std::move(error)); };
|
|
225
|
+
|
|
226
|
+
return detail::registerWith(registration, std::move(next), std::move(end), std::move(fail));
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
} // namespace lucent
|