@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,251 @@
1
+ // Lucent runtime — tracing.
2
+ //
3
+ // Structured events that say why something was slow: how long a job waited
4
+ // for its owner before it ran, how long a call waited for the Lucent lock,
5
+ // how long a compute task waited for a worker, ran and waited to be
6
+ // delivered, how many bytes a copy moved. Events that belong together (a
7
+ // JS call, the job it posted, its completion) share a correlation id; a
8
+ // job posted from another names it as its parent. Native code records its
9
+ // own spans with the .lucent.ts site its #line names (LUCENT_TRACE_SCOPE).
10
+ //
11
+ // Tracing is off by default and costs one relaxed load where it is
12
+ // checked. Started, it keeps the latest events in a bounded buffer
13
+ // (counting those it drops), exportable as a Chrome trace; asked to, it
14
+ // also mirrors spans live into the platform's tooling: os_signpost on
15
+ // Apple platforms (Instruments), ATrace on Android (Perfetto, systrace).
16
+ // The mirror is opt-in: signposts persist even when no tool records them.
17
+ //
18
+ // It starts from the environment too, when the module context starts:
19
+ // LUCENT_TRACE=1 (the buffer), =platform (and the platform's tooling), or
20
+ // =<file>.json (and a Chrome trace written there at exit). On Android the
21
+ // debug.lucent.trace system property says the same, and ATrace, cheap
22
+ // while nothing records, is always mirrored.
23
+ #pragma once
24
+
25
+ #include <atomic>
26
+ #include <chrono>
27
+ #include <cstddef>
28
+ #include <cstdint>
29
+ #include <string>
30
+ #include <vector>
31
+
32
+ namespace lucent::trace {
33
+
34
+ enum class Category : uint8_t {
35
+ /// A call from JavaScript into Lucent code.
36
+ Entry,
37
+ /// Waiting for the Lucent lock.
38
+ Lock,
39
+ /// A posted job waiting for its owner to run it.
40
+ Queue,
41
+ /// A posted job running on its owner.
42
+ Run,
43
+ /// Native work: Lucent code, platform calls.
44
+ Native,
45
+ /// A result delivered back to JavaScript.
46
+ Completion,
47
+ /// A compute task: waiting for a worker, running, being delivered.
48
+ Compute,
49
+ /// Bytes copied (transport, native buffer snapshots).
50
+ Copy,
51
+ /// Native allocations.
52
+ Alloc,
53
+ /// A build phase (the CLI's build record).
54
+ Build,
55
+ };
56
+
57
+ const char* categoryName(Category category);
58
+
59
+ /// Where native code came from: static, like the events that point at it.
60
+ struct Site {
61
+ const char* name;
62
+ const char* file;
63
+ int line;
64
+ };
65
+
66
+ struct Event {
67
+ enum class Phase : char { Span = 'X', Instant = 'i', Counter = 'C' };
68
+
69
+ Phase phase = Phase::Span;
70
+ Category category = Category::Native;
71
+ /// A static string, like every string an event holds.
72
+ const char* name = "";
73
+ /// What the event concerns: a compute task's entry, say.
74
+ const char* detail = nullptr;
75
+
76
+ /// Nanoseconds on the trace clock (steady), and the span's length.
77
+ uint64_t startNs = 0;
78
+ uint64_t durationNs = 0;
79
+
80
+ /// What it is correlated with, and what it came from (0: nothing).
81
+ uint64_t id = 0;
82
+ uint64_t parent = 0;
83
+
84
+ /// Bytes copied, a queue's depth, a counter's value; objects copied.
85
+ int64_t value = 0;
86
+ int64_t count = 0;
87
+
88
+ /// A small number per thread, and the execution context it was in (0:
89
+ /// none).
90
+ uint32_t thread = 0;
91
+ uint32_t context = 0;
92
+
93
+ const Site* site = nullptr;
94
+ };
95
+
96
+ struct Options {
97
+ /// Events kept: the latest, older ones are dropped and counted.
98
+ size_t capacity = 1 << 16;
99
+ /// Mirror spans into os_signpost / ATrace as well.
100
+ bool platform = false;
101
+ };
102
+
103
+ namespace detail {
104
+ // Constant-initialized: safe to read during static initialization.
105
+ inline std::atomic<bool> active{false};
106
+ } // namespace detail
107
+
108
+ /// Whether tracing is on: one relaxed load.
109
+ inline bool enabled() noexcept { return detail::active.load(std::memory_order_relaxed); }
110
+
111
+ /// Starts tracing with an empty buffer.
112
+ void start(Options options = {});
113
+
114
+ /// Stops recording; the buffer stays readable.
115
+ void stop();
116
+
117
+ /// Starts tracing if the environment asks for it (LUCENT_TRACE, or the
118
+ /// debug.lucent.trace property on Android). Once per process.
119
+ void startFromEnvironment();
120
+
121
+ /// The events kept, oldest first, and how many were dropped.
122
+ std::vector<Event> events();
123
+ uint64_t dropped();
124
+
125
+ /// The Chrome trace event format (chrome://tracing, Perfetto's UI), with
126
+ /// flow arrows between events that share an id.
127
+ std::string chromeJson(const std::vector<Event>& events);
128
+
129
+ /// Writes events() as a Chrome trace; false if the file cannot be written.
130
+ bool writeChromeJson(const std::string& path);
131
+
132
+ /// Nanoseconds on the trace clock.
133
+ uint64_t now() noexcept;
134
+ uint64_t toNs(std::chrono::steady_clock::time_point at) noexcept;
135
+
136
+ /// A process-unique correlation id (never 0).
137
+ uint64_t newId() noexcept;
138
+
139
+ /// What an event adds to its category and name.
140
+ struct Detail {
141
+ uint64_t id = 0;
142
+ uint64_t parent = 0;
143
+ int64_t value = 0;
144
+ int64_t count = 0;
145
+ const Site* site = nullptr;
146
+ const char* detail = nullptr;
147
+ };
148
+
149
+ /// The start of a span; empty when tracing was off.
150
+ struct Mark {
151
+ uint64_t startNs = 0;
152
+ /// Pairs the platform's begin and end, across threads.
153
+ uint64_t cookie = 0;
154
+
155
+ explicit operator bool() const noexcept { return startNs != 0; }
156
+ };
157
+
158
+ /// Begins a span: now, and live in the platform's tooling. Empty when off.
159
+ Mark begin(Category category, const char* name) noexcept;
160
+
161
+ /// Ends the span `mark` began (nothing if it is empty, or tracing stopped).
162
+ void end(const Mark& mark, Category category, const char* name, const Detail& detail = {}) noexcept;
163
+
164
+ /// A span recorded after the fact, from its two times.
165
+ void span(Category category, const char* name, uint64_t startNs, uint64_t endNs, const Detail& detail = {}) noexcept;
166
+
167
+ void instant(Category category, const char* name, uint64_t id = 0, int64_t value = 0) noexcept;
168
+ void instant(Category category, const char* name, const Detail& detail) noexcept;
169
+
170
+ void counter(const char* name, int64_t value) noexcept;
171
+
172
+ /// The correlation id of the traced job running on this thread (0: none).
173
+ uint64_t currentId() noexcept;
174
+
175
+ /// While it lives, the traced job on this thread is `id`: jobs it posts
176
+ /// name it as their parent.
177
+ class Current {
178
+ public:
179
+ explicit Current(uint64_t id) noexcept;
180
+ ~Current();
181
+
182
+ Current(const Current&) = delete;
183
+ Current& operator=(const Current&) = delete;
184
+
185
+ private:
186
+ uint64_t previous_;
187
+ };
188
+
189
+ /// While it lives, the next job posted from this thread takes `id` rather
190
+ /// than a new one: a JS call's id carried to the job it posts.
191
+ class Correlate {
192
+ public:
193
+ explicit Correlate(uint64_t id) noexcept;
194
+ ~Correlate();
195
+
196
+ Correlate(const Correlate&) = delete;
197
+ Correlate& operator=(const Correlate&) = delete;
198
+
199
+ private:
200
+ uint64_t previous_;
201
+ };
202
+
203
+ /// The id a Correlate set for this thread's next post, taken (0: none).
204
+ uint64_t takeCorrelation() noexcept;
205
+
206
+ /// A span over a scope (native work): LUCENT_TRACE_SCOPE.
207
+ class Scope {
208
+ public:
209
+ Scope(Category category, const char* name, const Site* site = nullptr, uint64_t id = 0) noexcept
210
+ : category_(category), name_(name), site_(site), id_(id) {
211
+ if (enabled()) [[unlikely]]
212
+ mark_ = begin(category, name);
213
+ }
214
+
215
+ ~Scope() {
216
+ if (mark_) [[unlikely]]
217
+ end(mark_, category_, name_, {.id = id_, .parent = currentId(), .value = value_, .site = site_});
218
+ }
219
+
220
+ Scope(const Scope&) = delete;
221
+ Scope& operator=(const Scope&) = delete;
222
+
223
+ void value(int64_t v) noexcept { value_ = v; }
224
+
225
+ private:
226
+ Mark mark_;
227
+ Category category_;
228
+ const char* name_;
229
+ const Site* site_;
230
+ uint64_t id_;
231
+ int64_t value_ = 0;
232
+ };
233
+
234
+ } // namespace lucent::trace
235
+
236
+ #define LUCENT_TRACE_CONCAT_(a, b) a##b
237
+ #define LUCENT_TRACE_CONCAT(a, b) LUCENT_TRACE_CONCAT_(a, b)
238
+
239
+ /// A static site: `name` at the file and line the code names (under a
240
+ /// #line, the .lucent.ts source), or at `file`:`line` given explicitly.
241
+ #define LUCENT_TRACE_SITE(name) LUCENT_TRACE_SITE_AT(name, __FILE__, __LINE__)
242
+ #define LUCENT_TRACE_SITE_AT(name, file, line) \
243
+ ([]() -> const ::lucent::trace::Site* { \
244
+ static constexpr ::lucent::trace::Site lucentTraceSite{name, file, line}; \
245
+ return &lucentTraceSite; \
246
+ }())
247
+
248
+ /// A native span over the rest of the scope, named `name`, at its site.
249
+ #define LUCENT_TRACE_SCOPE(name) \
250
+ ::lucent::trace::Scope LUCENT_TRACE_CONCAT(lucentTraceScope, __LINE__)(::lucent::trace::Category::Native, name, \
251
+ LUCENT_TRACE_SITE(name))
@@ -0,0 +1,9 @@
1
+ #include "transport.h"
2
+
3
+ namespace lucent {
4
+
5
+ void throwDataCloneError(const char* message) {
6
+ throwError(String::fromLatin1("DataCloneError"), String::fromUtf8(message));
7
+ }
8
+
9
+ } // namespace lucent
@@ -0,0 +1,341 @@
1
+ // Lucent runtime — the values a compute task receives.
2
+ //
3
+ // A task runs on another thread, so it shares nothing mutable with the code
4
+ // that submitted it: its input is copied, at submission, into a graph only
5
+ // the task holds. Every array, map, set, dictionary, byte buffer, date and
6
+ // object reachable from the input is copied once, so two references to one
7
+ // object are two references to one copy, and cycles survive. Strings are
8
+ // immutable, so they cross as they are. The copy is the whole transport:
9
+ // the task runs in the same process, so there is no wire format to unpack.
10
+ //
11
+ // A type crosses if it has a Transport. The runtime's value types have one;
12
+ // the compiler emits one for each struct and class (with transportObject).
13
+ // Anything else (functions, promises, signals, native instances) does not
14
+ // compile. An object whose class is a subclass of the one its type names is
15
+ // refused when copied, with a DataCloneError: its copy would lose fields.
16
+ //
17
+ // A transport may move instead of copying, for something that must have one
18
+ // owner at a time (an owned native buffer): it hands the task the thing
19
+ // itself and registers the move with the graph (CopyGraph::onCommit). Moves
20
+ // commit only once the whole input has been copied, and roll back if the
21
+ // copy fails, so a refused submission leaves the caller's values as they
22
+ // were.
23
+ #pragma once
24
+
25
+ #include <any>
26
+ #include <cstdint>
27
+ #include <functional>
28
+ #include <memory>
29
+ #include <tuple>
30
+ #include <type_traits>
31
+ #include <typeinfo>
32
+ #include <unordered_map>
33
+ #include <utility>
34
+ #include <variant>
35
+ #include <vector>
36
+
37
+ #include "array.h"
38
+ #include "bigint.h"
39
+ #include "bytes.h"
40
+ #include "core.h"
41
+ #include "date.h"
42
+ #include "jserror.h"
43
+ #include "jsstring.h"
44
+ #include "map.h"
45
+ #include "trace.h"
46
+
47
+ namespace lucent {
48
+
49
+ /// What a copy refuses to copy: a DataCloneError, as structuredClone throws.
50
+ [[noreturn]] void throwDataCloneError(const char* message);
51
+
52
+ /// One input's copy in progress: the copies made so far, by the identity of
53
+ /// what they copy, and the moves to commit once it is complete.
54
+ class CopyGraph {
55
+ public:
56
+ CopyGraph() = default;
57
+ CopyGraph(const CopyGraph&) = delete;
58
+ CopyGraph& operator=(const CopyGraph&) = delete;
59
+
60
+ /// The copy already made of the object `identity` names, if any.
61
+ template <class H>
62
+ const H* find(const void* identity) const {
63
+ auto it = copies_.find(identity);
64
+ if (it == copies_.end()) return nullptr;
65
+
66
+ const H* copy = std::any_cast<H>(&it->second);
67
+ if (!copy) throwDataCloneError("An object reached as two different types cannot be copied");
68
+
69
+ return copy;
70
+ }
71
+
72
+ /// Records `copy` as the copy of `identity`: before copying what the
73
+ /// object holds, so a cycle leads back to the copy.
74
+ template <class H>
75
+ void remember(const void* identity, const H& copy) {
76
+ copies_.emplace(identity, std::any(copy));
77
+ }
78
+
79
+ /// For a transport that moves: `commit` runs once the whole input has
80
+ /// been copied, `rollback` (if any) instead if the copy fails. Each runs
81
+ /// on the submitting thread, in the order registered.
82
+ void onCommit(std::function<void()> commit, std::function<void()> rollback = nullptr) {
83
+ moves_.push_back({std::move(commit), std::move(rollback)});
84
+ }
85
+
86
+ /// Objects copied (or moved) so far.
87
+ size_t copied() const { return copies_.size(); }
88
+
89
+ /// Bytes of element storage copied so far (arrays, byte buffers): what
90
+ /// a trace reports for the copy.
91
+ size_t bytes() const { return bytes_; }
92
+ void countBytes(size_t n) { bytes_ += n; }
93
+
94
+ /// Runs the commits (or, if `failed`, the rollbacks) and forgets them.
95
+ void finish(bool failed) {
96
+ auto moves = std::move(moves_);
97
+ moves_.clear();
98
+
99
+ for (auto& [commit, rollback] : moves) {
100
+ if (!failed) {
101
+ commit();
102
+ } else if (rollback) {
103
+ rollback();
104
+ }
105
+ }
106
+ }
107
+
108
+ private:
109
+ std::unordered_map<const void*, std::any> copies_;
110
+ std::vector<std::pair<std::function<void()>, std::function<void()>>> moves_;
111
+ size_t bytes_ = 0;
112
+ };
113
+
114
+ /// How a `T` crosses to a task: `static T copy(const T&, CopyGraph&)`.
115
+ /// Specialized for the runtime's value types here and, by the compiler, for
116
+ /// each struct and class. Unspecialized, the type cannot cross.
117
+ template <class T>
118
+ struct Transport {};
119
+
120
+ template <class T>
121
+ concept Transportable = requires(const T& value, CopyGraph& graph) {
122
+ { Transport<T>::copy(value, graph) } -> std::same_as<T>;
123
+ };
124
+
125
+ /// Copies one part of an input: what a Transport calls for what it holds.
126
+ template <class T>
127
+ T transport(const T& value, CopyGraph& graph) {
128
+ static_assert(Transportable<T>, "This type cannot cross to a compute task: it has no lucent::Transport");
129
+
130
+ return Transport<T>::copy(value, graph);
131
+ }
132
+
133
+ /// Copies `value` for a task, committing the moves it registered; if the
134
+ /// copy throws, they roll back and the error reaches the caller.
135
+ template <class T>
136
+ T transportCopy(const T& value) {
137
+ CopyGraph graph;
138
+ trace::Mark mark = trace::begin(trace::Category::Copy, "transport.copy");
139
+
140
+ try {
141
+ T copy = transport(value, graph);
142
+ graph.finish(false);
143
+
144
+ trace::end(mark, trace::Category::Copy, "transport.copy",
145
+ {.parent = trace::currentId(), .value = static_cast<int64_t>(graph.bytes()), .count = static_cast<int64_t>(graph.copied())});
146
+ return copy;
147
+ } catch (...) {
148
+ graph.finish(true);
149
+ trace::end(mark, trace::Category::Copy, "transport.copy", {.parent = trace::currentId(), .count = -1});
150
+ throw;
151
+ }
152
+ }
153
+
154
+ /// A Transport for a struct or class `T` (the compiler emits one per type):
155
+ /// refuses an object of a subclass, makes one copy per object, remembers it
156
+ /// before `fields(from, to, graph)` copies what it holds. `T` must be
157
+ /// default-constructible.
158
+ template <class T, class Fields>
159
+ Ref<T> transportObject(const Ref<T>& source, CopyGraph& graph, Fields&& fields) {
160
+ if (!source) return source;
161
+
162
+ // Most derived: the same object reached through any base is one.
163
+ const void* identity = dynamic_cast<const void*>(source.get());
164
+ if (auto* copy = graph.find<Ref<T>>(identity)) return *copy;
165
+
166
+ if (typeid(*source) != typeid(T)) throwDataCloneError("An object of a subclass cannot be copied as its base class");
167
+
168
+ auto copy = std::make_shared<T>();
169
+ graph.remember(identity, copy);
170
+
171
+ fields(*source, *copy, graph);
172
+ return copy;
173
+ }
174
+
175
+ // --- the runtime's values -----------------------------------------------------
176
+
177
+ namespace detail {
178
+
179
+ /// Values that cross as they are: plain data, and immutable strings and
180
+ /// bigints.
181
+ template <class T>
182
+ concept CrossesAsIs = std::is_arithmetic_v<T> || std::is_enum_v<T> || std::is_same_v<T, String> ||
183
+ std::is_same_v<T, BigInt> || std::is_same_v<T, Undefined> || std::is_same_v<T, Null>;
184
+
185
+ } // namespace detail
186
+
187
+ template <class T>
188
+ requires detail::CrossesAsIs<T>
189
+ struct Transport<T> {
190
+ static T copy(const T& value, CopyGraph&) { return value; }
191
+ };
192
+
193
+ template <Transportable T>
194
+ struct Transport<Opt<T>> {
195
+ static Opt<T> copy(const Opt<T>& value, CopyGraph& graph) {
196
+ if (value.has()) return Opt<T>(transport(value.get(), graph));
197
+
198
+ return value;
199
+ }
200
+ };
201
+
202
+ template <Transportable... Ts>
203
+ struct Transport<std::variant<Ts...>> {
204
+ using V = std::variant<Ts...>;
205
+
206
+ static V copy(const V& value, CopyGraph& graph) { return copyAt<0>(value, graph); }
207
+
208
+ /// By index, not type: an alternative's type may repeat.
209
+ template <size_t I>
210
+ static V copyAt(const V& value, CopyGraph& graph) {
211
+ if constexpr (I + 1 < sizeof...(Ts)) {
212
+ if (value.index() != I) return copyAt<I + 1>(value, graph);
213
+ }
214
+
215
+ return V(std::in_place_index<I>, transport(*std::get_if<I>(&value), graph));
216
+ }
217
+ };
218
+
219
+ template <Transportable... Ts>
220
+ struct Transport<std::tuple<Ts...>> {
221
+ static std::tuple<Ts...> copy(const std::tuple<Ts...>& value, CopyGraph& graph) {
222
+ // Braced: the parts are copied in order.
223
+ return std::apply([&](const Ts&... parts) { return std::tuple<Ts...>{transport(parts, graph)...}; }, value);
224
+ }
225
+ };
226
+
227
+ template <Transportable T>
228
+ struct Transport<Array<T>> {
229
+ static Array<T> copy(const Array<T>& source, CopyGraph& graph) {
230
+ if (auto* copy = graph.find<Array<T>>(source.identity())) return *copy;
231
+
232
+ Array<T> copy;
233
+ graph.remember(source.identity(), copy);
234
+
235
+ if constexpr (detail::CrossesAsIs<T>) {
236
+ copy.items() = source.items();
237
+ graph.countBytes(source.size() * sizeof(typename Array<T>::Elem));
238
+ } else {
239
+ auto& items = copy.items();
240
+ items.reserve(source.size());
241
+ graph.countBytes(source.size() * sizeof(typename Array<T>::Elem));
242
+
243
+ for (const auto& item : source.items()) items.push_back(transport(item, graph));
244
+ }
245
+
246
+ return copy;
247
+ }
248
+ };
249
+
250
+ template <Transportable K, Transportable V>
251
+ struct Transport<Map<K, V>> {
252
+ static Map<K, V> copy(const Map<K, V>& source, CopyGraph& graph) {
253
+ if (auto* copy = graph.find<Map<K, V>>(source.identity())) return *copy;
254
+
255
+ Map<K, V> copy;
256
+ graph.remember(source.identity(), copy);
257
+
258
+ auto& table = source.table();
259
+ for (size_t i = 0; i < table.slotCount(); i++) {
260
+ if (!table.slotLive(i)) continue;
261
+
262
+ const auto& entry = table.slot(i);
263
+ copy.set(transport(entry.key, graph), transport(entry.value, graph));
264
+ }
265
+
266
+ return copy;
267
+ }
268
+ };
269
+
270
+ template <Transportable T>
271
+ struct Transport<Set<T>> {
272
+ static Set<T> copy(const Set<T>& source, CopyGraph& graph) {
273
+ if (auto* copy = graph.find<Set<T>>(source.identity())) return *copy;
274
+
275
+ Set<T> copy;
276
+ graph.remember(source.identity(), copy);
277
+
278
+ auto& table = source.table();
279
+ for (size_t i = 0; i < table.slotCount(); i++) {
280
+ if (table.slotLive(i)) copy.add(transport(table.slot(i).key, graph));
281
+ }
282
+
283
+ return copy;
284
+ }
285
+ };
286
+
287
+ template <Transportable V>
288
+ struct Transport<Dict<V>> {
289
+ static Dict<V> copy(const Dict<V>& source, CopyGraph& graph) {
290
+ if (auto* copy = graph.find<Dict<V>>(source.identity())) return *copy;
291
+
292
+ Dict<V> copy;
293
+ graph.remember(source.identity(), copy);
294
+
295
+ auto& table = source.table();
296
+ for (size_t i = 0; i < table.slotCount(); i++) {
297
+ if (!table.slotLive(i)) continue;
298
+
299
+ const auto& entry = table.slot(i);
300
+ copy.set(entry.key, transport(entry.value, graph));
301
+ }
302
+
303
+ return copy;
304
+ }
305
+ };
306
+
307
+ /// Views of one buffer stay views of one (copied) buffer, as
308
+ /// structuredClone keeps them: a view carries its whole buffer, so slice()
309
+ /// a small view of a large buffer before submitting it.
310
+ template <>
311
+ struct Transport<Bytes> {
312
+ using Buffer = std::shared_ptr<std::vector<uint8_t>>;
313
+
314
+ static Bytes copy(const Bytes& source, CopyGraph& graph) {
315
+ const Buffer& buffer = source.buffer();
316
+
317
+ const Buffer* copied = graph.find<Buffer>(buffer.get());
318
+ if (!copied) {
319
+ graph.remember(buffer.get(), std::make_shared<std::vector<uint8_t>>(*buffer));
320
+ graph.countBytes(buffer->size());
321
+ copied = graph.find<Buffer>(buffer.get());
322
+ }
323
+
324
+ return Bytes::view(*copied, source.offset(), source.size());
325
+ }
326
+ };
327
+
328
+ template <>
329
+ struct Transport<Date> {
330
+ static Date copy(const Date& source, CopyGraph& graph) {
331
+ if (!source) return source;
332
+
333
+ if (auto* copy = graph.find<Date>(source.get())) return *copy;
334
+
335
+ Date copy = makeDate(source->getTime());
336
+ graph.remember(source.get(), copy);
337
+ return copy;
338
+ }
339
+ };
340
+
341
+ } // namespace lucent