@lucent-lang/lucent 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-B555m0wU.js} +10 -6
  4. package/dist/{build-CAIrRSYA.js → build-UZ5Zm5m6.js} +1 -1
  5. package/dist/{build-check-QozuNJm5.js → build-check-Dmf8_yiw.js} +28 -13
  6. package/dist/{check-1SCmYlqA.js → check-D-H9WLG4.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-C8iF5-6T.js} +2 -2
  11. package/dist/{dev-BYWMP7oC.js → dev-B3lSx5CF.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-BBY0df_2.js +700 -0
  18. package/dist/project-BLhWwnns.js +342 -0
  19. package/dist/sdk-coverage-DFGiDOzR.js +113 -0
  20. package/dist/sdk-diff-DpPYWzrl.js +109 -0
  21. package/dist/sdk-lock-QOxEFD1o.js +38 -0
  22. package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-DX6ztlPN.js} +2 -2
  23. package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-BShBL1LL.js} +2 -2
  24. package/dist/{sdk-show-CkrdlqlP.js → sdk-show-B3bI-0M7.js} +5 -4
  25. package/dist/sdk-usage-ljX5bTuZ.js +95 -0
  26. package/dist/src-DlICbK91.js +39682 -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 +2 -1
  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 +77 -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
@@ -1,7 +1,20 @@
1
- // Lucent runtime — `===` and SameValueZero.
1
+ // Lucent runtime — `===`, loose `==` and SameValueZero.
2
+ //
3
+ // Each type's `strictEquals` (and `toJsString`) is declared at namespace
4
+ // scope, next to the type (array.h, map.h, bytes.h, …), never as a hidden
5
+ // friend: generated code calls `lucent::strictEquals` qualified, and
6
+ // qualified lookup does not see hidden friends.
7
+ //
8
+ // Operands may have different C++ types: TypeScript allows comparing types
9
+ // that overlap, and a generic instantiated with an object type still
10
+ // compares it with `undefined` or `0`. Optionals and unions compare what
11
+ // they hold; class instances compare as objects; any other pair of
12
+ // different types holds no common JavaScript value, so it is never equal.
2
13
  #pragma once
3
14
 
4
15
  #include <cmath>
16
+ #include <tuple>
17
+ #include <type_traits>
5
18
  #include <variant>
6
19
 
7
20
  #include "core.h"
@@ -9,6 +22,43 @@
9
22
 
