@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,75 @@
1
+ // Lucent runtime — resources: what Lucent code opens and must close once.
2
+ //
3
+ // A Resource holds the release of something native (a camera session, a
4
+ // file, a subscription). It is open until it closes, closing while its
5
+ // release runs, then closed. It closes at the first of an explicit
6
+ // close(), the disposal of the scope it belongs to, or its last reference
7
+ // going: finalization is a backstop, never the only way. Its release runs
8
+ // exactly once, on the context the resource requires.
9
+ #pragma once
10
+
11
+ #include <atomic>
12
+ #include <cstdint>
13
+ #include <functional>
14
+ #include <memory>
15
+
16
+ #include "execution.h"
17
+ #include "jserror.h"
18
+ #include "jsstring.h"
19
+ #include "scope.h"
20
+
21
+ namespace lucent {
22
+
23
+ class Resource : public std::enable_shared_from_this<Resource> {
24
+ public:
25
+ enum class State : uint8_t { Open, Closing, Closed };
26
+
27
+ /// Opens a resource named `what` in its errors ("Camera is closed").
28
+ /// `release` runs once, when it closes: on `releaseOn`'s thread if given
29
+ /// (posted there from any other), else wherever it closes. With a
30
+ /// `scope`, disposing the scope closes the resource; the scope refers to
31
+ /// it weakly, so it never keeps the resource alive. Under a scope that is
32
+ /// no longer active, the resource is closed at once.
33
+ static std::shared_ptr<Resource> open(String what, std::function<void()> release, ExecutionContext* releaseOn = nullptr,
34
+ const std::shared_ptr<Scope>& scope = nullptr);
35
+
36
+ /// Closes it if still open (the backstop). An error from its release is
37
+ /// reported (reportUncaught).
38
+ ~Resource();
39
+
40
+ Resource(const Resource&) = delete;
41
+ Resource& operator=(const Resource&) = delete;
42
+
43
+ State state() const { return state_.load(std::memory_order_acquire); }
44
+
45
+ bool isOpen() const { return state() == State::Open; }
46
+
47
+ /// What every ordinary use checks first: throws InvalidStateError
48
+ /// "<what> is closed" unless the resource is open.
49
+ void check() const;
50
+
51
+ /// Any thread, idempotent. The first call closes the resource and
52
+ /// returns true; any other returns false at once, without waiting for a
53
+ /// release in progress. The resource is closed once its release has run,
54
+ /// even if the release throws: run here, its error reaches the caller;
55
+ /// posted, it is reported.
56
+ bool close();
57
+
58
+ private:
59
+ Resource(String what, std::function<void()> release, ExecutionContext* releaseOn);
60
+
61
+ /// Forgets the scope's cleanup, if it has not run.
62
+ void unlink();
63
+
64
+ const String what_;
65
+ ExecutionContext* const releaseOn_;
66
+
67
+ /// Taken by whichever closes the resource first.
68
+ std::function<void()> release_;
69
+ std::atomic<State> state_{State::Open};
70
+
71
+ std::weak_ptr<Scope> scope_;
72
+ std::atomic<Scope::CleanupId> cleanup_{0};
73
+ };
74
+
75
+ } // namespace lucent
@@ -1,153 +1,67 @@
1
1
  #include "scheduler.h"
2
2
 
