@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,270 @@
|
|
|
1
|
+
// Lucent runtime — execution contexts.
|
|
2
|
+
//
|
|
3
|
+
// An execution context is where Lucent code runs one piece at a time. Its
|
|
4
|
+
// executor (a thread, or the platform's main loop) runs turns in the order
|
|
5
|
+
// they were posted; each turn runs its job, then the context's own
|
|
6
|
+
// microtasks. Its root scope is the ancestor of every scope created for
|
|
7
|
+
// work that lives there. There are three kinds:
|
|
8
|
+
//
|
|
9
|
+
// - the legacy module context (Scheduler, scheduler.h): the Lucent thread,
|
|
10
|
+
// also entered from any thread by taking the Lucent lock. Every module's
|
|
11
|
+
// code runs there, serialized as it always was.
|
|
12
|
+
// - the main context: the platform's UI loop. It never takes the Lucent
|
|
13
|
+
// lock, so it never waits behind module code.
|
|
14
|
+
// - isolated contexts, each with a thread of its own.
|
|
15
|
+
//
|
|
16
|
+
// A context never waits for another: work crosses between them as posted
|
|
17
|
+
// jobs that own what they carry.
|
|
18
|
+
#pragma once
|
|
19
|
+
|
|
20
|
+
#include <atomic>
|
|
21
|
+
#include <chrono>
|
|
22
|
+
#include <condition_variable>
|
|
23
|
+
#include <cstdint>
|
|
24
|
+
#include <deque>
|
|
25
|
+
#include <functional>
|
|
26
|
+
#include <memory>
|
|
27
|
+
#include <mutex>
|
|
28
|
+
#include <queue>
|
|
29
|
+
#include <thread>
|
|
30
|
+
#include <vector>
|
|
31
|
+
|
|
32
|
+
#include "report.h"
|
|
33
|
+
#include "scope.h"
|
|
34
|
+
|
|
35
|
+
namespace lucent {
|
|
36
|
+
|
|
37
|
+
using Job = std::function<void()>;
|
|
38
|
+
|
|
39
|
+
/// Process-unique and never reused; 0 is none.
|
|
40
|
+
using ContextId = uint32_t;
|
|
41
|
+
|
|
42
|
+
/// Runs `job` on the platform's main thread (main queue, main Looper; a
|
|
43
|
+
/// dedicated thread standing in for it on other hosts). Implemented per
|
|
44
|
+
/// platform: lucent/platform, and native.cpp for hosts.
|
|
45
|
+
void postToMain(std::function<void()> job);
|
|
46
|
+
bool onMainThread();
|
|
47
|
+
|
|
48
|
+
/// A thread that runs posted jobs, and timers once due, one at a time in
|
|
49
|
+
/// order. The thread keeps this object alive until it ends.
|
|
50
|
+
class WorkerThread : public std::enable_shared_from_this<WorkerThread> {
|
|
51
|
+
public:
|
|
52
|
+
/// How the thread runs a job (a context's turn).
|
|
53
|
+
using Run = std::function<void(Job&)>;
|
|
54
|
+
|
|
55
|
+
static std::shared_ptr<WorkerThread> start(Run run);
|
|
56
|
+
|
|
57
|
+
WorkerThread(const WorkerThread&) = delete;
|
|
58
|
+
WorkerThread& operator=(const WorkerThread&) = delete;
|
|
59
|
+
|
|
60
|
+
/// Any thread. False once stopping: the job is dropped.
|
|
61
|
+
bool post(Job job);
|
|
62
|
+
bool postDelayed(double ms, Job job);
|
|
63
|
+
|
|
64
|
+
bool isCurrent() const { return std::this_thread::get_id() == id_; }
|
|
65
|
+
|
|
66
|
+
/// Jobs queued or running, and timers pending.
|
|
67
|
+
size_t pendingWork();
|
|
68
|
+
|
|
69
|
+
/// Blocks until no jobs or timers are pending, or the timeout elapses.
|
|
70
|
+
/// For tests and tools: never call it from a context's thread.
|
|
71
|
+
bool waitIdle(double timeoutMs);
|
|
72
|
+
|
|
73
|
+
/// Takes no more work: runs `last` (directly, not through Run) after the
|
|
74
|
+
/// job running now, destroys what is still queued on this thread, and
|
|
75
|
+
/// ends it. Returns at once, without waiting for any of that.
|
|
76
|
+
void stop(Job last = nullptr);
|
|
77
|
+
|
|
78
|
+
/// Blocks until the thread has ended, or the timeout elapses (tests).
|
|
79
|
+
bool waitStopped(double timeoutMs);
|
|
80
|
+
|
|
81
|
+
private:
|
|
82
|
+
explicit WorkerThread(Run run) : run_(std::move(run)) {}
|
|
83
|
+
|
|
84
|
+
void loop();
|
|
85
|
+
|
|
86
|
+
struct Timer {
|
|
87
|
+
std::chrono::steady_clock::time_point at;
|
|
88
|
+
uint64_t seq;
|
|
89
|
+
Job job;
|
|
90
|
+
|
|
91
|
+
bool operator>(const Timer& o) const { return at != o.at ? at > o.at : seq > o.seq; }
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const Run run_;
|
|
95
|
+
std::thread::id id_;
|
|
96
|
+
|
|
97
|
+
std::mutex m_;
|
|
98
|
+
std::condition_variable cv_;
|
|
99
|
+
std::condition_variable idleCv_;
|
|
100
|
+
std::deque<Job> jobs_;
|
|
101
|
+
std::priority_queue<Timer, std::vector<Timer>, std::greater<Timer>> timers_;
|
|
102
|
+
uint64_t timerSeq_ = 0;
|
|
103
|
+
size_t running_ = 0;
|
|
104
|
+
bool stopping_ = false;
|
|
105
|
+
bool stopped_ = false;
|
|
106
|
+
Job last_;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
class ExecutionContext;
|
|
110
|
+
|
|
111
|
+
/// A context as an owner: null names the legacy module context, which owns
|
|
112
|
+
/// whatever no other context does.
|
|
113
|
+
using ContextRef = std::shared_ptr<ExecutionContext>;
|
|
114
|
+
|
|
115
|
+
/// Also the owner of its root scope and the scopes under it: their disposal
|
|
116
|
+
/// runs here.
|
|
117
|
+
class ExecutionContext : public Scope::Owner, public std::enable_shared_from_this<ExecutionContext> {
|
|
118
|
+
public:
|
|
119
|
+
ExecutionContext(const ExecutionContext&) = delete;
|
|
120
|
+
ExecutionContext& operator=(const ExecutionContext&) = delete;
|
|
121
|
+
virtual ~ExecutionContext() = default;
|
|
122
|
+
|
|
123
|
+
/// The context the calling thread is in: the legacy module context while
|
|
124
|
+
/// the thread holds the Lucent lock, else the one it entered (a turn, or
|
|
125
|
+
/// a ContextEntry); null outside any.
|
|
126
|
+
static ExecutionContext* current();
|
|
127
|
+
|
|
128
|
+
/// current() as an owner reference (null for the legacy module context,
|
|
129
|
+
/// and outside any context).
|
|
130
|
+
static ContextRef currentRef();
|
|
131
|
+
|
|
132
|
+
/// The legacy module context (Scheduler::instance()).
|
|
133
|
+
static ExecutionContext& legacy();
|
|
134
|
+
|
|
135
|
+
/// The context `ref` names.
|
|
136
|
+
static ExecutionContext& of(const ContextRef& ref) { return ref ? *ref : legacy(); }
|
|
137
|
+
|
|
138
|
+
/// The platform's UI loop (a stand-in thread on hosts without one).
|
|
139
|
+
static ExecutionContext& main();
|
|
140
|
+
|
|
141
|
+
ContextId id() const { return id_; }
|
|
142
|
+
|
|
143
|
+
const std::shared_ptr<Scope>& root() const { return root_; }
|
|
144
|
+
|
|
145
|
+
bool isCurrent() const override { return current() == this; }
|
|
146
|
+
|
|
147
|
+
/// Whether the calling thread is the one this context's turns run on
|
|
148
|
+
/// (the main thread, for the main context), entered or not.
|
|
149
|
+
virtual bool onExecutor() const = 0;
|
|
150
|
+
|
|
151
|
+
/// Any thread: runs `job` as a turn of this context. False if the
|
|
152
|
+
/// context has stopped; the job is then destroyed on the calling thread.
|
|
153
|
+
bool post(Job job) override;
|
|
154
|
+
|
|
155
|
+
/// As post(), but the job is dropped, not run, if `owner` was disposed
|
|
156
|
+
/// before its turn came (it is destroyed on this context).
|
|
157
|
+
bool post(Job job, const std::shared_ptr<Scope>& owner);
|
|
158
|
+
|
|
159
|
+
/// Any thread: runs `job` as a turn of this context after `ms`.
|
|
160
|
+
bool postDelayed(double ms, Job job);
|
|
161
|
+
|
|
162
|
+
/// Queues a microtask. Only on this context: throws std::logic_error
|
|
163
|
+
/// elsewhere (post instead).
|
|
164
|
+
void enqueueMicrotask(Job job);
|
|
165
|
+
|
|
166
|
+
/// Runs queued microtasks, including those they queue. Only on this
|
|
167
|
+
/// context.
|
|
168
|
+
void drainMicrotasks();
|
|
169
|
+
|
|
170
|
+
bool hasMicrotasks() const { return *pendingMicrotasks_ > 0; }
|
|
171
|
+
|
|
172
|
+
protected:
|
|
173
|
+
/// `microtaskCount` is where the context counts its microtasks, if not in
|
|
174
|
+
/// itself: the legacy module context's is a static, which every call from
|
|
175
|
+
/// JavaScript reads without reaching the instance.
|
|
176
|
+
explicit ExecutionContext(size_t* microtaskCount = nullptr);
|
|
177
|
+
|
|
178
|
+
/// Gives the context its root scope, owned by it. Called once, by the
|
|
179
|
+
/// factory: the context must already be shared.
|
|
180
|
+
void makeRoot() { root_ = Scope::createRoot(0, weak_from_this()); }
|
|
181
|
+
|
|
182
|
+
/// Hands a turn to the executor; false if it has stopped.
|
|
183
|
+
virtual bool dispatch(Job job) = 0;
|
|
184
|
+
virtual bool dispatchDelayed(double ms, Job job) = 0;
|
|
185
|
+
|
|
186
|
+
/// Runs `job` as a turn: entered, the job run and destroyed, then the
|
|
187
|
+
/// microtasks.
|
|
188
|
+
virtual void runTurn(Job& job);
|
|
189
|
+
|
|
190
|
+
private:
|
|
191
|
+
friend class ContextEntry;
|
|
192
|
+
|
|
193
|
+
const ContextId id_;
|
|
194
|
+
std::shared_ptr<Scope> root_;
|
|
195
|
+
|
|
196
|
+
std::deque<Job> microtasks_;
|
|
197
|
+
size_t ownMicrotasks_ = 0;
|
|
198
|
+
size_t* const pendingMicrotasks_;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/// Enters a context other than the legacy module context (which is entered
|
|
202
|
+
/// with LucentScope) on the calling thread: for a platform callback that
|
|
203
|
+
/// needs an answer now. The thread must be the context's own (the main
|
|
204
|
+
/// thread for the main context), not holding the Lucent lock. Leaving the
|
|
205
|
+
/// outermost entry runs the context's microtasks.
|
|
206
|
+
class ContextEntry {
|
|
207
|
+
public:
|
|
208
|
+
explicit ContextEntry(ExecutionContext& context);
|
|
209
|
+
~ContextEntry();
|
|
210
|
+
|
|
211
|
+
ContextEntry(const ContextEntry&) = delete;
|
|
212
|
+
ContextEntry& operator=(const ContextEntry&) = delete;
|
|
213
|
+
|
|
214
|
+
private:
|
|
215
|
+
ExecutionContext& context_;
|
|
216
|
+
ExecutionContext* previous_;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/// A context with a thread of its own, running until shutdown().
|
|
220
|
+
class IsolatedContext final : public ExecutionContext {
|
|
221
|
+
public:
|
|
222
|
+
static std::shared_ptr<IsolatedContext> create();
|
|
223
|
+
|
|
224
|
+
/// Shuts down.
|
|
225
|
+
~IsolatedContext() override;
|
|
226
|
+
|
|
227
|
+
/// Takes no more work, then, on its thread and after the turn running
|
|
228
|
+
/// now: disposes the root scope and drops whatever is still queued.
|
|
229
|
+
/// Returns at once.
|
|
230
|
+
void shutdown();
|
|
231
|
+
|
|
232
|
+
size_t pendingWork() { return worker_->pendingWork(); }
|
|
233
|
+
|
|
234
|
+
/// For tests: see WorkerThread.
|
|
235
|
+
bool waitIdle(double timeoutMs) { return worker_->waitIdle(timeoutMs); }
|
|
236
|
+
bool waitStopped(double timeoutMs) { return worker_->waitStopped(timeoutMs); }
|
|
237
|
+
|
|
238
|
+
private:
|
|
239
|
+
IsolatedContext();
|
|
240
|
+
|
|
241
|
+
bool dispatch(Job job) override;
|
|
242
|
+
bool dispatchDelayed(double ms, Job job) override;
|
|
243
|
+
bool onExecutor() const override { return worker_->isCurrent(); }
|
|
244
|
+
|
|
245
|
+
std::shared_ptr<WorkerThread> worker_;
|
|
246
|
+
std::atomic<bool> stopping_{false};
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/// The scope module code's work belongs to (a compute task, say): the scope
|
|
250
|
+
/// of the JavaScript runtime its modules run for, so tearing that runtime
|
|
251
|
+
/// down (a reload) cancels the work; the legacy module context's root while
|
|
252
|
+
/// no runtime is attached. A torn-down runtime's scope stays the module
|
|
253
|
+
/// scope until another replaces it: nothing more starts for it. Any thread.
|
|
254
|
+
std::shared_ptr<Scope> moduleScope();
|
|
255
|
+
|
|
256
|
+
/// Makes `scope` the module scope (null: none); Host::create gives it the
|
|
257
|
+
/// new runtime's.
|
|
258
|
+
void setModuleScope(std::shared_ptr<Scope> scope);
|
|
259
|
+
|
|
260
|
+
namespace detail {
|
|
261
|
+
/// Runs `job`, reporting what it throws.
|
|
262
|
+
void runGuarded(Job& job, const char* where);
|
|
263
|
+
|
|
264
|
+
/// Runs `job` on the thread `on` runs on: here if that is this thread, or
|
|
265
|
+
/// `on` is null (what it throws reaches the caller); else as a turn there,
|
|
266
|
+
/// where what it throws is reported. Here too if `on` takes no more work.
|
|
267
|
+
void runOn(ExecutionContext* on, Job job);
|
|
268
|
+
} // namespace detail
|
|
269
|
+
|
|
270
|
+
} // namespace lucent
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#include "extension.h"
|
|
2
|
+
|
|
3
|
+
#include <cstring>
|
|
4
|
+
|
|
5
|
+
namespace lucent {
|
|
6
|
+
|
|
7
|
+
Handle Handle::open(const char* what, void* pointer, void (*destroy)(void*), ExecutionContext* destroyOn) {
|
|
8
|
+
auto state = std::make_shared<State>();
|
|
9
|
+
state->pointer = pointer;
|
|
10
|
+
state->destroy = destroy;
|
|
11
|
+
|
|
12
|
+
// The state owns the resource: the release reaches the state through a raw pointer.
|
|
13
|
+
State* raw = state.get();
|
|
14
|
+
state->resource = Resource::open(String::fromUtf8(what), [raw] { raw->closing(); }, destroyOn);
|
|
15
|
+
|
|
16
|
+
Handle h;
|
|
17
|
+
h.state_ = std::move(state);
|
|
18
|
+
return h;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
void Handle::State::closing() {
|
|
22
|
+
{
|
|
23
|
+
std::lock_guard<std::mutex> lock(mutex);
|
|
24
|
+
|
|
25
|
+
if (calls > 0) {
|
|
26
|
+
deferred = true;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
destroy(pointer);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void Handle::State::release() {
|
|
35
|
+
bool last;
|
|
36
|
+
{
|
|
37
|
+
std::lock_guard<std::mutex> lock(mutex);
|
|
38
|
+
last = --calls == 0 && deferred;
|
|
39
|
+
if (last) deferred = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (last) destroy(pointer);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void Handle::acquire() const {
|
|
46
|
+
if (!state_) throwError(String::fromLatin1("InvalidStateError"), String::fromLatin1("the handle is not open"));
|
|
47
|
+
|
|
48
|
+
// Checked and counted together: a close either sees this call or refuses it.
|
|
49
|
+
std::lock_guard<std::mutex> lock(state_->mutex);
|
|
50
|
+
state_->resource->check();
|
|
51
|
+
state_->calls++;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void Handle::close() const {
|
|
55
|
+
if (state_) state_->resource->close();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
namespace ext {
|
|
59
|
+
|
|
60
|
+
void outOfRange(const char* what, const std::string& low, const std::string& high, const std::string& value) {
|
|
61
|
+
throwError(String::fromLatin1("RangeError"),
|
|
62
|
+
String::fromUtf8(std::string(what) + " must be an integer from " + low + " to " + high + ", not " + value));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
uint8_t* data(Bytes& bytes) {
|
|
66
|
+
static uint8_t none = 0;
|
|
67
|
+
|
|
68
|
+
return bytes.size() ? bytes.data() : &none;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
std::string utf8(const String& s, const char* what) {
|
|
72
|
+
std::string out = s.toUtf8();
|
|
73
|
+
|
|
74
|
+
if (out.find('\0') != std::string::npos)
|
|
75
|
+
throwError(String::fromLatin1("TypeError"),
|
|
76
|
+
String::fromUtf8(std::string(what) + " holds a NUL character, which a C string cannot"));
|
|
77
|
+
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static Error failure(const char* function, const char* message) {
|
|
82
|
+
bool has = message && *message;
|
|
83
|
+
|
|
84
|
+
return makeError(has ? String::fromUtf8(std::string_view(message, std::strlen(message)))
|
|
85
|
+
: String::fromUtf8(std::string(function) + " failed"));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
void fail(const char* function, const char* message) { throwError(failure(function, message)); }
|
|
89
|
+
|
|
90
|
+
void fail(const char* function, const char* message, long long code) {
|
|
91
|
+
Error e = failure(function, message);
|
|
92
|
+
e->code = String::fromUtf8(std::to_string(code));
|
|
93
|
+
throwError(e);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
} // namespace ext
|
|
97
|
+
} // namespace lucent
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// Lucent runtime — native extensions: C functions a Lucent package declares.
|
|
2
|
+
//
|
|
3
|
+
// A Handle owns an opaque C pointer an extension's create function gave.
|
|
4
|
+
// It closes at the first of close() or its last copy going; the destroy
|
|
5
|
+
// function runs once, on the context the handle requires. After that,
|
|
6
|
+
// every use throws InvalidStateError.
|
|
7
|
+
//
|
|
8
|
+
// The conversions check what crosses into C: numbers into integer
|
|
9
|
+
// parameters as WebIDL's [EnforceRange] does, bigints exactly, byte counts
|
|
10
|
+
// against their length type, and strings as NUL-terminated UTF-8.
|
|
11
|
+
#pragma once
|
|
12
|
+
|
|
13
|
+
#include <cmath>
|
|
14
|
+
#include <cstddef>
|
|
15
|
+
#include <cstdint>
|
|
16
|
+
#include <limits>
|
|
17
|
+
#include <memory>
|
|
18
|
+
#include <mutex>
|
|
19
|
+
#include <string>
|
|
20
|
+
#include <type_traits>
|
|
21
|
+
|
|
22
|
+
#include "bigint.h"
|
|
23
|
+
#include "bytes.h"
|
|
24
|
+
#include "execution.h"
|
|
25
|
+
#include "jserror.h"
|
|
26
|
+
#include "jsstring.h"
|
|
27
|
+
#include "number.h"
|
|
28
|
+
#include "resource.h"
|
|
29
|
+
|
|
30
|
+
namespace lucent {
|
|
31
|
+
|
|
32
|
+
class Handle {
|
|
33
|
+
struct State;
|
|
34
|
+
|
|
35
|
+
public:
|
|
36
|
+
/// The pointer, held for one call: a close meanwhile (from another
|
|
37
|
+
/// thread) destroys it when the call is done, never under it.
|
|
38
|
+
template <class T>
|
|
39
|
+
class Use {
|
|
40
|
+
public:
|
|
41
|
+
explicit Use(std::shared_ptr<State> state) : state_(std::move(state)) {}
|
|
42
|
+
Use(const Use&) = delete;
|
|
43
|
+
Use& operator=(const Use&) = delete;
|
|
44
|
+
~Use() { state_->release(); }
|
|
45
|
+
|
|
46
|
+
T* get() const { return static_cast<T*>(state_->pointer); }
|
|
47
|
+
|
|
48
|
+
private:
|
|
49
|
+
std::shared_ptr<State> state_;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/// Empty: every use throws until a handle is assigned.
|
|
53
|
+
Handle() = default;
|
|
54
|
+
|
|
55
|
+
/// Owns `pointer` (never null); `destroy` takes it once, on `destroyOn`'s
|
|
56
|
+
/// thread when given. `what` names it in errors ("OrbitFilter is closed").
|
|
57
|
+
static Handle open(const char* what, void* pointer, void (*destroy)(void*), ExecutionContext* destroyOn = nullptr);
|
|
58
|
+
|
|
59
|
+
/// The pointer, for a call: throws InvalidStateError unless open.
|
|
60
|
+
template <class T>
|
|
61
|
+
Use<T> use() const {
|
|
62
|
+
acquire();
|
|
63
|
+
return Use<T>(state_);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// The pointer, unheld (tests): throws InvalidStateError unless open.
|
|
67
|
+
template <class T>
|
|
68
|
+
T* get() const {
|
|
69
|
+
return use<T>().get();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Destroys the pointer now (or posts it to its context), or when the
|
|
73
|
+
/// calls using it are done. Idempotent.
|
|
74
|
+
void close() const;
|
|
75
|
+
|
|
76
|
+
/// The same handle: copies share one.
|
|
77
|
+
const void* identity() const { return state_.get(); }
|
|
78
|
+
|
|
79
|
+
private:
|
|
80
|
+
struct State {
|
|
81
|
+
void* pointer = nullptr;
|
|
82
|
+
void (*destroy)(void*) = nullptr;
|
|
83
|
+
|
|
84
|
+
std::mutex mutex;
|
|
85
|
+
int calls = 0;
|
|
86
|
+
bool deferred = false;
|
|
87
|
+
|
|
88
|
+
/// Last: destroyed first, so a backstop close still finds the fields above.
|
|
89
|
+
std::shared_ptr<Resource> resource;
|
|
90
|
+
|
|
91
|
+
/// The resource's release: destroys now, or leaves it to the last call.
|
|
92
|
+
void closing();
|
|
93
|
+
/// A call is done.
|
|
94
|
+
void release();
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
void acquire() const;
|
|
98
|
+
|
|
99
|
+
std::shared_ptr<State> state_;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
inline bool strictEquals(const Handle& a, const Handle& b) { return a.identity() == b.identity(); }
|
|
103
|
+
inline String toJsString(const Handle&) { return String::fromLatin1("[object Handle]"); }
|
|
104
|
+
|
|
105
|
+
namespace ext {
|
|
106
|
+
|
|
107
|
+
[[noreturn]] void outOfRange(const char* what, const std::string& low, const std::string& high, const std::string& value);
|
|
108
|
+
|
|
109
|
+
/// A number as C integer type `I`: truncated toward zero; RangeError when
|
|
110
|
+
/// it is not finite or outside `I`. `what` names the parameter.
|
|
111
|
+
template <class I>
|
|
112
|
+
requires std::is_integral_v<I>
|
|
113
|
+
I integer(double v, const char* what) {
|
|
114
|
+
using L = std::numeric_limits<I>;
|
|
115
|
+
double t = std::trunc(v);
|
|
116
|
+
|
|
117
|
+
if (!(t >= static_cast<double>(L::min()) && t <= static_cast<double>(L::max())) ||
|
|
118
|
+
(sizeof(I) == 8 && t == static_cast<double>(L::max())))
|
|
119
|
+
outOfRange(what, std::to_string(L::min()), std::to_string(L::max()), numberToString(v).toUtf8());
|
|
120
|
+
|
|
121
|
+
return static_cast<I>(t);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// A bigint as C integer type `I`, exactly; RangeError outside `I`.
|
|
125
|
+
template <class I>
|
|
126
|
+
requires std::is_integral_v<I>
|
|
127
|
+
I integer(const BigInt& v, const char* what) {
|
|
128
|
+
using L = std::numeric_limits<I>;
|
|
129
|
+
|
|
130
|
+
if constexpr (std::is_signed_v<I>) {
|
|
131
|
+
auto x = v.tryInt64();
|
|
132
|
+
if (x && *x >= static_cast<int64_t>(L::min()) && *x <= static_cast<int64_t>(L::max())) return static_cast<I>(*x);
|
|
133
|
+
} else {
|
|
134
|
+
auto x = v.tryUint64();
|
|
135
|
+
if (x && *x <= static_cast<uint64_t>(L::max())) return static_cast<I>(*x);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
outOfRange(what, std::to_string(L::min()), std::to_string(L::max()), v.toString().toUtf8());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// A number as C floating type `F`, rounded as Math.fround does: infinite
|
|
142
|
+
/// from halfway past float's largest value on (rather than undefined
|
|
143
|
+
/// behavior), the largest below that.
|
|
144
|
+
template <class F>
|
|
145
|
+
requires std::is_floating_point_v<F>
|
|
146
|
+
F real(double v) {
|
|
147
|
+
using L = std::numeric_limits<F>;
|
|
148
|
+
|
|
149
|
+
if constexpr (sizeof(F) < sizeof(double)) {
|
|
150
|
+
// The largest plus half its gap to infinity: a tie, which rounds to even (infinity).
|
|
151
|
+
const double overflow = static_cast<double>(L::max()) + std::ldexp(1.0, L::max_exponent - L::digits - 1);
|
|
152
|
+
|
|
153
|
+
if (v >= overflow) return L::infinity();
|
|
154
|
+
if (v <= -overflow) return -L::infinity();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return static_cast<F>(v);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// The bytes' data for a C pointer: never null, even for none.
|
|
161
|
+
uint8_t* data(Bytes& bytes);
|
|
162
|
+
|
|
163
|
+
/// A byte count as the C length type `I`; RangeError when it does not fit.
|
|
164
|
+
template <class I>
|
|
165
|
+
requires std::is_integral_v<I>
|
|
166
|
+
I length(size_t n, const char* what) {
|
|
167
|
+
if (n > static_cast<std::make_unsigned_t<I>>(std::numeric_limits<I>::max()))
|
|
168
|
+
throwError(String::fromLatin1("RangeError"),
|
|
169
|
+
String::fromUtf8(std::string(what) + " is " + std::to_string(n) + " bytes; at most " +
|
|
170
|
+
std::to_string(std::numeric_limits<I>::max()) + " fit"));
|
|
171
|
+
|
|
172
|
+
return static_cast<I>(n);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// A C integer result as a number (types of 32 bits or fewer: always exact).
|
|
176
|
+
template <class I>
|
|
177
|
+
requires std::is_integral_v<I>
|
|
178
|
+
double number(I v) {
|
|
179
|
+
static_assert(sizeof(I) <= 4, "64-bit C integers cross as bigints");
|
|
180
|
+
return static_cast<double>(v);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// A C integer result as a bigint.
|
|
184
|
+
template <class I>
|
|
185
|
+
requires std::is_integral_v<I>
|
|
186
|
+
BigInt bigint(I v) {
|
|
187
|
+
if constexpr (std::is_signed_v<I>)
|
|
188
|
+
return BigInt::fromInt64(static_cast<int64_t>(v));
|
|
189
|
+
else
|
|
190
|
+
return BigInt::fromUint64(static_cast<uint64_t>(v));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// Runs a call into C. An exception escaping it (a C++ implementation that
|
|
194
|
+
/// did not catch) ends the process here instead of unwinding through Lucent
|
|
195
|
+
/// and JSI frames that expect none.
|
|
196
|
+
template <class F>
|
|
197
|
+
decltype(auto) call(F&& f) noexcept {
|
|
198
|
+
return f();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/// A string argument: NUL-terminated UTF-8 (TypeError if it holds NUL).
|
|
202
|
+
std::string utf8(const String& s, const char* what);
|
|
203
|
+
|
|
204
|
+
/// Throws the Error a failed call reports: C's message (copied now; the
|
|
205
|
+
/// function's name when it has none) and its code.
|
|
206
|
+
[[noreturn]] void fail(const char* function, const char* message);
|
|
207
|
+
[[noreturn]] void fail(const char* function, const char* message, long long code);
|
|
208
|
+
|
|
209
|
+
} // namespace ext
|
|
210
|
+
} // namespace lucent
|
|
@@ -19,10 +19,16 @@
|
|
|
19
19
|
|
|
20
20
|
namespace lucent {
|
|
21
21
|
|
|
22
|
-
/// `this` as a strong reference, inside a member function.
|
|
22
|
+
/// `this` as a strong reference, inside a member function. An object a
|
|
23
|
+
/// native object owns (a class extending an iOS class declares
|
|
24
|
+
/// `lucentSelf`) is referenced through that native object.
|
|
23
25
|
template <class T>
|
|
24
26
|
Ref<T> selfRef(T* p) {
|
|
25
|
-
|
|
27
|
+
if constexpr (requires { lucentSelf(p); }) {
|
|
28
|
+
return lucentSelf(p);
|
|
29
|
+
} else {
|
|
30
|
+
return std::static_pointer_cast<T>(static_cast<Object*>(p)->shared_from_this());
|
|
31
|
+
}
|
|
26
32
|
}
|
|
27
33
|
template <class T>
|
|
28
34
|
Ref<T> selfRef(const T* p) {
|
|
@@ -217,9 +223,10 @@ String toJsString(const std::tuple<Ts...>& t) {
|
|
|
217
223
|
t);
|
|
218
224
|
return out;
|
|
219
225
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
226
|
+
/// Tuples are values in Lucent: they compare element by element.
|
|
227
|
+
template <class... As, class... Bs>
|
|
228
|
+
requires(sizeof...(As) == sizeof...(Bs))
|
|
229
|
+
bool strictEquals(const std::tuple<As...>& a, const std::tuple<Bs...>& b) {
|
|
223
230
|
return std::apply([&](const auto&... x) { return std::apply([&](const auto&... y) { return (strictEquals(x, y) && ...); }, b); }, a);
|
|
224
231
|
}
|
|
225
232
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Lucent runtime — a toolkit body's list, by key.
|
|
2
|
+
//
|
|
3
|
+
// A SwiftUI or Compose body shows an array its setup computes item by
|
|
4
|
+
// item, each told apart by a key (a string or a number) its key function
|
|
5
|
+
// computes. The setup's effect keeps the latest items by key, so an
|
|
6
|
+
// action a callback of an item calls with the item's key runs with the
|
|
7
|
+
// item itself, or not at all once the item is gone. Keys are unique:
|
|
8
|
+
// two items with one key, or a NaN key, are an error.
|
|
9
|
+
#pragma once
|
|
10
|
+
|
|
11
|
+
#include <cmath>
|
|
12
|
+
#include <map>
|
|
13
|
+
#include <type_traits>
|
|
14
|
+
|
|
15
|
+
#include "core.h"
|
|
16
|
+
#include "jserror.h"
|
|
17
|
+
#include "jsstring.h"
|
|
18
|
+
#include "number.h"
|
|
19
|
+
|
|
20
|
+
namespace lucent::ui {
|
|
21
|
+
|
|
22
|
+
template <class K, class T>
|
|
23
|
+
class Items {
|
|
24
|
+
public:
|
|
25
|
+
/// Forgets the items: a new array is being keyed.
|
|
26
|
+
void clear() { byKey_.clear(); }
|
|
27
|
+
|
|
28
|
+
/// Keeps `item` under `key`, which no other item has.
|
|
29
|
+
void add(const K& key, const T& item) {
|
|
30
|
+
if constexpr (std::is_same_v<K, double>) {
|
|
31
|
+
if (std::isnan(key)) throwError(String::fromLatin1("Error"), String::fromLatin1("a list's key is NaN"));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!byKey_.emplace(key, item).second)
|
|
35
|
+
throwError(String::fromLatin1("Error"), String::fromLatin1("two items of a list have the key ") + text(key));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// The item with `key`, if there is one still.
|
|
39
|
+
Opt<T> find(const K& key) const {
|
|
40
|
+
auto it = byKey_.find(key);
|
|
41
|
+
|
|
42
|
+
return it == byKey_.end() ? Opt<T>() : Opt<T>(it->second);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private:
|
|
46
|
+
std::map<K, T> byKey_;
|
|
47
|
+
|
|
48
|
+
static String text(const String& key) { return key; }
|
|
49
|
+
static String text(double key) { return numberToString(key); }
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
} // namespace lucent::ui
|
|
@@ -48,4 +48,13 @@ String errorToString(const Error& e) {
|
|
|
48
48
|
return e->name + String::fromLatin1(": ") + e->message;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
std::exception_ptr suppressedError(std::exception_ptr error, std::exception_ptr suppressed) {
|
|
52
|
+
auto e = std::make_shared<SuppressedErrorObject>();
|
|
53
|
+
e->name = String::fromLatin1("SuppressedError");
|
|
54
|
+
e->message = String::fromLatin1("An error was suppressed during disposal.");
|
|
55
|
+
e->error = currentError(error);
|
|
56
|
+
e->suppressed = currentError(suppressed);
|
|
57
|
+
return std::make_exception_ptr(Exception(e));
|
|
58
|
+
}
|
|
59
|
+
|
|
51
60
|
} // namespace lucent
|