10
23
  namespace lucent {
11
24
 
25
+ namespace detail {
26
+
27
+ template <class T>
28
+ struct TupleArity : std::integral_constant<int, -1> {};
29
+ template <class... Ts>
30
+ struct TupleArity<std::tuple<Ts...>> : std::integral_constant<int, sizeof...(Ts)> {};
31
+
32
+ /// Types whose values another C++ type can also hold: an optional's or a
33
+ /// union's (they compare what they hold).
34
+ template <class T>
35
+ inline constexpr bool holdsOthers = IsOpt<T>::value || IsVariant<T>::value;
36
+
37
+ /// Numbers of two C++ types compare as numbers (by conversion), not as
38
+ /// different types.
39
+ template <class T>
40
+ inline constexpr bool numeric = std::is_arithmetic_v<T> && !std::is_same_v<T, bool>;
41
+
42
+ } // namespace detail
43
+
44
+ /// No JavaScript value is both an A and a B. Excluded, because they may be
45
+ /// one value: one type (its own overload), optionals and unions (they
46
+ /// compare what they hold), two class instances (one object seen through
47
+ /// a base class), two tuples of one length (element by element), and two
48
+ /// numeric types (one number).
49
+ template <class A, class B>
50
+ concept Disjoint = !std::is_same_v<A, B> && !detail::holdsOthers<A> && !detail::holdsOthers<B> &&
51
+ !(IsRef<A>::value && IsRef<B>::value) &&
52
+ !(detail::TupleArity<A>::value >= 0 && detail::TupleArity<A>::value == detail::TupleArity<B>::value) &&
53
+ !(detail::numeric<A> && detail::numeric<B>);
54
+
55
+ /// A number and a bigint, null and undefined, an array and `0`: never `===`.
56
+ template <class A, class B>
57
+ requires Disjoint<A, B>
58
+ constexpr bool strictEquals(const A&, const B&) {
59
+ return false;
60
+ }
61
+
12
62
  inline bool strictEquals(double a, double b) { return a == b; }
13
63
  inline bool strictEquals(bool a, bool b) { return a == b; }
14
64
  inline bool strictEquals(const String& a, const String& b) { return a == b; }
@@ -20,10 +70,10 @@ bool strictEquals(const Ref<A>& a, const Ref<B>& b) {
20
70
  return static_cast<const void*>(a.get()) == static_cast<const void*>(b.get());
21
71
  }
22
72
 
23
- template <class T>
24
- bool strictEquals(const Opt<T>& a, const Opt<T>& b) {
25
- if (a.state() != b.state()) return false;
26
- return !a.has() || strictEquals(a.get(), b.get());
73
+ template <class A, class B>
74
+ bool strictEquals(const Opt<A>& a, const Opt<B>& b) {
75
+ if (a.has() && b.has()) return strictEquals(a.get(), b.get());
76
+ return a.isUndefined() == b.isUndefined() && a.isNull() == b.isNull();
27
77
  }
28
78
  template <class T>
29
79
  bool strictEquals(const Opt<T>& a, Undefined) {
@@ -41,47 +91,76 @@ template <class T>
41
91
  bool strictEquals(Null, const Opt<T>& a) {
42
92
  return a.isNull();
43
93
  }
44
- template <class T, class U, std::enable_if_t<!IsOpt<U>::value && !std::is_same_v<U, Undefined> && !std::is_same_v<U, Null>, int> = 0>
94
+ template <class T, class U>
95
+ requires(!IsOpt<U>::value && !IsAbsent<U>)
45
96
  bool strictEquals(const Opt<T>& a, const U& b) {
46
97
  return a.has() && strictEquals(a.get(), b);
47
98
  }
48
- template <class T, class U, std::enable_if_t<!IsOpt<U>::value && !std::is_same_v<U, Undefined> && !std::is_same_v<U, Null>, int> = 0>
99
+ template <class T, class U>
100
+ requires(!IsOpt<U>::value && !IsAbsent<U>)
49
101
  bool strictEquals(const U& b, const Opt<T>& a) {
50
102
  return a.has() && strictEquals(b, a.get());
51
103
  }
52
104
 
53
- template <class... Ts>
54
- bool strictEquals(const std::variant<Ts...>& a, const std::variant<Ts...>& b) {
55
- if (a.index() != b.index()) return false;
56
- return std::visit(
57
- [&](const auto& x) {
58
- using X = std::decay_t<decltype(x)>;
59
- return strictEquals(x, std::get<X>(b));
60
- },
61
- a);
62
- }
63
- template <class... Ts, class U, std::enable_if_t<!IsOpt<U>::value, int> = 0>
105
+ /// Unions compare the members they hold, so members of different types
106
+ /// meet the rules above (`number | string` against `string | boolean`).
107
+ template <class... As, class... Bs>
108
+ bool strictEquals(const std::variant<As...>& a, const std::variant<Bs...>& b) {
109
+ return std::visit([](const auto& x, const auto& y) { return strictEquals(x, y); }, a, b);
110
+ }
111
+ template <class... Ts, class U>
112
+ requires(!IsOpt<U>::value && !IsVariant<U>::value)
64
113
  bool strictEquals(const std::variant<Ts...>& a, const U& b) {
65
- if (const U* p = std::get_if<U>(&a)) return strictEquals(*p, b);
66
- return false;
114
+ return std::visit([&](const auto& x) { return strictEquals(x, b); }, a);
115
+ }
116
+ template <class... Ts, class U>
117
+ requires(!IsOpt<U>::value && !IsVariant<U>::value)
118
+ bool strictEquals(const U& a, const std::variant<Ts...>& b) {
119
+ return std::visit([&](const auto& y) { return strictEquals(a, y); }, b);
67
120
  }
68
121
 
69
- /// Loose `==` only differs from `===` for null/undefined in Lucent's typed
70
- /// world: `x == null` is true for both absent states.
122
+ /// `x == null`: true for undefined and null, false for any present value.
123
+ template <class T>
124
+ bool looseEqualsNull(const T&) {
125
+ return false;
126
+ }
127
+ inline bool looseEqualsNull(Undefined) { return true; }
128
+ inline bool looseEqualsNull(Null) { return true; }
71
129
  template <class T>
72
130
  bool looseEqualsNull(const Opt<T>& a) {
73
131
  return !a.has();
74
132
  }
75
133
 
76
- inline bool sameValueZero(double a, double b) { return a == b || (std::isnan(a) && std::isnan(b)); }
134
+ /// Loose `==` differs from `===` only for null and undefined in Lucent's
135
+ /// typed world (the compiler refuses the comparisons JavaScript converts).
77
136
  template <class A, class B>
78
- bool sameValueZero(const A& a, const B& b) {
79
- return strictEquals(a, b);
137
+ bool looseEquals(const A& a, const B& b) {
138
+ return strictEquals(a, b) || (looseEqualsNull(a) && looseEqualsNull(b));
139
+ }
140
+
141
+ namespace detail {
142
+
143
+ /// Whether `v` holds NaN, where SameValueZero differs from `===`.
144
+ template <class T>
145
+ bool holdsNaN(const T& v) {
146
+ if constexpr (std::is_floating_point_v<T>) return std::isnan(v);
147
+ return false;
80
148
  }
81
149
  template <class T>
82
- bool sameValueZero(const Opt<T>& a, const Opt<T>& b) {
83
- if (a.state() != b.state()) return false;
84
- return !a.has() || sameValueZero(a.get(), b.get());
150
+ bool holdsNaN(const Opt<T>& v) {
151
+ return v.has() && holdsNaN(v.get());
152
+ }
153
+ template <class... Ts>
154
+ bool holdsNaN(const std::variant<Ts...>& v) {
155
+ return std::visit([](const auto& x) { return holdsNaN(x); }, v);
156
+ }
157
+
158
+ } // namespace detail
159
+
160
+ /// SameValueZero (includes, Map and Set keys): `===`, but NaN is NaN.
161
+ template <class A, class B>
162
+ bool sameValueZero(const A& a, const B& b) {
163
+ return strictEquals(a, b) || (detail::holdsNaN(a) && detail::holdsNaN(b));
85
164
  }
86
165
 
87
166
  } // namespace lucent
@@ -0,0 +1,416 @@
1
+ #include "execution.h"
2
+
3
+ #include <mutex>
4
+ #include <stdexcept>
5
+
6
+ // The legacy module context is entered by its lock rather than by the
7
+ // thread-local below, so current() asks the lock first.
8
+ #include "scheduler.h"
9
+ #include "trace.h"
10
+
11
+ namespace lucent {
12
+
13
+ namespace {
14
+
15
+ // Constant-initialized: safe to use during static initialization.
16
+ std::atomic<ContextId> nextContextId{1};
17
+
18
+ // The context this thread entered, other than the legacy module context.
19
+ thread_local ExecutionContext* entered = nullptr;
20
+
21
+ /// Runs the main context's timers: the platform's main loop has none that
22
+ /// every platform shares, and they must not wait behind module code.
23
+ WorkerThread& clock() {
24
+ static auto* thread = new std::shared_ptr<WorkerThread>(WorkerThread::start([](Job& job) { detail::runGuarded(job, "job"); }));
25
+ return **thread;
26
+ }
27
+
28
+ class MainContext final : public ExecutionContext {
29
+ public:
30
+ MainContext() = default;
31
+
32
+ private:
33
+ bool dispatch(Job job) override {
34
+ postToMain([this, job = std::move(job)]() mutable { runTurn(job); });
35
+ return true;
36
+ }
37
+
38
+ bool dispatchDelayed(double ms, Job job) override {
39
+ return clock().postDelayed(ms, [this, job = std::move(job)]() mutable { post(std::move(job)); });
40
+ }
41
+
42
+ bool onExecutor() const override { return onMainThread(); }
43
+ };
44
+
45
+ } // namespace
46
+
47
+ void detail::runGuarded(Job& job, const char* where) {
48
+ try {
49
+ job();
50
+ } catch (...) {
51
+ reportUncaught(std::current_exception(), where);
52
+ }
53
+ }
54
+
55
+ void detail::runOn(ExecutionContext* on, Job job) {
56
+ if (on && !on->onExecutor()) {
57
+ // Kept here as well: a context that refuses the job destroys it.
58
+ auto kept = std::make_shared<Job>(std::move(job));
59
+ if (on->post([kept] { (*kept)(); })) return;
60
+
61
+ job = std::move(*kept);
62
+ }
63
+
64
+ job();
65
+ }
66
+
67
+ // --- WorkerThread ---------------------------------------------------------------
68
+
69
+ std::shared_ptr<WorkerThread> WorkerThread::start(Run run) {
70
+ std::shared_ptr<WorkerThread> worker(new WorkerThread(std::move(run)));
71
+
72
+ std::thread thread([worker] { worker->loop(); });
73
+ worker->id_ = thread.get_id();
74
+ thread.detach();
75
+
76
+ return worker;
77
+ }
78
+
79
+ bool WorkerThread::post(Job job) {
80
+ {
81
+ std::lock_guard<std::mutex> g(m_);
82
+
83
+ if (stopping_) return false;
84
+
85
+ jobs_.push_back(std::move(job));
86
+ }
87
+
88
+ cv_.notify_one();
89
+ return true;
90
+ }
91
+
92
+ bool WorkerThread::postDelayed(double ms, Job job) {
93
+ if (!(ms > 0)) ms = 0;
94
+ auto at = std::chrono::steady_clock::now() + std::chrono::microseconds(static_cast<int64_t>(ms * 1000));
95
+
96
+ {
97
+ std::lock_guard<std::mutex> g(m_);
98
+
99
+ if (stopping_) return false;
100
+
101
+ timers_.push(Timer{at, timerSeq_++, std::move(job)});
102
+ }
103
+
104
+ cv_.notify_one();
105
+ return true;
106
+ }
107
+
108
+ size_t WorkerThread::pendingWork() {
109
+ std::lock_guard<std::mutex> g(m_);
110
+ return jobs_.size() + timers_.size() + running_;
111
+ }
112
+
113
+ bool WorkerThread::waitIdle(double timeoutMs) {
114
+ std::unique_lock<std::mutex> g(m_);
115
+ return idleCv_.wait_for(g, std::chrono::microseconds(static_cast<int64_t>(timeoutMs * 1000)),
116
+ [this] { return stopped_ || (jobs_.empty() && timers_.empty() && running_ == 0); });
117
+ }
118
+
119
+ void WorkerThread::stop(Job last) {
120
+ {
121
+ std::lock_guard<std::mutex> g(m_);
122
+
123
+ if (stopping_) return;
124
+
125
+ stopping_ = true;
126
+ last_ = std::move(last);
127
+ }
128
+
129
+ cv_.notify_all();
130
+ }
131
+
132
+ bool WorkerThread::waitStopped(double timeoutMs) {
133
+ std::unique_lock<std::mutex> g(m_);
134
+ return idleCv_.wait_for(g, std::chrono::microseconds(static_cast<int64_t>(timeoutMs * 1000)), [this] { return stopped_; });
135
+ }
136
+
137
+ void WorkerThread::loop() {
138
+ std::unique_lock<std::mutex> g(m_);
139
+
140
+ while (!stopping_) {
141
+ auto now = std::chrono::steady_clock::now();
142
+ while (!timers_.empty() && timers_.top().at <= now) {
143
+ jobs_.push_back(std::move(const_cast<Timer&>(timers_.top()).job));
144
+ timers_.pop();
145
+ }
146
+
147
+ if (!jobs_.empty()) {
148
+ Job job = std::move(jobs_.front());
149
+ jobs_.pop_front();
150
+ running_++;
151
+ g.unlock();
152
+
153
+ run_(job);
154
+ // Unlocked: what the job captured may post again.
155
+ job = nullptr;
156
+
157
+ g.lock();
158
+ running_--;
159
+ if (jobs_.empty() && timers_.empty() && running_ == 0) idleCv_.notify_all();
160
+ continue;
161
+ }
162
+
163
+ if (timers_.empty()) {
164
+ cv_.wait(g);
165
+ } else {
166
+ // A copy: wait_until reads the deadline after unlocking, when a
167
+ // postDelayed may have reallocated the heap.
168
+ const auto deadline = timers_.top().at;
169
+ cv_.wait_until(g, deadline);
170
+ }
171
+ }
172
+
173
+ Job last = std::move(last_);
174
+ std::deque<Job> dropped;
175
+ dropped.swap(jobs_);
176
+ auto timers = std::move(timers_);
177
+ g.unlock();
178
+
179
+ if (last) detail::runGuarded(last, "job");
180
+
181
+ // Released here, on this thread, like the jobs that ran.
182
+ last = nullptr;
183
+ dropped.clear();
184
+ timers = {};
185
+
186
+ g.lock();
187
+ stopped_ = true;
188
+ g.unlock();
189
+ idleCv_.notify_all();
190
+ }
191
+
192
+ // --- ExecutionContext -----------------------------------------------------------
193
+
194
+ ExecutionContext::ExecutionContext(size_t* microtaskCount)
195
+ : id_(nextContextId.fetch_add(1, std::memory_order_relaxed)),
196
+ pendingMicrotasks_(microtaskCount ? microtaskCount : &ownMicrotasks_) {}
197
+
198
+ ExecutionContext* ExecutionContext::current() {
199
+ if (Scheduler::lock().heldByCurrentThread()) return &Scheduler::instance();
200
+
201
+ return entered;
202
+ }
203
+
204
+ ContextRef ExecutionContext::currentRef() {
205
+ if (Scheduler::lock().heldByCurrentThread()) return nullptr;
206
+
207
+ ExecutionContext* context = entered;
208
+ return context ? context->shared_from_this() : nullptr;
209
+ }
210
+
211
+ ExecutionContext& ExecutionContext::legacy() { return Scheduler::instance(); }
212
+
213
+ namespace {
214
+
215
+ struct ModuleScope {
216
+ std::mutex m;
217
+ std::shared_ptr<Scope> scope;
218
+ };
219
+
220
+ /// Never destroyed: a scope released during static destruction could need
221
+ /// contexts already gone.
222
+ ModuleScope& moduleScopeState() {
223
+ static auto* state = new ModuleScope();
224
+ return *state;
225
+ }
226
+
227
+ } // namespace
228
+
229
+ std::shared_ptr<Scope> moduleScope() {
230
+ ModuleScope& state = moduleScopeState();
231
+
232
+ {
233
+ std::lock_guard<std::mutex> g(state.m);
234
+ if (state.scope) return state.scope;
235
+ }
236
+
237
+ return ExecutionContext::legacy().root();
238
+ }
239
+
240
+ void setModuleScope(std::shared_ptr<Scope> scope) {
241
+ ModuleScope& state = moduleScopeState();
242
+ std::lock_guard<std::mutex> g(state.m);
243
+
244
+ state.scope = std::move(scope);
245
+ }
246
+
247
+ ExecutionContext& ExecutionContext::main() {
248
+ // Leaked, like the main loop it stands for.
249
+ static auto* context = [] {
250
+ auto* kept = new std::shared_ptr<MainContext>(std::make_shared<MainContext>());
251
+ (*kept)->makeRoot();
252
+ return kept;
253
+ }();
254
+ return **context;
255
+ }
256
+
257
+ namespace {
258
+
259
+ /// `job`, recording how long it waits for its owner and how long it runs,
260
+ /// under one correlation id: the one a Correlate carried from the caller
261
+ /// (a JS call), or a new one. The job running where it was posted is its
262
+ /// parent.
263
+ Job traced(Job job) {
264
+ uint64_t id = trace::takeCorrelation();
265
+ if (!id) id = trace::newId();
266
+
267
+ uint64_t parent = trace::currentId();
268
+ trace::Mark wait = trace::begin(trace::Category::Queue, "wait");
269
+
270
+ return [job = std::move(job), id, parent, wait]() mutable {
271
+ trace::end(wait, trace::Category::Queue, "wait", {.id = id, .parent = parent});
272
+
273
+ struct Running {
274
+ trace::Mark mark = trace::begin(trace::Category::Run, "run");
275
+ trace::Current current;
276
+ uint64_t id;
277
+ uint64_t parent;
278
+
279
+ Running(uint64_t id, uint64_t parent) : current(id), id(id), parent(parent) {}
280
+
281
+ ~Running() { trace::end(mark, trace::Category::Run, "run", {.id = id, .parent = parent}); }
282
+ } running(id, parent);
283
+
284
+ job();
285
+ };
286
+ }
287
+
288
+ } // namespace
289
+
290
+ bool ExecutionContext::post(Job job) {
291
+ if (trace::enabled()) [[unlikely]]
292
+ job = traced(std::move(job));
293
+
294
+ return dispatch(std::move(job));
295
+ }
296
+
297
+ bool ExecutionContext::post(Job job, const std::shared_ptr<Scope>& owner) {
298
+ if (!owner) return post(std::move(job));
299
+
300
+ if (owner->state() != Scope::State::Active) return false;
301
+
302
+ std::weak_ptr<Scope> weak = owner;
303
+ Generation generation = owner->generation();
304
+
305
+ return post([weak, generation, job = std::move(job)]() mutable {
306
+ auto scope = weak.lock();
307
+ if (!scope || scope->state() != Scope::State::Active || scope->generation() != generation) return;
308
+
309
+ job();
310
+ });
311
+ }
312
+
313
+ bool ExecutionContext::postDelayed(double ms, Job job) { return dispatchDelayed(ms, std::move(job)); }
314
+
315
+ void ExecutionContext::enqueueMicrotask(Job job) {
316
+ if (!isCurrent()) throw std::logic_error("A microtask is queued on its own context; post to another one");
317
+
318
+ microtasks_.push_back(std::move(job));
319
+ ++*pendingMicrotasks_;
320
+ }
321
+
322
+ void ExecutionContext::drainMicrotasks() {
323
+ if (!isCurrent()) throw std::logic_error("A context's microtasks run on that context");
324
+
325
+ while (!microtasks_.empty()) {
326
+ Job job = std::move(microtasks_.front());
327
+ microtasks_.pop_front();
328
+ --*pendingMicrotasks_;
329
+
330
+ detail::runGuarded(job, "job");
331
+ }
332
+ }
333
+
334
+ void ExecutionContext::runTurn(Job& job) {
335
+ ContextEntry entry(*this);
336
+ Job local = std::move(job);
337
+
338
+ detail::runGuarded(local, "job");
339
+ }
340
+
341
+ // --- ContextEntry ---------------------------------------------------------------
342
+
343
+ ContextEntry::ContextEntry(ExecutionContext& context) : context_(context), previous_(entered) {
344
+ if (&context == &ExecutionContext::legacy()) throw std::logic_error("The legacy module context is entered with LucentScope");
345
+
346
+ if (Scheduler::lock().heldByCurrentThread()) {
347
+ throw std::logic_error("Code holding the Lucent lock cannot enter another context; post to it");
348
+ }
349
+
350
+ if (previous_ && previous_ != &context) throw std::logic_error("A context cannot enter another one; post to it");
351
+
352
+ if (!context.onExecutor()) throw std::logic_error("A context is entered only on its own thread; post to it");
353
+
354
+ entered = &context;
355
+ }
356
+
357
+ ContextEntry::~ContextEntry() {
358
+ // The outermost entry: the stack is empty once it leaves.
359
+ if (previous_ != &context_) context_.drainMicrotasks();
360
+
361
+ entered = previous_;
362
+ }
363
+
364
+ // --- IsolatedContext ------------------------------------------------------------
365
+
366
+ IsolatedContext::IsolatedContext() = default;
367
+
368
+ std::shared_ptr<IsolatedContext> IsolatedContext::create() {
369
+ std::shared_ptr<IsolatedContext> context(new IsolatedContext());
370
+ std::weak_ptr<IsolatedContext> weak = context;
371
+
372
+ context->makeRoot();
373
+
374
+ // A job whose context is gone is dropped, destroyed on the thread.
375
+ context->worker_ = WorkerThread::start([weak](Job& job) {
376
+ if (auto self = weak.lock()) self->runTurn(job);
377
+ });
378
+
379
+ return context;
380
+ }
381
+
382
+ IsolatedContext::~IsolatedContext() { shutdown(); }
383
+
384
+ void IsolatedContext::shutdown() {
385
+ if (stopping_.exchange(true)) return;
386
+
387
+ // Expired when called from the destructor: the root is then disposed
388
+ // outside a turn.
389
+ std::weak_ptr<ExecutionContext> weak = weak_from_this();
390
+
391
+ worker_->stop([weak, root = root()] {
392
+ Job dispose = [root] {
393
+ if (auto e = root->dispose()) reportUncaught(e, "scope");
394
+ };
395
+
396
+ if (auto self = weak.lock()) {
397
+ static_cast<IsolatedContext&>(*self).runTurn(dispose);
398
+ } else {
399
+ dispose();
400
+ }
401
+ });
402
+ }
403
+
404
+ bool IsolatedContext::dispatch(Job job) {
405
+ if (stopping_.load()) return false;
406
+
407
+ return worker_->post(std::move(job));
408
+ }
409
+
410
+ bool IsolatedContext::dispatchDelayed(double ms, Job job) {
411
+ if (stopping_.load()) return false;
412
+
413
+ return worker_->postDelayed(ms, std::move(job));
414
+ }
415
+
416
+ } // namespace lucent