@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
@@ -20,6 +20,9 @@ std::unordered_map<jsi::Runtime*, std::weak_ptr<Host>>& registry() {
20
20
  // stale. Constant-initialized: reading it needs no guard.
21
21
  std::atomic<uint64_t> registryGeneration{1};
22
22
 
23
+ // Constant-initialized: safe to use during static initialization.
24
+ std::atomic<RuntimeId> nextRuntimeId{1};
25
+
23
26
  // The last host this thread looked up: every call from JavaScript asks for
24
27
  // it, and a thread nearly always talks to one runtime. One thread_local, as
25
28
  // each costs a lookup on some platforms (__tlv_get_addr on Apple's).
@@ -30,29 +33,109 @@ struct CachedHost {
30
33
  };
31
34
  thread_local CachedHost cached;
32
35
 
36
+ /// Runs `job` on the legacy module context: here if this thread is in it,
37
+ /// else as a turn there. Either way `job` is destroyed there, holding the
38
+ /// Lucent lock, like the values it carries.
39
+ void onModuleContext(Job job) {
40
+ if (!Scheduler::lock().heldByCurrentThread()) {
41
+ Scheduler::instance().post(std::move(job));
42
+ return;
43
+ }
44
+
45
+ detail::runGuarded(job, "job");
46
+ }
47
+
48
+ /// Counts one task or job in flight for a host while it lives.
49
+ class Counted {
50
+ public:
51
+ explicit Counted(std::shared_ptr<std::atomic<size_t>> count) : count_(std::move(count)) { ++*count_; }
52
+
53
+ ~Counted() { --*count_; }
54
+
55
+ Counted(const Counted&) = delete;
56
+ Counted& operator=(const Counted&) = delete;
57
+
58
+ private:
59
+ std::shared_ptr<std::atomic<size_t>> count_;
60
+ };
61
+
62
+ /// A task for the JS thread. If it never runs there, it is released on the
63
+ /// legacy module context, after `dropped`: the values it carries are the
64
+ /// module context's, whichever thread gave up on the task. It counts as in
65
+ /// flight until then.
66
+ struct Parcel {
67
+ std::shared_ptr<Counted> counted;
68
+ JsTask task;
69
+ Job dropped;
70
+ std::atomic<bool> ran{false};
71
+
72
+ Parcel(std::shared_ptr<Counted> c, JsTask t, Job d) : counted(std::move(c)), task(std::move(t)), dropped(std::move(d)) {}
73
+
74
+ ~Parcel() {
75
+ if (ran.load()) return;
76
+
77
+ onModuleContext([counted = std::move(counted), task = std::move(task), dropped = std::move(dropped)]() mutable {
78
+ if (dropped) dropped();
79
+
80
+ task = nullptr;
81
+ dropped = nullptr;
82
+ });
83
+ }
84
+ };
85
+
86
+ } // namespace
87
+
33
88
  // Installed on the JS global object so the runtime's teardown releases our