3
- #include <cstdio>
4
- #include <exception>
5
- #include <string>
6
-
7
- #if defined(__ANDROID__)
8
- #include <android/log.h>
9
- #elif defined(__APPLE__)
10
- #include <os/log.h>
11
- #endif
12
-
13
3
  namespace lucent {
14
4
 
15
- namespace {
16
- /// Where each platform shows an app's errors: logcat, the unified log, and
17
- /// stderr (host runs; Android and release iOS apps drop it).
18
- void writeError(const std::string& message) {
19
- #if defined(__ANDROID__)
20
- __android_log_write(ANDROID_LOG_ERROR, "Lucent", message.c_str());
21
- #elif defined(__APPLE__)
22
- os_log_error(OS_LOG_DEFAULT, "%{public}s", message.c_str());
23
- #endif
24
- std::fprintf(stderr, "%s\n", message.c_str());
25
- }
26
- } // namespace
27
-
28
- void logError(const char* message) { writeError(message); }
5
+ Scheduler::Scheduler() : ExecutionContext(&microtaskCount_) {
6
+ // The module context starts with the first module: tracing asked for by
7
+ // the environment (LUCENT_TRACE) starts with it.
8
+ trace::startFromEnvironment();
29
9
 
30
- void reportUncaught(std::exception_ptr e, const char* where) {
31
- try {
32
- std::rethrow_exception(e);
33
- } catch (const std::exception& x) {
34
- writeError(std::string("[lucent] uncaught exception in ") + where + ": " + x.what());
35
- } catch (...) {
36
- writeError(std::string("[lucent] uncaught exception in ") + where);
37
- }
10
+ worker_ = WorkerThread::start([this](Job& job) { runTurn(job); });
38
11
  }
39
12
 
40
13
  namespace {
41
- void runGuarded(const Scheduler::Job& job) {
42
- try {
43
- job();
44
- } catch (...) {
45
- reportUncaught(std::current_exception(), "job");
46
- }
47
- }
14
+ /// The scheduler's owner, leaked on purpose (jobs may still reference the
15
+ /// scheduler during static destruction at process exit), and kept
16
+ /// reachable: a leak checker (LeakSanitizer) reports what it cannot reach.
17
+ std::shared_ptr<Scheduler>* keptScheduler = nullptr;
48
18
  } // namespace
49
19
 
50
-
51
- Scheduler::Scheduler() {
52
- thread_ = std::thread([this] { run(); });
53
- threadId_ = thread_.get_id();
20
+ Scheduler& Scheduler::instance() {
21
+ // The scheduler itself, one load away on the call path.
22
+ static Scheduler* s = [] {
23
+ keptScheduler = new std::shared_ptr<Scheduler>(new Scheduler());
24
+ (*keptScheduler)->makeRoot();
25
+ return keptScheduler->get();
26
+ }();
27
+ return *s;
54
28
  }
55
29
 
56
- Scheduler::~Scheduler() {
57
- {
58
- std::lock_guard<std::mutex> g(queueMutex_);
59
- stopping_ = true;
30
+ void Scheduler::runTurn(Job& job) {
31
+ if (trace::enabled()) [[unlikely]] {
32
+ LucentScope::lockTraced(lock_);
33
+ } else {
34
+ lock_.lock();
60
35
  }
61
- cv_.notify_all();
62
- if (thread_.joinable()) thread_.join();
63
- }
64
36
 
65
- void Scheduler::post(Job job) {
66
- {
67
- std::lock_guard<std::mutex> g(queueMutex_);
68
- jobs_.push_back(std::move(job));
69
- }
70
- cv_.notify_one();
71
- }
37
+ std::lock_guard<LucentLock> lucent(lock_, std::adopt_lock);
72
38
 
73
- void Scheduler::postDelayed(double ms, Job job) {
74
- if (!(ms > 0)) ms = 0;
75
- auto at = std::chrono::steady_clock::now() + std::chrono::microseconds(static_cast<int64_t>(ms * 1000));
76
39
  {
77
- std::lock_guard<std::mutex> g(queueMutex_);
78
- timers_.push(Timer{at, timerSeq_++, std::move(job)});
40
+ Job local = std::move(job);
41
+ detail::runGuarded(local, "job");
79
42
  }
80
- cv_.notify_one();
81
- }
82
43
 
83
- void Scheduler::enqueueMicrotask(Job job) {
84
- microtasks_.push_back(std::move(job));
85
- pendingMicrotasks_++;
44
+ drainMicrotasks();
86
45
  }
87
46
 
88
- void Scheduler::drainMicrotasks() {
89
- while (!microtasks_.empty()) {
90
- Job job = std::move(microtasks_.front());
91
- microtasks_.pop_front();
92
- pendingMicrotasks_--;
93
- runGuarded(job);
47
+ void LucentScope::lockTraced(LucentLock& lock) {
48
+ // Already held here (a nested call): no wait to record.
49
+ if (lock.heldByCurrentThread()) {
50
+ lock.lock();
51
+ return;
94
52
  }
95
- }
96
-
97
- size_t Scheduler::pendingWork() {
98
- std::lock_guard<std::mutex> g(queueMutex_);
99
- return jobs_.size() + timers_.size() + running_;
100
- }
101
53
 
102
- bool Scheduler::waitIdle(double timeoutMs) {
103
- std::unique_lock<std::mutex> g(queueMutex_);
104
- return idleCv_.wait_for(g, std::chrono::microseconds(static_cast<int64_t>(timeoutMs * 1000)),
105
- [this] { return jobs_.empty() && timers_.empty() && running_ == 0; });
106
- }
107
-
108
- void Scheduler::run() {
109
- std::unique_lock<std::mutex> g(queueMutex_);
110
- for (;;) {
111
- if (stopping_) return;
112
- auto now = std::chrono::steady_clock::now();
113
- while (!timers_.empty() && timers_.top().at <= now) {
114
- jobs_.push_back(std::move(const_cast<Timer&>(timers_.top()).job));
115
- timers_.pop();
116
- }
117
- if (!jobs_.empty()) {
118
- Job job = std::move(jobs_.front());
119
- jobs_.pop_front();
120
- running_++;
121
- g.unlock();
122
- {
123
- std::lock_guard<LucentLock> lucent(lock_);
124
- runGuarded(job);
125
- drainMicrotasks();
126
- }
127
- g.lock();
128
- running_--;
129
- if (jobs_.empty() && timers_.empty() && running_ == 0) idleCv_.notify_all();
130
- continue;
131
- }
132
- if (timers_.empty()) {
133
- cv_.wait(g);
134
- } else {
135
- // A copy: wait_until reads the deadline after unlocking, when a
136
- // postDelayed may have reallocated the heap.
137
- const auto deadline = timers_.top().at;
138
- cv_.wait_until(g, deadline);
139
- }
140
- }
54
+ trace::Mark wait = trace::begin(trace::Category::Lock, "lucent-lock");
55
+ lock.lock();
56
+ trace::end(wait, trace::Category::Lock, "lucent-lock", {.parent = trace::currentId()});
141
57
  }
142
58
 
143
59
  void LucentScope::handOffMicrotasks() {
144
60
  Scheduler& s = Scheduler::instance();
145
- if (s.onLucentThread()) {
146
- s.drainMicrotasks();
147
- } else {
148
- // Continuations run on the Lucent thread, never on the JS thread.
149
- s.post([] {});
150
- }
61
+
62
+ // Continuations run on the Lucent thread, never on the JS thread. There,
63
+ // the turn this scope is nested in runs them after its job.
64
+ if (!s.onLucentThread()) s.post([] {});
151
65
  }
152
66
 
153
67
  } // namespace lucent
@@ -1,160 +1,179 @@
1
- // Lucent runtime — execution model.
1
+ // Lucent runtime — the legacy module context.
2
2
  //
3
- // Lucent code runs one piece at a time, like JavaScript: every entry into
4
- // Lucent code (a synchronous call from the JS thread, or a job on the Lucent
5
- // thread) holds the Lucent lock. Async functions run on the Lucent thread and
6
- // interleave only at `await`, so Lucent code never races with itself.
3
+ // Module code runs one piece at a time, like JavaScript: every entry into it
4
+ // (a synchronous call from the JS thread, or a job on the Lucent thread)
5
+ // holds the Lucent lock. Async functions run on the Lucent thread and
6
+ // interleave only at `await`, so module code never races with itself.
7
7
  //
8
- // Invariant: the Lucent thread never waits for the JS thread, so the JS
9
- // thread may always block on the Lucent lock without deadlocking.
8
+ // Invariant: code holding the Lucent lock never waits for another thread,
9
+ // so any thread may block on the lock without deadlocking. The main context
10
+ // and isolated contexts (execution.h) never take it.
10
11
  #pragma once
11
12
 
12
13
  #include <atomic>
13
- #include <chrono>
14
- #include <condition_variable>
15
14
  #include <cstdint>
16
- #include <deque>
17
- #include <exception>
18
- #include <functional>
15
+ #include <memory>
19
16
  #include <mutex>
20
- #include <queue>
21
17
  #include <thread>
22
- #include <vector>
23
18
 
24
- namespace lucent {
19
+ #include "execution.h"
20
+ #include "report.h"
21
+ #include "trace.h"
25
22
 
26
- /// The one place errors no Lucent code can catch are reported: thrown by a
27
- /// job, or by a callback the platform made.
28
- void reportUncaught(std::exception_ptr e, const char* where);
29
- /// Logs an error where the platform shows an app's errors (logcat, the
30
- /// unified log) and to stderr.
31
- void logError(const char* message);
23
+ namespace lucent {
32
24
 
33
25
  /// The Lucent lock: recursive, as a Lucent call may call back into
34
26
  /// JavaScript that calls Lucent again. Every synchronous call from
35
- /// JavaScript takes it, so the uncontended path is one compare-and-swap;
36
- /// std::recursive_mutex (a pthread mutex) cost a third of a call. Waiters
27
+ /// JavaScript takes it, so the uncontended path is one atomic increment
28
+ /// and a load; std::recursive_mutex (a pthread mutex) cost a third of a
29
+ /// call.
30
+ ///
31
+ /// It is fair: a ticket lock, serving threads in the order they asked. A
32
+ /// thread that releases it and asks again (the Lucent thread between the
33
+ /// turns of a loop that yields) queues behind the threads already waiting,
34
+ /// so a waiter gets in within one turn per thread ahead of it. Waiters
37
35
  /// block in atomic::wait (a futex, or __ulock on Apple platforms).
38
36
  class LucentLock {
39
37
  public:
40
38
  void lock() {
41
39
  std::thread::id self = std::this_thread::get_id();
40
+
42
41
  // Only this thread stores its own id, so a relaxed read that sees it is
43
42
  // exact; any other value means another owner or none.
44
43
  if (owner_.load(std::memory_order_relaxed) == self) {
45
44
  depth_++;
46
45
  return;
47
46
  }
48
- uint32_t c = kFree;
49
- if (!state_.compare_exchange_strong(c, kLocked, std::memory_order_acquire, std::memory_order_relaxed)) {
50
- if (c != kContended) c = state_.exchange(kContended, std::memory_order_acquire);
51
- while (c != kFree) {
52
- state_.wait(kContended, std::memory_order_relaxed);
53
- c = state_.exchange(kContended, std::memory_order_acquire);
54
- }
47
+
48
+ // Sequentially consistent, with unlock(): either it sees this ticket
49
+ // and wakes the waiters, or this thread sees the ticket it serves.
50
+ const uint32_t ticket = next_.fetch_add(1, std::memory_order_seq_cst);
51
+
52
+ uint32_t serving = serving_.load(std::memory_order_seq_cst);
53
+ while (serving != ticket) {
54
+ serving_.wait(serving, std::memory_order_relaxed);
55
+ serving = serving_.load(std::memory_order_seq_cst);
55
56
  }
57
+
56
58
  owner_.store(self, std::memory_order_relaxed);
57
59
  depth_ = 1;
58
60
  }
59
61
 
60
62
  bool try_lock() {
61
63
  std::thread::id self = std::this_thread::get_id();
64
+
62
65
  if (owner_.load(std::memory_order_relaxed) == self) {
63
66
  depth_++;
64
67
  return true;
65
68
  }
66
- uint32_t c = kFree;
67
- if (!state_.compare_exchange_strong(c, kLocked, std::memory_order_acquire, std::memory_order_relaxed)) return false;
69
+
70
+ // Free is no ticket handed out beyond the one served: take that one.
71
+ uint32_t serving = serving_.load(std::memory_order_seq_cst);
72
+ uint32_t expected = serving;
73
+ if (!next_.compare_exchange_strong(expected, serving + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) {
74
+ return false;
75
+ }
76
+
68
77
  owner_.store(self, std::memory_order_relaxed);
69
78
  depth_ = 1;
70
79
  return true;
71
80
  }
72
81
 
82
+ /// Whether the calling thread holds the lock.
83
+ bool heldByCurrentThread() const { return owner_.load(std::memory_order_relaxed) == std::this_thread::get_id(); }
84
+
73
85
  void unlock() {
74
86
  if (--depth_ > 0) return;
87
+
75
88
  owner_.store(std::thread::id(), std::memory_order_relaxed);
76
- if (state_.exchange(kFree, std::memory_order_release) == kContended) state_.notify_one();
89
+
90
+ // Only the owner moves `serving_`, so it may read it relaxed.
91
+ const uint32_t next = serving_.load(std::memory_order_relaxed) + 1;
92
+ serving_.store(next, std::memory_order_seq_cst);
93
+
94
+ // A ticket beyond the one now served: someone waits. Every waiter
95
+ // wakes, and the one holding `next` goes in.
96
+ if (next_.load(std::memory_order_seq_cst) != next) serving_.notify_all();
77
97
  }
78
98
 
79
99
  private:
80
- static constexpr uint32_t kFree = 0, kLocked = 1, kContended = 2;
81
- std::atomic<uint32_t> state_{kFree};
100
+ /// The next ticket to hand out, and the one that holds the lock (or may
101
+ /// take it). Equal: free.
102
+ std::atomic<uint32_t> next_{0};
103
+ std::atomic<uint32_t> serving_{0};
104
+
82
105
  std::atomic<std::thread::id> owner_{};
83
106
  unsigned depth_ = 0;
84
107
  };
85
108
 
86
- class Scheduler {
109
+ /// The legacy module context: every module's code, serialized by the Lucent
110
+ /// lock. Its turns run on the Lucent thread; synchronous calls from other
111
+ /// threads (JavaScript's, or a platform callback's) enter it by taking the
112
+ /// lock, which is what makes it current (ExecutionContext::current).
113
+ class Scheduler final : public ExecutionContext {
87
114
  public:
88
- using Job = std::function<void()>;
89
-
90
- static Scheduler& instance() {
91
- // Leaked on purpose: jobs may still reference the scheduler during
92
- // static destruction at process exit.
93
- static Scheduler* s = new Scheduler();
94
- return *s;
95
- }
115
+ static Scheduler& instance();
96
116
 
97
- /// Static, like the pending count: a call from JavaScript reaches both
117
+ /// Static, like microtasksPending(): a call from JavaScript reaches both
98
118
  /// without the scheduler's function-local static.
99
119
  static LucentLock& lock() { return lock_; }
100
120
 
101
- /// Runs `job` on the Lucent thread, holding the lock, then drains
102
- /// microtasks.
103
- void post(Job job);
104
- void postDelayed(double ms, Job job);
105
- /// Queues a promise continuation. Callers hold the lock.
106
- void enqueueMicrotask(Job job);
107
- /// Runs queued microtasks. Callers hold the lock.
108
- void drainMicrotasks();
109
- static bool hasMicrotasks() { return pendingMicrotasks_ > 0; }
110
-
111
- bool onLucentThread() const { return std::this_thread::get_id() == threadId_; }
121
+ /// Whether microtasks are queued here. Callers hold the lock.
122
+ static bool microtasksPending() { return microtaskCount_ > 0; }
123
+
124
+ bool onLucentThread() const { return worker_->isCurrent(); }
125
+
112
126
  /// Jobs queued or running, and timers pending (for tests and shutdown).
113
- size_t pendingWork();
127
+ size_t pendingWork() { return worker_->pendingWork(); }
128
+
114
129
  /// Blocks until no jobs or timers are pending, or the timeout elapses.
115
- bool waitIdle(double timeoutMs);
130
+ bool waitIdle(double timeoutMs) { return worker_->waitIdle(timeoutMs); }
116
131
 
117
132
  private:
118
133
  Scheduler();
119
- ~Scheduler();
120
- void run();
121
134
 
122
- struct Timer {
123
- std::chrono::steady_clock::time_point at;
124
- uint64_t seq;
125
- Job job;
126
- bool operator>(const Timer& o) const { return at != o.at ? at > o.at : seq > o.seq; }
127
- };
135
+ bool dispatch(Job job) override { return worker_->post(std::move(job)); }
136
+ bool dispatchDelayed(double ms, Job job) override { return worker_->postDelayed(ms, std::move(job)); }
137
+ bool onExecutor() const override { return worker_->isCurrent(); }
138
+
139
+ /// Holds the lock for the job and the microtasks after it.
140
+ void runTurn(Job& job) override;
128
141
 
129
142
  static inline LucentLock lock_;
130
- std::mutex queueMutex_;
131
- std::condition_variable cv_;
132
- std::condition_variable idleCv_;
133
- std::deque<Job> jobs_;
134
- std::priority_queue<Timer, std::vector<Timer>, std::greater<Timer>> timers_;
135
- uint64_t timerSeq_ = 0;
136
- size_t running_ = 0;
137
- std::deque<Job> microtasks_;
138
- static inline size_t pendingMicrotasks_ = 0; // microtasks_.size(), under the lock
139
- bool stopping_ = false;
140
- std::thread thread_;
141
- std::thread::id threadId_;
143
+ /// Guarded by the lock.
144
+ static inline size_t microtaskCount_ = 0;
145
+ std::shared_ptr<WorkerThread> worker_;
142
146
  };
143
147
 
144
- /// Entered for every call from JavaScript into Lucent code. Holds the Lucent
145
- /// lock; on exit, hands pending microtasks to the Lucent thread.
148
+ /// Entered for every call from JavaScript into Lucent code, and by platform
149
+ /// callbacks into module code (callNow): enters the legacy module context
150
+ /// by holding the Lucent lock. On exit off the Lucent thread, hands pending
151
+ /// microtasks to it; on the Lucent thread, the enclosing turn runs them once
152
+ /// its stack is empty.
146
153
  class LucentScope {
147
154
  public:
148
- LucentScope() : guard_(Scheduler::lock()) {}
155
+ LucentScope() {
156
+ if (trace::enabled()) [[unlikely]] {
157
+ lockTraced(Scheduler::lock());
158
+ } else {
159
+ Scheduler::lock().lock();
160
+ }
161
+ }
162
+
149
163
  ~LucentScope() {
150
- if (Scheduler::hasMicrotasks()) handOffMicrotasks();
164
+ if (Scheduler::microtasksPending()) handOffMicrotasks();
165
+
166
+ Scheduler::lock().unlock();
151
167
  }
168
+
152
169
  LucentScope(const LucentScope&) = delete;
153
170
  LucentScope& operator=(const LucentScope&) = delete;
154
171
 
172
+ /// Takes `lock`, recording the wait as a span (a call's, or a turn's).
173
+ static void lockTraced(LucentLock& lock);
174
+
155
175
  private:
156
176
  static void handOffMicrotasks();
157
- std::unique_lock<LucentLock> guard_;
158
177
  };
159
178
 
160
179
  } // namespace lucent