@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
@@ -5,27 +5,40 @@
5
5
  #import <objc/runtime.h>
6
6
  #import <Foundation/Foundation.h>
7
7
 
8
+ #include <functional>
9
+ #include <initializer_list>
10
+ #include <memory>
8
11
  #include <variant>
9
12
 
10
13
  #include "../array.h"
14
+ #include "../bigint.h"
11
15
  #include "../bytes.h"
12
16
  #include "../date.h"
13
17
  #include "../jsstring.h"
14
18
  #include "../map.h"
15
19
  #include "../native.h"
20
+ #include "../report.h"
21
+
22
+ // Lucent code checks the OS before using an API newer than the deployment
23
+ // target (LUCENT3007), with available(), which Clang does not see as a check:
24
+ // the glue that includes this header uses them unwarned.
25
+ #pragma clang diagnostic ignored "-Wunguarded-availability"
26
+ #pragma clang diagnostic ignored "-Wunguarded-availability-new"
16
27
 
17
28
  namespace lucent::objc {
18
29
 
19
- void releaseOnMain(void* retained);
30
+ inline void releaseObject(void* retained) { CFRelease(retained); }
20
31
 
21
- /// A Lucent reference to `obj` (retained). Throws TypeError for nil.
32
+ /// A Lucent reference to `obj` (retained), released on the main thread:
33
+ /// UIKit objects must be deallocated there, and the last Lucent reference
34
+ /// can go on any thread. Throws TypeError for nil.
22
35
  inline NativeRef wrap(id obj, const char* what) {
23
36
  if (!obj) throw Exception(makeError(String::fromLatin1("TypeError"), String::fromUtf8(std::string(what) + " returned nil")));
24
- return NativeRef((__bridge_retained void*)obj, releaseOnMain, nullptr);
37
+ return NativeRef((__bridge_retained void*)obj, releaseObject, nullptr, &ExecutionContext::main());
25
38
  }
26
39
  inline Opt<NativeRef> wrapOpt(id obj) {
27
40
  if (!obj) return Opt<NativeRef>(null);
28
- return NativeRef((__bridge_retained void*)obj, releaseOnMain, nullptr);
41
+ return wrap(obj, "");
29
42
  }
30
43
  inline id unwrap(const NativeRef& r) { return (__bridge id)r.get(); }
31
44
  inline id unwrap(const Opt<NativeRef>& r) { return r.has() ? (__bridge id)r.get().get() : nil; }
@@ -137,10 +150,14 @@ Opt<Dict<V>> fromNSDictionaryOpt(NSDictionary* d, F fromObject) {
137
150
  return d ? Opt<Dict<V>>(fromNSDictionary<V>(d, fromObject, "")) : Opt<Dict<V>>(null);
138
151
  }
139
152
 
140
- /// Applies `f` to a value that may be absent; absent gives a null pointer.
153
+ /// Applies `f` to a value that may be absent; absent gives a null pointer
154
+ /// (of f's result type: a nil object, block or CoreFoundation reference).
141
155
  template <class T, class F>
142
156
  auto ifPresent(const Opt<T>& v, F f) -> decltype(f(v.get())) {
143
- return v.has() ? f(v.get()) : nullptr;
157
+ using R = decltype(f(v.get()));
158
+ if (v.has()) return f(v.get());
159
+
160
+ return R(nullptr);
144
161
  }
145
162
  template <class T, class F>
146
163
  requires(!IsOpt<T>::value)
@@ -197,15 +214,93 @@ inline Opt<Date> asDate(const Opt<NativeRef>& o) {
197
214
  return [x isKindOfClass:[NSDate class]] ? Opt<Date>(fromNSDate(x, "")) : Opt<Date>(null);
198
215
  }
199
216
 
217
+ // --- C structs, as Swift shims pass them: their bytes ----------------------------------
218
+
219
+ template <class S>
220
+ NSData* structBytes(const S& s) {
221
+ return [NSData dataWithBytes:&s length:sizeof(S)];
222
+ }
223
+ template <class S>
224
+ S structFromBytes(id data) {
225
+ S s{};
226
+ [(NSData*)data getBytes:&s length:sizeof(S)];
227
+ return s;
228
+ }
229
+
230
+ // --- Swift enums with payloads ---------------------------------------------------------
231
+
232
+ /// A case of a Swift enum with payloads, as the shims pass it: {"kind": case, field: payload…}.
233
+ inline id swiftCase(NSString* kind, std::initializer_list<std::pair<const char*, id>> payload) {
234
+ NSMutableDictionary* d = [NSMutableDictionary dictionaryWithObject:kind forKey:@"kind"];
235
+ // An absent optional payload is left out.
236
+ for (const auto& [field, value] : payload)
237
+ if (value) d[@(field)] = value;
238
+ return d;
239
+ }
240
+ inline id swiftCase(const char* kind, std::initializer_list<std::pair<const char*, id>> payload) {
241
+ return swiftCase(@(kind), payload);
242
+ }
243
+ /// Cases whose payloads have the same fields share an object type in Lucent: its kind says which.
244
+ inline id swiftCase(const String& kind, std::initializer_list<std::pair<const char*, id>> payload) {
245
+ return swiftCase(toNSString(kind), payload);
246
+ }
247
+ inline bool isSwiftCase(id c, const char* kind) {
248
+ return [((NSDictionary*)c)[@"kind"] isEqualToString:@(kind)];
249
+ }
250
+ /// A payload of a case, nil for an absent optional one (NSNull from Swift).
251
+ inline id swiftPayload(id c, const char* field) {
252
+ id v = ((NSDictionary*)c)[@(field)];
253
+ return v == [NSNull null] ? nil : v;
254
+ }
255
+ [[noreturn]] inline void unknownSwiftCase(id c, const char* type) {
256
+ throw Exception(makeError(String::fromLatin1("TypeError"),
257
+ String::fromUtf8(std::string(type) + " has no case " + [[((NSDictionary*)c)[@"kind"] description] UTF8String])));
258
+ }
259
+
200
260
  // --- errors ----------------------------------------------------------------------------
201
261
 
202
- /// An NSError as a Lucent error: code "<domain>:<code>".
262
+ /// The domain of the NSErrors Lucent errors become (toNSError).
263
+ inline NSString* const lucentErrorDomain = @"Lucent";
264
+
265
+ /// An NSError as a Lucent error: code "<domain>:<code>". One toNSError made
266
+ /// is the Lucent error it was made from again: its name, message and code.
203
267
  inline Error fromNSError(NSError* e, const char* what) {
204
268
  if (!e) throw Exception(makeError(String::fromLatin1("TypeError"), String::fromUtf8(std::string(what) + " got no error")));
269
+
270
+ NSString* name = [e.domain isEqualToString:lucentErrorDomain] ? e.userInfo[@"LucentErrorName"] : nil;
271
+ if (name) {
272
+ Error err = makeError(fromNSString(name, ""), fromNSString(e.localizedDescription ?: @"", ""));
273
+ if (NSString* code = e.userInfo[@"LucentErrorCode"]) err->code = fromNSString(code, "");
274
+ return err;
275
+ }
276
+
205
277
  Error err = makeError(String::fromLatin1("Error"), fromNSString(e.localizedDescription ?: @"", ""));
206
278
  err->code = fromNSString([NSString stringWithFormat:@"%@:%ld", e.domain, (long)e.code], "");
207
279
  return err;
208
280
  }
281
+
282
+ /// A Lucent error as an NSError, for Swift to throw where Lucent code
283
+ /// implements a throwing requirement: its message as the description, its
284
+ /// name and code kept for the way back (fromNSError).
285
+ inline NSError* toNSError(const Error& e) {
286
+ NSMutableDictionary* info = [NSMutableDictionary dictionary];
287
+ info[NSLocalizedDescriptionKey] = toNSString(e->message);
288
+ info[@"LucentErrorName"] = toNSString(e->name);
289
+ if (e->code.has()) info[@"LucentErrorCode"] = toNSString(e->code.get());
290
+
291
+ return [NSError errorWithDomain:lucentErrorDomain code:0 userInfo:info];
292
+ }
293
+
294
+ /// In a catch: what was thrown, as the NSError a Swift requirement's error
295
+ /// out-parameter takes (retained).
296
+ inline void setError(void** out) {
297
+ *out = (__bridge_retained void*)toNSError(currentError(std::current_exception()));
298
+ }
299
+
300
+ /// Reports an error Swift has no way to receive (a requirement that does not throw).
301
+ inline void reportError(const Error& e, const char* where) {
302
+ reportUncaught(std::make_exception_ptr(Exception(e)), where);
303
+ }
209
304
  inline Opt<Error> fromNSErrorOpt(NSError* e) { return e ? Opt<Error>(fromNSError(e, "")) : Opt<Error>(null); }
210
305
 
211
306
  /// A method's NSError** result as a thrown Lucent error.
@@ -216,8 +311,10 @@ inline void throwIfError(NSError* e) {
216
311
  // --- blocks ----------------------------------------------------------------------------
217
312
 
218
313
  /// One Objective-C object per `key` (a Lucent object) while the object is
219
- /// alive: `make` creates it. Callers hold the Lucent lock.
220
- inline id cachedObject(const void* key, id (^make)(void)) {
314
+ /// alive: `make` creates it. Callers hold the Lucent lock. Each `Table`
315
+ /// has its own: a Lucent object's Objective-C and Swift delegates differ.
316
+ template <int Table = 0>
317
+ id cachedObject(const void* key, id (^make)(void)) {
221
318
  static NSMapTable* objects = [NSMapTable strongToWeakObjectsMapTable];
222
319
  NSValue* k = [NSValue valueWithPointer:key];
223
320
  id o = [objects objectForKey:k];
@@ -228,6 +325,43 @@ inline id cachedObject(const void* key, id (^make)(void)) {
228
325
  return o;
229
326
  }
230
327
 
328
+ // --- Lucent classes extending Objective-C classes ------------------------------------
329
+ //
330
+ // A generated subclass of the base stands for each instance: it holds the
331
+ // Lucent object, and Lucent references to the object hold it (nativeOwned).
332
+
333
+ /// The native object of each Lucent object extending an Objective-C class,
334
+ /// by the Lucent object's address; held weakly, as the native one holds it.
335
+ inline NSMapTable* subclassObjects() {
336
+ static NSMapTable* objects = [NSMapTable strongToWeakObjectsMapTable];
337
+ return objects;
338
+ }
339
+
340
+ inline void adoptSubclassObject(const void* lucent, id object) {
341
+ [subclassObjects() setObject:object forKey:[NSValue valueWithPointer:lucent]];
342
+ }
343
+
344
+ inline id subclassObject(const void* lucent) {
345
+ return [subclassObjects() objectForKey:[NSValue valueWithPointer:lucent]];
346
+ }
347
+
348
+ /// The native object `lucent` is, for the members it inherits.
349
+ inline NativeRef nativeOfSubclass(const void* lucent, const char* what) {
350
+ id o = subclassObject(lucent);
351
+ if (!o) throw Exception(makeError(String::fromLatin1("TypeError"), String::fromUtf8(std::string(what) + ": its native object is gone")));
352
+ return wrap(o, what);
353
+ }
354
+
355
+ /// A reference to `o` that holds its native object, which holds `o`: an
356
+ /// aliasing reference, so the two go together. Before the native object is
357
+ /// made (in the constructor, before super(…)), the Lucent object's own.
358
+ template <class T>
359
+ Ref<T> nativeOwned(T* o) {
360
+ id x = subclassObject(o);
361
+ if (!x) return std::static_pointer_cast<T>(static_cast<Object*>(o)->shared_from_this());
362
+ return Ref<T>(std::make_shared<NativeRef>(wrap(x, "")), o);
363
+ }
364
+
231
365
  /// A C++ callable as a heap block of type `Block` (the platform may keep it).
232
366
  template <class Block, class F>
233
367
  Block block(F f) {
@@ -237,16 +371,25 @@ Block block(F f) {
237
371
 
238
372
  // --- out-parameters --------------------------------------------------------------------
239
373
 
240
- /// `new Out()` from lucent:ios: a CFTypeRef a C function writes into.
374
+ /// `new Out()` from lucent:ios: what a method writes through a pointer, and
375
+ /// (for inout pointers) reads first. An object as a CFTypeRef; a number, an
376
+ /// enum or a BOOL as a number; a 64-bit integer as a bigint; a C struct as
377
+ /// the Lucent object it converts to.
241
378
  struct OutSlot {
242
379
  CFTypeRef value = nullptr;
243
380
  bool owned = false;
381
+ Opt<double> number = Opt<double>(null);
382
+ Opt<BigInt> integer = Opt<BigInt>(null);
383
+ std::shared_ptr<Object> record;
244
384
  ~OutSlot() {
245
385
  if (value && owned) CFRelease(value);
246
386
  }
247
387
  };
388
+ inline OutSlot* slotOf(const NativeRef& r) { return static_cast<OutSlot*>(r.get()); }
389
+ inline OutSlot* slotOf(const Opt<NativeRef>& r) { return r.has() ? slotOf(r.get()) : nullptr; }
390
+ /// Released on the main thread, like the object it may hold.
248
391
  inline NativeRef makeOut() {
249
- return NativeRef(new OutSlot(), [](void* p) { delete static_cast<OutSlot*>(p); }, nullptr);
392
+ return NativeRef(new OutSlot(), [](void* p) { delete static_cast<OutSlot*>(p); }, nullptr, &ExecutionContext::main());
250
393
  }
251
394
  /// The slot for a call; `owned` per CoreFoundation's Create/Copy rule.
252
395
  inline CFTypeRef* outSlot(const NativeRef& r, bool owned) {
@@ -257,33 +400,190 @@ inline CFTypeRef* outSlot(const NativeRef& r, bool owned) {
257
400
  return &s->value;
258
401
  }
259
402
  inline CFTypeRef* outSlot(const Opt<NativeRef>& r, bool owned) { return r.has() ? outSlot(r.get(), owned) : nullptr; }
260
- inline Opt<NativeRef> outValue(const NativeRef& r) { return wrapOpt((__bridge id) static_cast<OutSlot*>(r.get())->value); }
403
+ inline Opt<NativeRef> outValue(const NativeRef& r) { return wrapOpt((__bridge id) slotOf(r)->value); }
261
404
 
262
405
  /**
263
- * An NSError** argument writing into an Out<Error>: the method writes an
264
- * autoreleased error, which the slot retains when the call's full
265
- * expression ends (before any autorelease pool drains).
406
+ * An object pointer argument (`NSDate **`, `NSError **`) writing into an
407
+ * Out: the method writes an autoreleased object, which the slot retains
408
+ * when the call's full expression ends (before any autorelease pool
409
+ * drains). `T` is the pointee, `NSDate*` or `id`.
266
410
  */
267
- class ErrorOut {
411
+ template <class T>
412
+ class ObjectOut {
268
413
  public:
269
- explicit ErrorOut(const NativeRef& out) : slot_(static_cast<OutSlot*>(out.get())) {}
270
- explicit ErrorOut(const Opt<NativeRef>& out) : slot_(out.has() ? static_cast<OutSlot*>(out.get().get()) : nullptr) {}
271
- ~ErrorOut() {
414
+ explicit ObjectOut(const NativeRef& out) : ObjectOut(slotOf(out)) {}
415
+ explicit ObjectOut(const Opt<NativeRef>& out) : ObjectOut(slotOf(out)) {}
416
+ ~ObjectOut() {
272
417
  if (!slot_) return;
273
418
  if (slot_->value && slot_->owned) CFRelease(slot_->value);
274
- slot_->value = error_ ? CFBridgingRetain(error_) : nullptr;
419
+ slot_->value = object_ ? CFBridgingRetain(object_) : nullptr;
275
420
  slot_->owned = true;
276
421
  }
277
- ErrorOut(const ErrorOut&) = delete;
278
- ErrorOut& operator=(const ErrorOut&) = delete;
279
- NSError* __autoreleasing* ptr() { return slot_ ? reinterpret_cast<NSError* __autoreleasing*>(&error_) : nullptr; }
422
+ ObjectOut(const ObjectOut&) = delete;
423
+ ObjectOut& operator=(const ObjectOut&) = delete;
424
+ T __autoreleasing* ptr() { return slot_ ? reinterpret_cast<T __autoreleasing*>(&object_) : nullptr; }
425
+
426
+ private:
427
+ explicit ObjectOut(OutSlot* slot) : slot_(slot), object_(slot ? (__bridge T)slot->value : nil) {}
428
+ OutSlot* slot_;
429
+ T __unsafe_unretained object_;
430
+ };
431
+ using ErrorOut = ObjectOut<NSError*>;
432
+
433
+ /// A 64-bit integer (NSInteger, int64_t, NSUInteger): a bigint in Lucent.
434
+ template <class T>
435
+ constexpr bool kWideInteger = std::is_integral_v<T> && sizeof(T) == 8;
436
+
437
+ /// A number, enum or BOOL pointer argument (`CGFloat *`): the slot's number
438
+ /// goes in, and the method's comes out; a 64-bit integer's (`NSInteger *`)
439
+ /// as a bigint, exactly.
440
+ template <class T>
441
+ class NumberOut {
442
+ public:
443
+ explicit NumberOut(const NativeRef& out) : NumberOut(slotOf(out)) {}
444
+ explicit NumberOut(const Opt<NativeRef>& out) : NumberOut(slotOf(out)) {}
445
+
446
+ ~NumberOut() {
447
+ if (!slot_) return;
448
+
449
+ if constexpr (kWideInteger<T>) slot_->integer = Opt<BigInt>(BigInt(value_));
450
+ else slot_->number = Opt<double>(static_cast<double>(value_));
451
+ }
452
+
453
+ NumberOut(const NumberOut&) = delete;
454
+ NumberOut& operator=(const NumberOut&) = delete;
455
+ T* ptr() { return slot_ ? &value_ : nullptr; }
456
+
457
+ private:
458
+ static T in(OutSlot* slot) {
459
+ if constexpr (kWideInteger<T>) {
460
+ return slot && slot->integer.has() ? toNativeInteger<T>(slot->integer.get(), "an Out's value") : T{};
461
+ } else {
462
+ return slot && slot->number.has() ? static_cast<T>(slot->number.get()) : T{};
463
+ }
464
+ }
465
+
466
+ explicit NumberOut(OutSlot* slot) : slot_(slot), value_(in(slot)) {}
467
+
468
+ OutSlot* slot_;
469
+ T value_;
470
+ };
471
+
472
+ /// A C struct pointer argument (`NSRange *`), converted to and from the Lucent object the slot holds.
473
+ template <class T>
474
+ class StructOut {
475
+ public:
476
+ using ToC = std::function<T(const std::shared_ptr<Object>&)>;
477
+ using FromC = std::function<std::shared_ptr<Object>(const T&)>;
478
+ StructOut(const NativeRef& out, ToC toC, FromC fromC) : StructOut(slotOf(out), toC, std::move(fromC)) {}
479
+ StructOut(const Opt<NativeRef>& out, ToC toC, FromC fromC) : StructOut(slotOf(out), toC, std::move(fromC)) {}
480
+ ~StructOut() {
481
+ if (slot_) slot_->record = fromC_(value_);
482
+ }
483
+ StructOut(const StructOut&) = delete;
484
+ StructOut& operator=(const StructOut&) = delete;
485
+ T* ptr() { return slot_ ? &value_ : nullptr; }
280
486
 
281
487
  private:
488
+ StructOut(OutSlot* slot, const ToC& toC, FromC fromC)
489
+ : slot_(slot), value_(slot && slot->record ? toC(slot->record) : T{}), fromC_(std::move(fromC)) {}
282
490
  OutSlot* slot_;
283
- NSError* __unsafe_unretained error_ = nil;
491
+ T value_;
492
+ FromC fromC_;
284
493
  };
285
494
 
286
- inline Opt<Error> outError(const NativeRef& r) { return fromNSErrorOpt((__bridge NSError*) static_cast<OutSlot*>(r.get())->value); }
495
+ inline Opt<Error> outError(const NativeRef& r) { return fromNSErrorOpt((__bridge NSError*) slotOf(r)->value); }
496
+ inline Opt<double> outNumber(const NativeRef& r) { return slotOf(r)->number; }
497
+ inline Opt<BigInt> outBigInt(const NativeRef& r) { return slotOf(r)->integer; }
498
+ inline Opt<bool> outBool(const NativeRef& r) {
499
+ const Opt<double>& n = slotOf(r)->number;
500
+ return n.has() ? Opt<bool>(n.get() != 0) : Opt<bool>(null);
501
+ }
502
+ /// The struct a method wrote, as the Lucent object type `S` it converts to.
503
+ template <class S>
504
+ Opt<Ref<S>> outRecord(const NativeRef& r) {
505
+ const auto& o = slotOf(r)->record;
506
+ return o ? Opt<Ref<S>>(std::static_pointer_cast<S>(o)) : Opt<Ref<S>>(null);
507
+ }
508
+
509
+ inline void setOutObject(const NativeRef& r, id v) {
510
+ OutSlot* s = slotOf(r);
511
+ if (s->value && s->owned) CFRelease(s->value);
512
+ s->value = v ? CFBridgingRetain(v) : nullptr;
513
+ s->owned = true;
514
+ }
515
+ /// A pointer a block receives (`BOOL *stop`) as an Out, holding what it points to.
516
+ template <class T>
517
+ NativeRef outOf(T* p) {
518
+ NativeRef out = makeOut();
519
+ if (!p) return out;
520
+
521
+ if constexpr (kWideInteger<T>) slotOf(out)->integer = Opt<BigInt>(BigInt(*p));
522
+ else slotOf(out)->number = Opt<double>(static_cast<double>(*p));
523
+ return out;
524
+ }
525
+ /// What the Lucent function set, written back through the block's pointer.
526
+ template <class T>
527
+ void writeOut(const NativeRef& out, T* p) {
528
+ if (!p) return;
529
+
530
+ if constexpr (kWideInteger<T>) {
531
+ const Opt<BigInt>& n = slotOf(out)->integer;
532
+ if (n.has()) *p = toNativeInteger<T>(n.get(), "an Out's value");
533
+ } else {
534
+ const Opt<double>& n = slotOf(out)->number;
535
+ if (n.has()) *p = static_cast<T>(n.get());
536
+ }
537
+ }
538
+
539
+ // `out.value = x`: what an inout pointer passes in. Each gives the value assigned.
540
+ inline double setOut(const NativeRef& r, double v) {
541
+ slotOf(r)->number = Opt<double>(v);
542
+ return v;
543
+ }
544
+ inline BigInt setOut(const NativeRef& r, BigInt v) {
545
+ slotOf(r)->integer = Opt<BigInt>(v);
546
+ return v;
547
+ }
548
+ inline bool setOut(const NativeRef& r, bool v) {
549
+ slotOf(r)->number = Opt<double>(v ? 1.0 : 0.0);
550
+ return v;
551
+ }
552
+ template <class S>
553
+ Ref<S> setOut(const NativeRef& r, Ref<S> v) {
554
+ slotOf(r)->record = std::static_pointer_cast<Object>(v);
555
+ return v;
556
+ }
557
+ inline NativeRef setOut(const NativeRef& r, NativeRef v) {
558
+ setOutObject(r, unwrap(v));
559
+ return v;
560
+ }
561
+ inline String setOut(const NativeRef& r, String v) {
562
+ setOutObject(r, toNSString(v));
563
+ return v;
564
+ }
565
+ inline Date setOut(const NativeRef& r, Date v) {
566
+ setOutObject(r, toNSDate(v));
567
+ return v;
568
+ }
569
+ inline Bytes setOut(const NativeRef& r, Bytes v) {
570
+ setOutObject(r, toNSData(v));
571
+ return v;
572
+ }
573
+ template <class T>
574
+ Opt<T> setOut(const NativeRef& r, Opt<T> v) {
575
+ if (v.has()) {
576
+ setOut(r, v.get());
577
+ } else {
578
+ OutSlot* s = slotOf(r);
579
+ if (s->value && s->owned) CFRelease(s->value);
580
+ s->value = nullptr;
581
+ s->number = Opt<double>(null);
582
+ s->integer = Opt<BigInt>(null);
583
+ s->record.reset();
584
+ }
585
+ return v;
586
+ }
287
587
 
288
588
  /// `mainQueue()` from lucent:ios: the main dispatch queue (an OS object).
289
589
  inline NativeRef mainQueue() { return wrap(dispatch_get_main_queue(), "mainQueue"); }
@@ -1,4 +1,4 @@
1
- // Lucent runtime — iOS: the main queue, and releasing objects there.
1
+ // Lucent runtime — iOS: the main queue.
2
2
  #include "ios.h"
3
3
 
4
4
  #import <dispatch/dispatch.h>
@@ -15,17 +15,4 @@ void postToMain(std::function<void()> job) {
15
15
 
16
16
  bool onMainThread() { return [NSThread isMainThread]; }
17
17
 
18
- namespace objc {
19
-
20
- // UIKit objects must be deallocated on the main thread; the last Lucent
21
- // reference can go on any thread.
22
- void releaseOnMain(void* retained) {
23
- if ([NSThread isMainThread]) {
24
- CFRelease(retained);
25
- return;
26
- }
27
- dispatch_async_f(dispatch_get_main_queue(), retained, [](void* p) { CFRelease(p); });
28
- }
29
-
30
- } // namespace objc
31
18
  } // namespace lucent
@@ -0,0 +1,192 @@
1
+ // Lucent runtime — UIKit glue: the scene to present from, presentations,
2
+ // and lifecycle subscriptions, for *.ios.lucent.ts units and native
3
+ // wrappers (Objective-C++ with ARC, iOS only).
4
+ //
5
+ // Nothing here keeps a window or view controller: the presentation context
6
+ // is looked up in UIKit's scenes each time it is asked for, and scenes are
7
+ // named by SceneIds (lucent/lifecycle.h). UIKit's notifications feed the
8
+ // shared Lifecycle from the moment the binary loads; no app or scene
9
+ // delegate is replaced, so the app's own, and other modules', keep working.
10
+ #pragma once
11
+
12
+ #import <UIKit/UIKit.h>
13
+
14
+ #include <functional>
15
+ #include <memory>
16
+ #include <optional>
17
+ #include <type_traits>
18
+
19
+ #include "../lifecycle.h"
20
+ #include "../presentation.h"
21
+ #include "ios.h"
22
+
23
+ namespace lucent::objc {
24
+
25
+ /// Where to present from, as UIKit has it now.
26
+ struct PresentationContext {
27
+ SceneId id = 0;
28
+ UIWindowScene* scene = nil;
29
+ UIWindow* window = nil;
30
+ /// The key window's root view controller, or the view controller it
31
+ /// presents (and so on), minus one being dismissed.
32
+ UIViewController* top = nil;
33
+ };
34
+
35
+ /**
36
+ * The presentation context of the scene the person is using: the most
37
+ * recently activated window scene in the foreground (active before
38
+ * inactive), its key window and top view controller. None while every
39
+ * scene is in the background, or it has no window with a root view
40
+ * controller. On the main thread only (InvalidStateError elsewhere). Keep
41
+ * none of it past the current turn of the main thread: ask again.
42
+ */
43
+ std::optional<PresentationContext> presentationContext();
44
+
45
+ /// The id of a connected scene (given when UIKit connects it, or when it
46
+ /// is first seen); 0 for nil. Main thread.
47
+ SceneId sceneIdOf(UIScene* scene);
48
+
49
+ /// The connected scene with that id, else nil. Main thread.
50
+ UIScene* sceneOf(SceneId id);
51
+
52
+ namespace detail {
53
+
54
+ /**
55
+ * Presents `viewController` from the top view controller of `scene`'s key
56
+ * window, and returns what dismisses it: at once if it is shown, else once
57
+ * UIKit has shown it (some view controllers appear later than the call).
58
+ * `dismissed` runs once if, before that, the person dismisses it (a sheet
59
+ * swiped down) or it is released. Throws InvalidStateError if it is
60
+ * already shown, another presentation is under way, or UIKit throws.
61
+ * Main thread.
62
+ */
63
+ std::function<void()> show(SceneId scene, UIViewController* viewController, bool animated, std::function<void()> dismissed);
64
+
65
+ /// What cancels a presentation the person dismissed: an AbortError.
66
+ Error dismissedError();
67
+
68
+ /// The root scope of the calling context: what Lucent code's presentations
69
+ /// and subscriptions belong to.
70
+ std::shared_ptr<Scope> callerScope();
71
+
72
+ } // namespace detail
73
+
74
+ /**
75
+ * For native wrappers: presents, on the main thread, the view controller
76
+ * `build` returns, from the presentation context now, as an operation under
77
+ * `scope` (lucent/presentation.h: it settles once, and whatever settles it
78
+ * dismisses the view controller). `build` runs here, before anything is
79
+ * shown, and wires the view controller's own outcome (a delegate, a
80
+ * completion handler) to the operation; it may settle it at once, and then
81
+ * nothing is shown. The person dismissing it, or its release, cancels the
82
+ * operation with an AbortError; so does its scene disconnecting.
83
+ */
84
+ template <class T>
85
+ std::shared_ptr<Operation<T>> presentOperation(const std::shared_ptr<Scope>& scope, Opt<AbortSignal> signal,
86
+ const std::function<UIViewController*(const std::shared_ptr<Operation<T>>&)>& build,
87
+ bool animated = true) {
88
+ std::optional<PresentationContext> context = presentationContext();
89
+ SceneId scene = context ? context->id : 0;
90
+
91
+ return presentIn<T>(Lifecycle::shared(), scene, scope, signal, [&](const std::shared_ptr<Operation<T>>& op) -> std::function<void()> {
92
+ UIViewController* viewController = build(op);
93
+ if (op->state() != OperationState::Pending) return {};
94
+ if (!viewController) returnedNil("present's view controller");
95
+
96
+ std::weak_ptr<Operation<T>> weak = op;
97
+ return detail::show(scene, viewController, animated, [weak] {
98
+ if (auto op = weak.lock()) op->cancel(detail::dismissedError());
99
+ });
100
+ });
101
+ }
102
+
103
+ namespace detail {
104
+ template <class T>
105
+ struct ResolveOf {
106
+ using type = Fn<void(T)>;
107
+ };
108
+ template <>
109
+ struct ResolveOf<void> {
110
+ using type = Fn<void()>;
111
+ };
112
+ } // namespace detail
113
+
114
+ /// What `present` gives its function to settle with a value.
115
+ template <class T>
116
+ using Resolve = typename detail::ResolveOf<T>::type;
117
+
118
+ /**
119
+ * `present(build, signal)` from lucent:ios: on the main thread, holding the
120
+ * Lucent lock (build is module code), presents the view controller `build`
121
+ * returns (presentOperation) under the calling context's root scope, and
122
+ * settles the promise, which belongs to the calling context, with the
123
+ * outcome.
124
+ */
125
+ template <class T>
126
+ Promise<T> present(Fn<NativeRef(Resolve<T>, Fn<void(Error)>)> build, Opt<AbortSignal> signal = {}) {
127
+ Promise<T> promise;
128
+ std::shared_ptr<Scope> scope = detail::callerScope();
129
+
130
+ postToMain([promise, build = std::move(build), signal, scope]() mutable {
131
+ LucentScope lucent;
132
+
133
+ std::shared_ptr<Operation<T>> op;
134
+ try {
135
+ op = presentOperation<T>(scope, signal, [&](const std::shared_ptr<Operation<T>>& op) -> UIViewController* {
136
+ std::weak_ptr<Operation<T>> weak = op;
137
+
138
+ Resolve<T> resolve;
139
+ if constexpr (std::is_void_v<T>) {
140
+ resolve = Resolve<T>([weak] {
141
+ if (auto op = weak.lock()) op->succeed();
142
+ });
143
+ } else {
144
+ resolve = Resolve<T>([weak](T value) {
145
+ if (auto op = weak.lock()) op->succeed(std::move(value));
146
+ });
147
+ }
148
+
149
+ Fn<void(Error)> reject([weak](Error error) {
150
+ if (auto op = weak.lock()) op->fail(std::move(error));
151
+ });
152
+
153
+ id made = unwrap(build(resolve, reject));
154
+ if (made && ![made isKindOfClass:[UIViewController class]])
155
+ throw Exception(makeError(String::fromLatin1("TypeError"), String::fromLatin1("present's function must return a UIViewController")));
156
+
157
+ return (UIViewController*)made;
158
+ });
159
+ } catch (...) {
160
+ promise.reject(currentError(std::current_exception()));
161
+ return;
162
+ }
163
+
164
+ op->onSettled([promise](const typename Operation<T>::Outcome& outcome) {
165
+ if (outcome.state != OperationState::Succeeded) {
166
+ promise.reject(outcome.error);
167
+ } else if constexpr (std::is_void_v<T>) {
168
+ promise.resolve(undefined);
169
+ } else {
170
+ promise.resolve(*outcome.value);
171
+ }
172
+ });
173
+ });
174
+
175
+ return promise;
176
+ }
177
+
178
+ /**
179
+ * `onAppEvent(event, listener, signal)` from lucent:ios: calls `listener`
180
+ * on each of UIApplication's `event` notifications ("didBecomeActive"…),
181
+ * on the main thread holding the Lucent lock, until the returned function
182
+ * is called or `signal` aborts. What it throws is reported; UIKit and the
183
+ * other observers go on.
184
+ */
185
+ Fn<void()> onAppEvent(const String& event, Fn<void()> listener, Opt<AbortSignal> signal = {});
186
+
187
+ /// `onSceneEvent(event, listener, signal)` from lucent:ios: as onAppEvent,
188
+ /// for UIScene's notifications ("willConnect"…); `listener` gets the
189
+ /// scene's session's persistent identifier.
190
+ Fn<void()> onSceneEvent(const String& event, Fn<void(String)> listener, Opt<AbortSignal> signal = {});
191
+
192
+ } // namespace lucent::objc