34
- // JSI references on the JS thread while the runtime still exists.
35
- class HostAnchor : public jsi::HostObject {
89
+ // JSI references on the JS thread while the runtime still exists. It runs
90
+ // no JavaScript: the runtime may be being destroyed.
91
+ class Host::Anchor : public jsi::HostObject {
36
92
  public:
37
- explicit HostAnchor(std::shared_ptr<Host> host) : host_(std::move(host)) {}
38
- ~HostAnchor() override {
39
- if (host_) host_->invalidate();
93
+ explicit Anchor(std::shared_ptr<Host> host) : host_(std::move(host)) {}
94
+
95
+ ~Anchor() override { host_->tearDown(false); }
96
+
97
+ #ifndef NDEBUG
98
+ jsi::Value get(jsi::Runtime& rt, const jsi::PropNameID& name) override {
99
+ if (name.utf8(rt) != "ownership") return jsi::Value::undefined();
100
+
101
+ Ownership held = host_->ownership();
102
+ jsi::Object o(rt);
103
+ o.setProperty(rt, "runtime", static_cast<double>(held.runtime));
104
+ o.setProperty(rt, "promises", static_cast<double>(held.promises));
105
+ o.setProperty(rt, "callbacks", static_cast<double>(held.callbacks));
106
+ o.setProperty(rt, "identities", static_cast<double>(held.identities));
107
+ o.setProperty(rt, "prototypes", static_cast<double>(held.prototypes));
108
+ o.setProperty(rt, "modules", static_cast<double>(held.modules));
109
+ o.setProperty(rt, "registrations", static_cast<double>(held.registrations));
110
+ o.setProperty(rt, "inFlight", static_cast<double>(held.inFlight));
111
+ return o;
40
112
  }
113
+ #endif
41
114
 
42
115
  private:
43
116
  std::shared_ptr<Host> host_;
44
117
  };
45
118
 
46
- } // namespace
47
-
48
119
  std::shared_ptr<Host> Host::create(jsi::Runtime& rt, JsPoster poster) {
120
+ std::shared_ptr<Host> previous;
121
+ {
122
+ std::lock_guard<std::mutex> g(registryMutex());
123
+ auto it = registry().find(&rt);
124
+ if (it != registry().end()) previous = it->second.lock();
125
+ }
126
+
127
+ if (previous) previous->invalidate();
128
+
49
129
  std::shared_ptr<Host> host(new Host(rt, std::move(poster)));
50
130
  {
51
131
  std::lock_guard<std::mutex> g(registryMutex());
52
132
  registry()[&rt] = host;
53
133
  registryGeneration++;
54
134
  }
55
- rt.global().setProperty(rt, "__lucentHost", jsi::Object::createFromHostObject(rt, std::make_shared<HostAnchor>(host)));
135
+ rt.global().setProperty(rt, "__lucentHost", jsi::Object::createFromHostObject(rt, std::make_shared<Anchor>(host)));
136
+
137
+ // Module code's work from now on belongs to this runtime: tearing it down cancels that work.
138
+ setModuleScope(host->scope());
56
139
  {
57
140
  LucentScope scope;
58
141
  resetModuleState();
@@ -81,7 +164,12 @@ const jsi::PropNameID& Host::makeProp(jsi::Runtime& rt, const PropName& name) {
81
164
  return *props_[name.id];
82
165
  }
83
166
 
84
- Host::Host(jsi::Runtime& rt, JsPoster poster) : rt_(rt), poster_(std::move(poster)), jsThread_(std::this_thread::get_id()) {}
167
+ Host::Host(jsi::Runtime& rt, JsPoster poster)
168
+ : id_(nextRuntimeId.fetch_add(1, std::memory_order_relaxed)),
169
+ rt_(rt),
170
+ poster_(std::move(poster)),
171
+ jsThread_(std::this_thread::get_id()),
172
+ scope_(Scope::create(id_, Scheduler::instance().root())) {}
85
173
 
86
174
  Host::~Host() {
87
175
  std::lock_guard<std::mutex> g(registryMutex());
@@ -90,31 +178,102 @@ Host::~Host() {
90
178
  registryGeneration++;
91
179
  }
92
180
 
93
- void Host::postToJs(JsTask task) {
94
- if (!alive_.load()) return;
181
+ bool Host::postToJs(JsTask task, Job dropped) {
182
+ auto parcel = std::make_shared<Parcel>(std::make_shared<Counted>(inFlight_), std::move(task), std::move(dropped));
183
+
184
+ if (!alive_.load()) return false;
185
+
95
186
  std::weak_ptr<Host> weak = weak_from_this();
96
- poster_([weak, task = std::move(task)](jsi::Runtime& rt) {
187
+ poster_([weak, parcel](jsi::Runtime& rt) {
97
188
  auto self = weak.lock();
98
189
  if (!self || !self->alive()) return;
99
- task(rt);
190
+
191
+ parcel->ran = true;
192
+ parcel->task(rt);
100
193
  });
194
+
195
+ return true;
101
196
  }
102
197
 
103
- void Host::invalidate() {
198
+ void Host::postToModule(Job job) {
199
+ std::weak_ptr<Host> weak = weak_from_this();
200
+
201
+ // The scope drops the job once disposed; until its disposal has run on
202
+ // the module context, the host's own state says it is torn down.
203
+ Scheduler::instance().post(
204
+ [weak, counted = std::make_shared<Counted>(inFlight_), job = std::move(job)]() mutable {
205
+ auto self = weak.lock();
206
+ if (self && self->alive()) job();
207
+
208
+ job = nullptr;
209
+ },
210
+ scope_);
211
+ }
212
+
213
+ Host::Ownership Host::ownership() const {
214
+ size_t identities = 0;
215
+ for (auto& [native, weak] : identities_) {
216
+ if (weak.lock(rt_).isObject()) identities++;
217
+ }
218
+
219
+ return {id_,
220
+ promises_.size(),
221
+ functions_.size(),
222
+ identities,
223
+ prototypes_.size(),
224
+ modules_.size(),
225
+ scope_->registrations(),
226
+ inFlight_->load()};
227
+ }
228
+
229
+ Error Host::goneError() { return makeError(String::fromLatin1("AbortError"), String::fromLatin1("The JavaScript runtime is gone")); }
230
+
231
+ void Host::invalidate() { tearDown(true); }
232
+
233
+ void Host::tearDown(bool runtimeUsable) {
104
234
  if (!alive_.exchange(false)) return;
235
+
236
+ // JavaScript still waiting for Lucent learns that it never will.
237
+ auto owed = std::move(promises_);
105
238
  promises_.clear();
239
+
240
+ if (runtimeUsable && !owed.empty()) {
241
+ Error torn = makeError(String::fromLatin1("AbortError"), String::fromLatin1("Lucent was torn down for this JavaScript runtime"));
242
+
243
+ for (auto& [id, resolvers] : owed) {
244
+ try {
245
+ resolvers.reject.call(rt_, errorToJs(rt_, torn));
246
+ } catch (...) {
247
+ reportUncaught(std::current_exception(), "host");
248
+ }
249
+ }
250
+ }
251
+
252
+ owed.clear();
253
+
254
+ // Cancels its operations and runs its cleanups on the module context.
255
+ if (auto e = scope_->dispose()) reportUncaught(e, "host");
256
+
106
257
  functions_.clear();
107
258
  prototypes_.clear();
108
259
  modules_.clear();
109
260
  identities_.clear();
110
261
  props_.clear();
262
+
263
+ // Only its own entry: the runtime may have a newer host by now.
264
+ std::weak_ptr<Host> self = weak_from_this();
111
265
  std::lock_guard<std::mutex> g(registryMutex());
112
266
  auto it = registry().find(&rt_);
113
- if (it != registry().end()) registry().erase(it);
267
+ if (it != registry().end() && !it->second.owner_before(self) && !self.owner_before(it->second)) registry().erase(it);
114
268
  registryGeneration++;
115
269
  }
116
270
 
117
271
  jsi::Value Host::module(jsi::Runtime& rt, const std::string& name) {
272
+ // A replaced host's module (a TurboModule that kept it) is the new one's.
273
+ if (!alive()) return get(rt).module(rt, name);
274
+
275
+ if (name == kIdentityName) return identity(rt);
276
+
118
277
  auto it = modules_.find(name);
119
278
  if (it != modules_.end()) return jsi::Value(rt, it->second);
120
279
  size_t count = 0;
@@ -139,9 +298,27 @@ jsi::Object Host::modules(jsi::Runtime& rt) {
139
298
  size_t count = 0;
140
299
  const ModuleDef* defs = registeredModules(count);
141
300
  for (size_t i = 0; i < count; i++) all.setProperty(rt, defs[i].name, module(rt, defs[i].name));
301
+ all.setProperty(rt, kIdentityName, identity(rt));
142
302
  return all;
143
303
  }
144
304
 
305
+ jsi::Object Host::identity(jsi::Runtime& rt) {
306
+ const BuildIdentity& built = buildIdentity();
307
+
308
+ jsi::Object modules(rt);
309
+ for (size_t i = 0; i < built.moduleCount; i++)
310
+ modules.setProperty(rt, built.modules[i].name, jsi::String::createFromUtf8(rt, built.modules[i].api));
311
+
312
+ jsi::Object o(rt);
313
+ o.setProperty(rt, "host", static_cast<double>(id_));
314
+ o.setProperty(rt, "runtimeAbi", kRuntimeAbi);
315
+ o.setProperty(rt, "target", jsi::String::createFromUtf8(rt, built.target));
316
+ o.setProperty(rt, "program", jsi::String::createFromUtf8(rt, built.program));
317
+ o.setProperty(rt, "modules", modules);
318
+
319
+ return o;
320
+ }
321
+
145
322
  jsi::Value Host::createPromise(jsi::Runtime& rt, uint64_t& id) {
146
323
  id = nextId_++;
147
324
  auto slot = std::make_shared<std::pair<std::optional<jsi::Function>, std::optional<jsi::Function>>>();
@@ -215,16 +392,20 @@ jsi::Object& Host::prototype(jsi::Runtime& rt, const char* key, PrototypeInit in
215
392
  return prototypes_.emplace(key, std::move(proto)).first->second;
216
393
  }
217
394
 
218
- namespace {
219
- std::atomic<uint64_t> nextIdentity{1};
220
- } // namespace
221
-
222
395
  Ref<Object> instanceOf(jsi::Runtime& rt, const jsi::Value& v) {
223
396
  if (!v.isObject()) return nullptr;
224
397
  jsi::Object o = v.getObject(rt);
225
398
  if (!o.hasNativeState(rt)) return nullptr;
226
399
  auto s = std::dynamic_pointer_cast<InstanceState>(o.getNativeState(rt));
227
- return s ? s->object : nullptr;
400
+ if (!s) return nullptr;
401
+
402
+ // One host per runtime: a live one is this runtime's.
403
+ if (!s->host->alive()) {
404
+ jsi::Value error = rt.global().getPropertyAsFunction(rt, "TypeError").callAsConstructor(rt, "This Lucent object belongs to a host that was torn down");
405
+ throw jsi::JSError(rt, std::move(error));
406
+ }
407
+
408
+ return s->object;
228
409
  }
229
410
 
230
411
  void defineFunction(jsi::Runtime& rt, jsi::Object& target, const char* name, unsigned argc, HostFn fn) {
@@ -254,34 +435,31 @@ void defineClass(jsi::Runtime& rt, Host& host, jsi::Object& exports, const char*
254
435
 
255
436
  jsi::Value Host::wrap(jsi::Runtime& rt, const Ref<Object>& instance, const char* key, PrototypeInit init) {
256
437
  if (!instance) return jsi::Value::null();
257
- if (instance->jsIdentity != 0) {
258
- auto it = identities_.find(instance->jsIdentity);
259
- if (it != identities_.end()) {
260
- jsi::Value v = it->second.lock(rt);
261
- if (v.isObject()) return v;
262
- }
263
- } else {
264
- instance->jsIdentity = nextIdentity.fetch_add(1);
438
+ auto it = identities_.find(instance.get());
439
+ if (it != identities_.end()) {
440
+ jsi::Value v = it->second.lock(rt);
441
+ if (v.isObject()) return v;
265
442
  }
266
443
  jsi::Object& proto = prototype(rt, key, init);
267
444
  jsi::Object obj = jsi::Object::create(rt, jsi::Value(rt, proto));
268
- obj.setNativeState(rt, std::make_shared<InstanceState>(instance));
445
+ obj.setNativeState(rt, std::make_shared<InstanceState>(instance, shared_from_this()));
269
446
  // Periodically drop cache entries whose JS objects were collected.
270
447
  if (++identitySweep_ >= 256) {
271
448
  identitySweep_ = 0;
272
- std::vector<uint64_t> dead;
273
- for (auto& [id, weak] : identities_) {
274
- if (!weak.lock(rt).isObject()) dead.push_back(id);
449
+ std::vector<const Object*> dead;
450
+ for (auto& [native, weak] : identities_) {
451
+ if (!weak.lock(rt).isObject()) dead.push_back(native);
275
452
  }
276
- for (uint64_t id : dead) identities_.erase(id);
453
+ for (const Object* native : dead) identities_.erase(native);
277
454
  }
278
- identities_.insert_or_assign(instance->jsIdentity, jsi::WeakObject(rt, obj));
455
+ identities_.insert_or_assign(instance.get(), jsi::WeakObject(rt, obj));
279
456
  return jsi::Value(std::move(obj));
280
457
  }
281
458
 
282
459
  jsi::Value Host::errorToJs(jsi::Runtime& rt, const Error& e) {
283
460
  std::string name = e->name.toUtf8();
284
- const char* ctor = (name == "TypeError" || name == "RangeError") ? name.c_str() : "Error";
461
+ // The kinds Lucent throws (SyntaxError from BigInt(string)), as JavaScript's own.
462
+ const char* ctor = (name == "TypeError" || name == "RangeError" || name == "SyntaxError") ? name.c_str() : "Error";
285
463
  jsi::Object err = rt.global()
286
464
  .getPropertyAsFunction(rt, ctor)
287
465
  .callAsConstructor(rt, jsi::String::createFromUtf8(rt, e->message.toUtf8()))
@@ -5,6 +5,11 @@
5
5
  // pending promise resolvers, JS callbacks, class prototypes and the identity
6
6
  // cache. JSI values may only be touched on the JS thread, so native code
7
7
  // refers to them by id and hops to the JS thread to use them.
8
+ //
9
+ // Work for a runtime belongs to its host's scope, and ends with the host:
10
+ // torn down (a reload, the runtime's end), the host disposes its scope,
11
+ // drops what it posted that has not run, and releases what that carried on
12
+ // the legacy module context.
8
13
  #pragma once
9
14
 
10
15
  #include <jsi/jsi.h>
@@ -57,9 +62,39 @@ const ModuleDef* registeredModules(size_t& count);
57
62
  /// Implemented by generated code: resets module-level state for a new runtime.
58
63
  void resetModuleState();
59
64
 
65
+ /// What generated code and JavaScript proxies expect of this runtime: a
66
+ /// change that breaks either takes a new number. The compiler's
67
+ /// RUNTIME_ABI; generated code checks it as it builds.
68
+ inline constexpr int kRuntimeAbi = 1;
69
+
70
+ /// A module as the program was compiled: its name, and a hash of what
71
+ /// JavaScript sees of it (its exports and their signatures).
72
+ struct ModuleIdentity {
73
+ const char* name;
74
+ const char* api;
75
+ };
76
+
77
+ /// What this app's native code was built from, which JavaScript checks its
78
+ /// proxies against before they call it: the target it was compiled for
79
+ /// ("all" for code every target shares), a hash of the program, and each
80
+ /// module's API.
81
+ struct BuildIdentity {
82
+ const char* target;
83
+ const char* program;
84
+ const ModuleIdentity* modules;
85
+ size_t moduleCount;
86
+ };
87
+
88
+ /// Implemented by generated code.
89
+ const BuildIdentity& buildIdentity();
90
+
91
+ /// The name JavaScript reads the build identity by, next to the modules.
92
+ inline constexpr const char* kIdentityName = "__lucentIdentity";
93
+
60
94
  class Host : public std::enable_shared_from_this<Host> {
61
95
  public:
62
- /// Creates the host for `rt`. Call on the JS thread.
96
+ /// Creates the host for `rt`. Call on the JS thread. A runtime has one
97
+ /// host: one it had already is torn down (invalidate) and replaced.
63
98
  static std::shared_ptr<Host> create(jsi::Runtime& rt, JsPoster poster);
64
99
  static Host& get(jsi::Runtime& rt);
65
100
  /// The host for a call to a function `installed` defined: that host while
@@ -70,20 +105,73 @@ class Host : public std::enable_shared_from_this<Host> {
70
105
  }
71
106
  ~Host();
72
107
 
108
+ /// Nonzero, and no other host has had it: what scopes and tokens of
109
+ /// work for this runtime carry.
110
+ RuntimeId id() const { return id_; }
111
+
73
112
  jsi::Runtime& runtime() { return rt_; }
74
113
  bool onJsThread() const { return std::this_thread::get_id() == jsThread_; }
75
114
  bool alive() const { return alive_.load(); }
76
115
 
77
- /// Runs `task` on the JS thread unless the host was invalidated.
78
- void postToJs(JsTask task);
79
- /// Drops every JSI reference. Call on the JS thread before the runtime dies.
116
+ /// The root of the work done for this runtime, under the legacy module
117
+ /// context's root scope: disposed there when the host is torn down,
118
+ /// cancelling its operations and running its cleanups.
119
+ const std::shared_ptr<Scope>& scope() const { return scope_; }
120
+
121
+ /// Runs `task` on the JS thread unless the host is torn down first. A
122
+ /// task that never runs (the host torn down, or the poster dropped it)
123
+ /// is released on the legacy module context, after `dropped` runs there.
124
+ /// False if the host was already torn down.
125
+ bool postToJs(JsTask task, Job dropped = nullptr);
126
+
127
+ /// Runs `job` as a turn of the legacy module context unless the host is
128
+ /// torn down before it starts; dropped, it is released there.
129
+ void postToModule(Job job);
130
+
131
+ /// Tears the host down, on the JS thread while its runtime is usable:
132
+ /// the JS promises it owes reject with an AbortError, then its scope is
133
+ /// disposed and every JSI reference dropped. Idempotent.
80
134
  void invalidate();
81
135
 
82
- /// The exports object of a module, created on first use.
136
+ /// What Lucent code waiting on this runtime gets once the host is torn
137
+ /// down: an AbortError.
138
+ static Error goneError();
139
+
140
+ /// What the host holds, for debug ownership reports and tests (debug
141
+ /// builds show it to JavaScript as `__lucentHost.ownership`).
142
+ struct Ownership {
143
+ RuntimeId runtime = 0;
144
+ /// JS promises it will settle.
145
+ size_t promises = 0;
146
+ /// JS functions Lucent holds.
147
+ size_t callbacks = 0;
148
+ /// Live JS objects of native instances.
149
+ size_t identities = 0;
150
+ size_t prototypes = 0;
151
+ size_t modules = 0;
152
+ /// What its scope holds: JS promises Lucent awaits, operations, …
153
+ size_t registrations = 0;
154
+ size_t inFlight = 0;
155
+ };
156
+
157
+ /// On the JS thread.
158
+ Ownership ownership() const;
159
+
160
+ /// Tasks for the JS thread and jobs for the module context posted for
161
+ /// this runtime that have not yet run or been released. Any thread.
162
+ size_t inFlight() const { return inFlight_->load(); }
163
+
164
+ /// The exports object of a module, created on first use; for
165
+ /// kIdentityName, identity().
83
166
  jsi::Value module(jsi::Runtime& rt, const std::string& name);
84
- /// The object with every module as a property (for tests and debugging).
167
+ /// The object with every module as a property, and the identity (for
168
+ /// test hosts, which install it as React Native's module would be).
85
169
  jsi::Object modules(jsi::Runtime& rt);
86
170
 
171
+ /// The build identity for JavaScript: `{host, runtimeAbi, target,
172
+ /// program, modules: {name: api}}`, `host` being this host's id.
173
+ jsi::Object identity(jsi::Runtime& rt);
174
+
87
175
  // --- promises ---------------------------------------------------------
88
176
  /// Creates a JS promise; settle it later with resolve/reject on the JS thread.
89
177
  jsi::Value createPromise(jsi::Runtime& rt, uint64_t& id);
@@ -101,8 +189,9 @@ class Host : public std::enable_shared_from_this<Host> {
101
189
  /// The prototype for class `key`: the runtime's own across hosts (kept on
102
190
  /// the global object), with this host's methods.
103
191
  jsi::Object& prototype(jsi::Runtime& rt, const char* key, PrototypeInit init);
104
- /// The JS object for a native instance: the same object each time while
105
- /// JavaScript still references it.
192
+ /// The JS object for a native instance in this runtime: the same object
193
+ /// each time while JavaScript still references it. Each runtime has its
194
+ /// own for the same instance.
106
195
  jsi::Value wrap(jsi::Runtime& rt, const Ref<Object>& instance, const char* key, PrototypeInit init);
107
196
 
108
197
  /// Converts a Lucent error into a JS Error object (not thrown).
@@ -117,39 +206,50 @@ class Host : public std::enable_shared_from_this<Host> {
117
206
  }
118
207
 
119
208
  private:
209
+ /// On the global object: tears the host down when the runtime does.
210
+ class Anchor;
211
+
120
212
  Host(jsi::Runtime& rt, JsPoster poster);
121
213
 
214
+ /// invalidate(), but JS runs only if `runtimeUsable`: not while the
215
+ /// runtime is being destroyed.
216
+ void tearDown(bool runtimeUsable);
217
+
122
218
  struct Resolvers {
123
219
  jsi::Function resolve;
124
220
  jsi::Function reject;
125
221
  };
126
222
 
223
+ const RuntimeId id_;
127
224
  jsi::Runtime& rt_;
128
225
  JsPoster poster_;
129
226
  std::thread::id jsThread_;
130
227
  std::atomic<bool> alive_{true};
228
+ const std::shared_ptr<Scope> scope_;
229
+ const std::shared_ptr<std::atomic<size_t>> inFlight_ = std::make_shared<std::atomic<size_t>>(0);
131
230
  uint64_t nextId_ = 1;
132
231
  std::unordered_map<uint64_t, Resolvers> promises_;
133
232
  std::unordered_map<uint64_t, jsi::Function> functions_;
134
233
  std::unordered_map<std::string, jsi::Object> prototypes_;
135
234
  std::unordered_map<std::string, jsi::Value> modules_;
136
- std::unordered_map<uint64_t, jsi::WeakObject> identities_;
235
+ // Keyed by the native instance: its JS object keeps it alive, so while
236
+ // an entry's object lives, no other instance can have its address.
237
+ std::unordered_map<const Object*, jsi::WeakObject> identities_;
137
238
  size_t identitySweep_ = 0;
138
239
  std::vector<std::unique_ptr<jsi::PropNameID>> props_;
139
240
  const jsi::PropNameID& makeProp(jsi::Runtime& rt, const PropName& name);
140
241
  };
141
242
 
142
- /// Holds a strong reference to the Host while posting results back to JS.
143
- using HostRef = std::shared_ptr<Host>;
144
-
145
- /// NativeState attached to the JS object of a Lucent class instance.
243
+ /// NativeState attached to the JS object of a Lucent class instance, and
244
+ /// the host that made it.
146
245
  struct InstanceState : jsi::NativeState {
147
- explicit InstanceState(Ref<Object> o) : object(std::move(o)) {}
246
+ InstanceState(Ref<Object> o, std::shared_ptr<Host> h) : object(std::move(o)), host(std::move(h)) {}
148
247
  Ref<Object> object;
248
+ std::shared_ptr<Host> host;
149
249
  };
150
- using InstanceStateBase = InstanceState;
151
250
 
152
- /// The native instance behind a JS object, or null.
251
+ /// The native instance behind a JS object, or null. Throws a TypeError for
252
+ /// an object a torn-down host made: the state it belongs to has ended.
153
253
  Ref<Object> instanceOf(jsi::Runtime& rt, const jsi::Value& v);
154
254
 
155
255
  using HostFn = jsi::HostFunctionType;
@@ -8,6 +8,7 @@
8
8
  #include <variant>
9
9
 
10
10
  #include "array.h"
11
+ #include "bigint.h"
11
12
  #include "bytes.h"
12
13
  #include "date.h"
13
14
  #include "regexp.h"
@@ -15,6 +16,7 @@
15
16
  #include "jserror.h"
16
17
  #include "function.h"
17
18
  #include "map.h"
19
+ #include "native.h"
18
20
  #include "number.h"
19
21
  #include "jsstring.h"
20
22
 
@@ -51,10 +53,17 @@ inline void jsonWrite(JsonWriter& w, double v) {
51
53
  }
52
54
  }
53
55
  inline void jsonWrite(JsonWriter& w, bool v) { w.raw(v ? "true" : "false"); }
56
+ /// JSON has no bigints: JSON.stringify throws, as in JavaScript.
57
+ inline void jsonWrite(JsonWriter&, const BigInt&) { throwTypeError("Do not know how to serialize a BigInt"); }
54
58
  inline void jsonWrite(JsonWriter& w, const String& s) { w.quote(s); }
55
59
  inline void jsonWrite(JsonWriter& w, Undefined) { w.raw("null"); }
56
60
  inline void jsonWrite(JsonWriter& w, Null) { w.raw("null"); }
57
61
  inline void jsonWrite(JsonWriter& w, const Error&) { w.raw("{}"); }
62
+ /// An SDK object: a host object, with no enumerable own properties.
63
+ inline void jsonWrite(JsonWriter& w, const NativeRef&) { w.raw("{}"); }
64
+ class NativeBufferObject;
65
+ /// A NativeBuffer: an opaque handle, with no enumerable own properties either.
66
+ inline void jsonWrite(JsonWriter& w, const Ref<NativeBufferObject>&) { w.raw("{}"); }
58
67
  template <class Sig>
59
68
  void jsonWrite(JsonWriter& w, const Fn<Sig>&) {
60
69
  w.raw("null");
@@ -123,7 +132,9 @@ inline void jsonWrite(JsonWriter& w, const Date& d) {
123
132
  if (std::isnan(d->getTime())) w.raw("null");
124
133
  else w.quote(d->toISOString());
125
134
  }
126
- inline void jsonWrite(JsonWriter& w, const Bytes& b) {
135
+ /// A Uint8Array, or the bytes a buffer's borrow lends: its indexes as keys.
136
+ template <class B>
137
+ void jsonWriteBytes(JsonWriter& w, const B& b) {
127
138
  w.raw("{");
128
139
  for (size_t i = 0; i < b.size(); i++) {
129
140
  if (i) w.raw(",");
@@ -133,6 +144,13 @@ inline void jsonWrite(JsonWriter& w, const Bytes& b) {
133
144
  }
134
145
  w.raw("}");
135
146
  }
147
+ inline void jsonWrite(JsonWriter& w, const Bytes& b) { jsonWriteBytes(w, b); }
148
+ template <bool Writable>
149
+ class BasicByteSpan;
150
+ template <bool Writable>
151
+ void jsonWrite(JsonWriter& w, const BasicByteSpan<Writable>& b) {
152
+ jsonWriteBytes(w, b);
153
+ }
136
154
  /// Helper for generated struct writers: one `"name":value` member.
137
155
  template <class T>
138
156
  void jsonField(JsonWriter& w, bool& first, const char* name, const T& v) {