@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,454 @@
1
+ // Lucent runtime — UIKit glue: UIKit's lifecycle notifications into the
2
+ // shared Lifecycle, the presentation context, and showing view controllers.
3
+ #include "ios_ui.h"
4
+
5
+ #import <objc/runtime.h>
6
+
7
+ #include <array>
8
+ #include <string>
9
+
10
+ using namespace lucent;
11
+
12
+ namespace {
13
+
14
+ // --- the notifications, as data -----------------------------------------------------------
15
+
16
+ struct AppNotification {
17
+ const char* name;
18
+ AppEvent event;
19
+ NSNotificationName __unsafe_unretained notification;
20
+ };
21
+
22
+ struct SceneNotification {
23
+ const char* name;
24
+ SceneEvent event;
25
+ NSNotificationName __unsafe_unretained notification;
26
+ };
27
+
28
+ const std::array<AppNotification, 6>& appNotifications() {
29
+ static const std::array<AppNotification, 6> table = {{
30
+ {"didBecomeActive", AppEvent::DidBecomeActive, UIApplicationDidBecomeActiveNotification},
31
+ {"willResignActive", AppEvent::WillResignActive, UIApplicationWillResignActiveNotification},
32
+ {"didEnterBackground", AppEvent::DidEnterBackground, UIApplicationDidEnterBackgroundNotification},
33
+ {"willEnterForeground", AppEvent::WillEnterForeground, UIApplicationWillEnterForegroundNotification},
34
+ {"didReceiveMemoryWarning", AppEvent::DidReceiveMemoryWarning, UIApplicationDidReceiveMemoryWarningNotification},
35
+ {"willTerminate", AppEvent::WillTerminate, UIApplicationWillTerminateNotification},
36
+ }};
37
+ return table;
38
+ }
39
+
40
+ const std::array<SceneNotification, 6>& sceneNotifications() {
41
+ static const std::array<SceneNotification, 6> table = {{
42
+ {"willConnect", SceneEvent::WillConnect, UISceneWillConnectNotification},
43
+ {"didDisconnect", SceneEvent::DidDisconnect, UISceneDidDisconnectNotification},
44
+ {"didActivate", SceneEvent::DidActivate, UISceneDidActivateNotification},
45
+ {"willDeactivate", SceneEvent::WillDeactivate, UISceneWillDeactivateNotification},
46
+ {"willEnterForeground", SceneEvent::WillEnterForeground, UISceneWillEnterForegroundNotification},
47
+ {"didEnterBackground", SceneEvent::DidEnterBackground, UISceneDidEnterBackgroundNotification},
48
+ }};
49
+ return table;
50
+ }
51
+
52
+ /// The entry of `table` named `name`; TypeError for any other name.
53
+ template <class Table>
54
+ const auto& named(const Table& table, const String& name, const char* what) {
55
+ std::string wanted = name.toUtf8();
56
+ for (const auto& entry : table)
57
+ if (wanted == entry.name) return entry;
58
+
59
+ throw Exception(makeError(String::fromLatin1("TypeError"), String::fromUtf8(std::string("Unknown ") + what + ": " + wanted)));
60
+ }
61
+
62
+ SceneState stateOf(UIScene* scene) {
63
+ // Only window scenes can present.
64
+ if (![scene isKindOfClass:[UIWindowScene class]]) return SceneState::Unattached;
65
+
66
+ switch (scene.activationState) {
67
+ case UISceneActivationStateForegroundActive:
68
+ return SceneState::ForegroundActive;
69
+ case UISceneActivationStateForegroundInactive:
70
+ return SceneState::ForegroundInactive;
71
+ case UISceneActivationStateBackground:
72
+ return SceneState::Background;
73
+ default:
74
+ return SceneState::Unattached;
75
+ }
76
+ }
77
+
78
+ void requireMain(const char* what) {
79
+ if (onMainThread()) return;
80
+
81
+ throwError(String::fromLatin1("InvalidStateError"), String::fromUtf8(std::string(what) + " is only available on the main thread"));
82
+ }
83
+
84
+ } // namespace
85
+
86
+ // --- scenes and their ids -------------------------------------------------------------------
87
+
88
+ /// Tags a scene with its SceneId. It goes with the scene: if UIKit never
89
+ /// said the scene disconnected, its going says so.
90
+ @interface LucentSceneTag : NSObject
91
+ @property(nonatomic, readonly) SceneId sceneId;
92
+ - (instancetype)initWithSceneId:(SceneId)sceneId;
93
+ @end
94
+
95
+ @implementation LucentSceneTag
96
+
97
+ - (instancetype)initWithSceneId:(SceneId)sceneId {
98
+ if ((self = [super init])) _sceneId = sceneId;
99
+ return self;
100
+ }
101
+
102
+ - (void)dealloc {
103
+ SceneId id = _sceneId;
104
+ lucent::detail::runOn(&ExecutionContext::main(), [id] {
105
+ if (Lifecycle::shared().sceneScope(id)) Lifecycle::shared().report(id, SceneEvent::DidDisconnect);
106
+ });
107
+ }
108
+
109
+ @end
110
+
111
+ namespace {
112
+
113
+ char tagKey;
114
+
115
+ /// Connected scenes by id, held weakly. Main thread.
116
+ NSMapTable<NSNumber*, UIScene*>* scenesById() {
117
+ static NSMapTable<NSNumber*, UIScene*>* table = [NSMapTable strongToWeakObjectsMapTable];
118
+ return table;
119
+ }
120
+
121
+ SceneId taggedId(UIScene* scene) {
122
+ LucentSceneTag* tag = objc_getAssociatedObject(scene, &tagKey);
123
+ return tag ? tag.sceneId : 0;
124
+ }
125
+
126
+ /// The scene's id, connecting it first if it has none.
127
+ SceneId tag(UIScene* scene) {
128
+ if (SceneId known = taggedId(scene)) return known;
129
+
130
+ SceneId id = Lifecycle::shared().connect();
131
+ objc_setAssociatedObject(scene, &tagKey, [[LucentSceneTag alloc] initWithSceneId:id], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
132
+ [scenesById() setObject:scene forKey:@(id)];
133
+
134
+ return id;
135
+ }
136
+
137
+ /// Scenes connected before Lucent heard of them.
138
+ void adoptConnectedScenes() {
139
+ UIApplication* app = [UIApplication sharedApplication];
140
+ if (!app) return;
141
+
142
+ for (UIScene* scene in app.connectedScenes) tag(scene);
143
+ }
144
+
145
+ void sceneNotified(UIScene* scene, SceneEvent event) {
146
+ if (![scene isKindOfClass:[UIScene class]]) return;
147
+
148
+ SceneId id = tag(scene);
149
+ Lifecycle::shared().report(id, event);
150
+
151
+ if (event != SceneEvent::DidDisconnect) return;
152
+
153
+ // Should UIKit connect this object again, it is a new scene.
154
+ [scenesById() removeObjectForKey:@(id)];
155
+ objc_setAssociatedObject(scene, &tagKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
156
+ }
157
+
158
+ UIWindow* keyWindowOf(UIWindowScene* scene) {
159
+ if (scene.keyWindow) return scene.keyWindow;
160
+
161
+ // Not key yet (still activating): its first visible window at the normal level.
162
+ for (UIWindow* window in scene.windows)
163
+ if (!window.hidden && window.windowLevel == UIWindowLevelNormal) return window;
164
+
165
+ return nil;
166
+ }
167
+
168
+ UIViewController* topOf(UIViewController* root) {
169
+ UIViewController* top = root;
170
+
171
+ while (top.presentedViewController && !top.presentedViewController.isBeingDismissed) top = top.presentedViewController;
172
+
173
+ return top;
174
+ }
175
+
176
+ std::optional<objc::PresentationContext> contextOf(SceneId id) {
177
+ auto* scene = (UIWindowScene*)[scenesById() objectForKey:@(id)];
178
+ if (![scene isKindOfClass:[UIWindowScene class]]) return std::nullopt;
179
+
180
+ UIWindow* window = keyWindowOf(scene);
181
+ UIViewController* top = topOf(window.rootViewController);
182
+ if (!top) return std::nullopt;
183
+
184
+ return objc::PresentationContext{id, scene, window, top};
185
+ }
186
+
187
+ } // namespace
188
+
189
+ /// Follows UIKit's lifecycle notifications from the moment the binary loads,
190
+ /// before any scene connects, on the main thread (where UIKit posts them;
191
+ /// one posted elsewhere is passed there). The observers stay for the
192
+ /// process.
193
+ @interface LucentLifecycleObserver : NSObject
194
+ @end
195
+
196
+ @implementation LucentLifecycleObserver
197
+
198
+ + (void)load {
199
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
200
+
201
+ for (const auto& n : sceneNotifications()) {
202
+ SceneEvent event = n.event;
203
+ [center addObserverForName:n.notification
204
+ object:nil
205
+ queue:nil
206
+ usingBlock:^(NSNotification* note) {
207
+ id scene = note.object;
208
+ lucent::detail::runOn(&ExecutionContext::main(), [scene, event] { sceneNotified(scene, event); });
209
+ }];
210
+ }
211
+
212
+ for (const auto& n : appNotifications()) {
213
+ AppEvent event = n.event;
214
+ [center addObserverForName:n.notification
215
+ object:nil
216
+ queue:nil
217
+ usingBlock:^(NSNotification*) {
218
+ lucent::detail::runOn(&ExecutionContext::main(), [event] { Lifecycle::shared().report(event); });
219
+ }];
220
+ }
221
+ }
222
+
223
+ @end
224
+
225
+ // --- presenting ----------------------------------------------------------------------------
226
+
227
+ /// Watches a presented view controller for the person dismissing it (as its
228
+ /// presentation controller's delegate, if it had none) and for its release
229
+ /// (associated with it), until disarmed.
230
+ @interface LucentPresentationWatch : NSObject <UIAdaptivePresentationControllerDelegate>
231
+ - (instancetype)initWithDismissed:(std::function<void()>)dismissed;
232
+ - (void)disarm;
233
+ @end
234
+
235
+ @implementation LucentPresentationWatch {
236
+ std::function<void()> _dismissed;
237
+ }
238
+
239
+ - (instancetype)initWithDismissed:(std::function<void()>)dismissed {
240
+ if ((self = [super init])) _dismissed = std::move(dismissed);
241
+ return self;
242
+ }
243
+
244
+ - (void)fire {
245
+ std::function<void()> dismissed = std::move(_dismissed);
246
+ _dismissed = nullptr;
247
+ if (dismissed) dismissed();
248
+ }
249
+
250
+ - (void)disarm {
251
+ _dismissed = nullptr;
252
+ }
253
+
254
+ - (void)presentationControllerDidDismiss:(UIPresentationController*)presentationController {
255
+ // Settling may unwatch, releasing this object: keep it until it returns.
256
+ LucentPresentationWatch* watch = self;
257
+ [watch fire];
258
+ }
259
+
260
+ - (void)dealloc {
261
+ [self fire];
262
+ }
263
+
264
+ @end
265
+
266
+ namespace {
267
+
268
+ char watchKey;
269
+
270
+ /// A popover needs an anchor (UIActivityViewController on iPad): the middle
271
+ /// of the presenting view, unless the view controller has its own.
272
+ void anchorPopover(UIViewController* viewController, UIViewController* from) {
273
+ if (viewController.modalPresentationStyle != UIModalPresentationPopover) return;
274
+
275
+ UIPopoverPresentationController* popover = viewController.popoverPresentationController;
276
+ if (!popover || popover.sourceView || popover.barButtonItem) return;
277
+ if (@available(iOS 16.0, *)) {
278
+ if (popover.sourceItem) return;
279
+ }
280
+
281
+ CGRect bounds = from.view.bounds;
282
+ popover.sourceView = from.view;
283
+ popover.sourceRect = CGRectMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds), 0, 0);
284
+ popover.permittedArrowDirections = 0;
285
+ }
286
+
287
+ void unwatch(UIViewController* viewController, LucentPresentationWatch* watch) {
288
+ [watch disarm];
289
+
290
+ if (viewController.presentationController.delegate == watch) viewController.presentationController.delegate = nil;
291
+ if (objc_getAssociatedObject(viewController, &watchKey) == watch)
292
+ objc_setAssociatedObject(viewController, &watchKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
293
+ }
294
+
295
+ [[noreturn]] void refuse(const std::string& why) {
296
+ throwError(String::fromLatin1("InvalidStateError"), String::fromUtf8(why));
297
+ }
298
+
299
+ } // namespace
300
+
301
+ namespace lucent::objc {
302
+
303
+ std::optional<PresentationContext> presentationContext() {
304
+ requireMain("The presentation context");
305
+ adoptConnectedScenes();
306
+
307
+ std::optional<SceneId> picked = presentingScene(Lifecycle::shared().scenes(), [](SceneId id) {
308
+ return stateOf([scenesById() objectForKey:@(id)]);
309
+ });
310
+
311
+ return picked ? contextOf(*picked) : std::nullopt;
312
+ }
313
+
314
+ SceneId sceneIdOf(UIScene* scene) {
315
+ requireMain("A scene's id");
316
+ return scene ? tag(scene) : 0;
317
+ }
318
+
319
+ UIScene* sceneOf(SceneId id) {
320
+ requireMain("A scene");
321
+ return [scenesById() objectForKey:@(id)];
322
+ }
323
+
324
+ std::function<void()> detail::show(SceneId scene, UIViewController* viewController, bool animated, std::function<void()> dismissed) {
325
+ requireMain("Presenting");
326
+
327
+ std::optional<PresentationContext> context = contextOf(scene);
328
+ if (!context) refuse("The scene has no window to present from");
329
+
330
+ if (viewController.presentingViewController || viewController.isBeingPresented || viewController.parentViewController ||
331
+ viewController.viewIfLoaded.window)
332
+ refuse("The view controller is already shown");
333
+
334
+ // UIKit logs, rather than throws, when it refuses to present during
335
+ // another presentation or dismissal: refuse first.
336
+ UIViewController* top = context->top;
337
+ if (top.presentedViewController || top.isBeingPresented || top.isBeingDismissed) refuse("Another presentation is under way");
338
+
339
+ anchorPopover(viewController, top);
340
+
341
+ LucentPresentationWatch* watch = [[LucentPresentationWatch alloc] initWithDismissed:std::move(dismissed)];
342
+ objc_setAssociatedObject(viewController, &watchKey, watch, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
343
+
344
+ UIPresentationController* controller = viewController.presentationController;
345
+ if (controller && !controller.delegate) controller.delegate = watch;
346
+
347
+ // Some view controllers (UIActivityViewController) are shown later than
348
+ // the call, once their content is ready: a dismissal asked for before
349
+ // then waits for it. Main thread only.
350
+ struct Presenting {
351
+ bool shown = false;
352
+ bool withdrawn = false;
353
+ };
354
+ auto presenting = std::make_shared<Presenting>();
355
+
356
+ __weak UIViewController* weakViewController = viewController;
357
+ void (^dismiss)(void) = ^{
358
+ UIViewController* shown = weakViewController;
359
+ UIViewController* presenter = shown.presentingViewController;
360
+ if (presenter && !shown.isBeingDismissed) [presenter dismissViewControllerAnimated:animated completion:nil];
361
+ };
362
+
363
+ NSString* failure = nil;
364
+ @try {
365
+ [top presentViewController:viewController
366
+ animated:animated
367
+ completion:^{
368
+ presenting->shown = true;
369
+ if (presenting->withdrawn) dismiss();
370
+ }];
371
+ } @catch (NSException* e) {
372
+ failure = e.reason ?: e.name;
373
+ }
374
+
375
+ if (failure) {
376
+ unwatch(viewController, watch);
377
+ refuse(failure.UTF8String);
378
+ }
379
+
380
+ __weak LucentPresentationWatch* weakWatch = watch;
381
+ return [weakViewController, weakWatch, presenting, dismiss] {
382
+ presenting->withdrawn = true;
383
+
384
+ UIViewController* shown = weakViewController;
385
+ LucentPresentationWatch* watching = weakWatch;
386
+ if (shown && watching) unwatch(shown, watching);
387
+
388
+ if (presenting->shown) dismiss();
389
+ };
390
+ }
391
+
392
+ Error detail::dismissedError() {
393
+ return makeError(String::fromLatin1("AbortError"), String::fromLatin1("The view controller was dismissed"));
394
+ }
395
+
396
+ std::shared_ptr<Scope> detail::callerScope() { return ExecutionContext::of(ExecutionContext::currentRef()).root(); }
397
+
398
+ namespace {
399
+
400
+ /// A subscription as the function that ends it, also ended by `signal`.
401
+ Fn<void()> stopper(std::shared_ptr<Resource> subscription, Opt<AbortSignal> signal) {
402
+ AbortSignal abort = signal.has() ? signal.get() : nullptr;
403
+ uint64_t listener = 0;
404
+
405
+ if (abort) {
406
+ std::weak_ptr<Resource> weak = subscription;
407
+ listener = abort->add([weak] {
408
+ if (auto s = weak.lock()) s->close();
409
+ });
410
+ }
411
+
412
+ std::weak_ptr<AbortSignalObject> weakSignal = abort;
413
+ return Fn<void()>([subscription, weakSignal, listener] {
414
+ subscription->close();
415
+ if (auto s = weakSignal.lock(); s && listener) s->remove(listener);
416
+ });
417
+ }
418
+
419
+ bool abortedAlready(const Opt<AbortSignal>& signal) { return signal.has() && signal.get() && signal.get()->aborted.load(); }
420
+
421
+ /// The session's persistent identifier of a connected scene, else "".
422
+ String identifierOf(SceneId id) {
423
+ NSString* identifier = [scenesById() objectForKey:@(id)].session.persistentIdentifier;
424
+ return identifier ? fromNSString(identifier, "persistentIdentifier") : String();
425
+ }
426
+
427
+ } // namespace
428
+
429
+ Fn<void()> onAppEvent(const String& event, Fn<void()> listener, Opt<AbortSignal> signal) {
430
+ AppEvent e = named(appNotifications(), event, "app event").event;
431
+ if (abortedAlready(signal)) return Fn<void()>([] {});
432
+
433
+ auto subscription = Lifecycle::shared().subscribe(
434
+ e, [listener] { callNow([&] { listener(); }); }, detail::callerScope());
435
+
436
+ return stopper(std::move(subscription), signal);
437
+ }
438
+
439
+ Fn<void()> onSceneEvent(const String& event, Fn<void(String)> listener, Opt<AbortSignal> signal) {
440
+ SceneEvent e = named(sceneNotifications(), event, "scene event").event;
441
+ if (abortedAlready(signal)) return Fn<void()>([] {});
442
+
443
+ auto subscription = Lifecycle::shared().subscribe(
444
+ e,
445
+ [listener](SceneId scene) {
446
+ String identifier = identifierOf(scene);
447
+ callNow([&] { listener(identifier); });
448
+ },
449
+ detail::callerScope());
450
+
451
+ return stopper(std::move(subscription), signal);
452
+ }
453
+
454
+ } // namespace lucent::objc
@@ -0,0 +1,190 @@
1
+ // Lucent runtime — what the glue of SwiftUI components shares (iOS,
2
+ // Objective-C++ with ARC).
3
+ //
4
+ // A SwiftUI component's body is Swift, generated from the JSX its setup
5
+ // returns; the rest of the setup is Lucent code. The
6
+ // generated Swift calls back into it through C functions: the body's
7
+ // actions (the setup's functions a tap… calls) by index, with the
8
+ // arguments the Swift gives them, the Lucent code `withAnimation`
9
+ // animates, and a change of the body's size. All run on the main thread;
10
+ // actions and animations in Lucent's main context.
11
+ #pragma once
12
+
13
+ #import <Foundation/Foundation.h>
14
+
15
+ #include <cstddef>
16
+ #include <cstdint>
17
+ #include <exception>
18
+ #include <functional>
19
+ #include <initializer_list>
20
+ #include <memory>
21
+ #include <type_traits>
22
+ #include <utility>
23
+ #include <vector>
24
+
25
+ #include "../array.h"
26
+ #include "../function.h"
27
+ #include "../items.h"
28
+ #include "../native.h"
29
+ #include "../view.h"
30
+ #include "ios.h"
31
+
32
+ namespace lucent::swiftui {
33
+
34
+ /// How the Swift side runs action `action` of `context`, with `args`: a
35
+ /// retained NSArray of its arguments, which the call takes.
36
+ using Invoke = void (*)(void* context, int32_t action, void* args);
37
+ /// How it lets `context` go, when its view goes.
38
+ using Release = void (*)(void* context);
39
+ /// How it reports that the body's size changed.
40
+ using Resized = void (*)(void* context);
41
+ /// The Lucent code withAnimation runs.
42
+ using Body = void (*)(void* context);
43
+
44
+ /**
45
+ * The actions a body calls, by index, and the content of their mount. The
46
+ * Swift side holds them through a context (context()) until its view
47
+ * goes; the mount clears them when it ends, so what they captured goes
48
+ * with it.
49
+ */
50
+ struct Actions {
51
+ explicit Actions(std::weak_ptr<ui::Content> content) : content(std::move(content)) {}
52
+
53
+ std::vector<std::function<void(NSArray* args)>> list;
54
+ /// The mount's content, which a change of the body's size marks.
55
+ std::weak_ptr<ui::Content> content;
56
+ };
57
+
58
+ /// A context the Swift side owns (release() lets it go), holding `actions`.
59
+ inline void* context(const std::shared_ptr<Actions>& actions) {
60
+ return new std::shared_ptr<Actions>(actions);
61
+ }
62
+
63
+ /// Runs an action, which Swift calls on the main thread, in the main context.
64
+ inline void invoke(void* context, int32_t action, void* args) {
65
+ NSArray* given = (__bridge_transfer NSArray*)args;
66
+ auto actions = *static_cast<std::shared_ptr<Actions>*>(context);
67
+
68
+ if (action < 0 || static_cast<std::size_t>(action) >= actions->list.size()) return;
69
+
70
+ // A copy: the action may end the mount, which clears the list.
71
+ auto run = actions->list[static_cast<std::size_t>(action)];
72
+
73
+ if (run) callNowIn(ExecutionContext::main(), [&] { run(given); });
74
+ }
75
+
76
+ namespace detail {
77
+
78
+ /// An action's argument `i`, as the Lucent function takes it; missing, or NSNull, is none.
79
+ template <class T>
80
+ struct Arg;
81
+
82
+ template <>
83
+ struct Arg<double> {
84
+ static double from(id v) { return [(NSNumber*)v doubleValue]; }
85
+ };
86
+
87
+ template <>
88
+ struct Arg<bool> {
89
+ static bool from(id v) { return [(NSNumber*)v boolValue]; }
90
+ };
91
+
92
+ template <>
93
+ struct Arg<String> {
94
+ static String from(id v) { return objc::fromNSString((NSString*)v, "a SwiftUI argument"); }
95
+ };
96
+
97
+ template <class T>
98
+ struct Arg<Opt<T>> {
99
+ static Opt<T> from(id v) { return v && v != [NSNull null] ? Opt<T>(Arg<T>::from(v)) : Opt<T>(null); }
100
+ };
101
+
102
+ template <class T>
103
+ T arg(NSArray* args, std::size_t i) {
104
+ return Arg<T>::from(i < args.count ? args[i] : nil);
105
+ }
106
+
107
+ template <class... A, std::size_t... I>
108
+ void call(const Fn<void(A...)>& f, NSArray* args, std::index_sequence<I...>) {
109
+ f(arg<A>(args, I)...);
110
+ }
111
+
112
+ } // namespace detail
113
+
114
+ /// The setup function `f` as an action: the Swift side's arguments, in order, are its parameters.
115
+ template <class... A>
116
+ std::function<void(NSArray*)> action(Fn<void(A...)> f) {
117
+ return [f = std::move(f)](NSArray* args) { detail::call(f, args, std::index_sequence_for<A...>{}); };
118
+ }
119
+
120
+ inline void release(void* context) { delete static_cast<std::shared_ptr<Actions>*>(context); }
121
+
122
+ /**
123
+ * The body's size changed, SwiftUI's own layout too (no Lucent code ran):
124
+ * its mount's content is marked, so the host measures it again on a later
125
+ * turn of the main thread. Swift calls it on the main thread.
126
+ */
127
+ inline void resized(void* context) {
128
+ ui::invalidateSize((*static_cast<std::shared_ptr<Actions>*>(context))->content);
129
+ }
130
+
131
+ // --- plain data, encoded for the Swift side (emit/toolkit-values.ts) ----------------------
132
+
133
+ inline id value(double v) { return @(v); }
134
+ inline id value(bool v) { return @(v); }
135
+ inline id value(const String& v) { return objc::toNSString(v); }
136
+
137
+ /// NSNull when `v` is null or undefined, else `each` of its value.
138
+ template <class T, class F>
139
+ id optional(const Opt<T>& v, F each) {
140
+ return v.has() ? each(v.get()) : [NSNull null];
141
+ }
142
+
143
+ template <class T, class F>
144
+ id array(const Array<T>& a, F each) {
145
+ NSMutableArray* out = [NSMutableArray arrayWithCapacity:a.size()];
146
+
147
+ for (std::size_t i = 0; i < a.size(); i++) [out addObject:each(a.at(i))];
148
+
149
+ return out;
150
+ }
151
+
152
+ /// An object's fields, in order.
153
+ inline id record(std::initializer_list<id> fields) {
154
+ return [NSArray arrayWithObjects:fields.begin() count:fields.size()];
155
+ }
156
+
157
+ /// A retained object the Swift side returns (a hosting controller), as Lucent's reference.
158
+ inline NativeRef adopt(void* retained, const char* what) {
159
+ return objc::wrap((__bridge_transfer id)retained, what);
160
+ }
161
+
162
+ /**
163
+ * withAnimation: `shim` runs SwiftUI's withAnimation around a call of
164
+ * `body`, now, with the animation's Lucent values (`args`, as its C
165
+ * types). Its writes are one transaction, whose effects (the values the
166
+ * body shows) run before withAnimation returns, so SwiftUI animates what
167
+ * they change. What `body` throws is thrown here, once Swift is done.
168
+ */
169
+ template <class F, class... A>
170
+ void animate(void (*shim)(void* context, Body body, A...), F body, std::type_identity_t<A>... args) {
171
+ struct Call {
172
+ F* body;
173
+ std::exception_ptr error;
174
+ } call{&body, nullptr};
175
+
176
+ shim(&call, [](void* context) {
177
+ auto* c = static_cast<Call*>(context);
178
+
179
+ // Nothing may unwind through Swift's frames.
180
+ try {
181
+ ui::mainGraph()->transaction([&] { (*c->body)(); });
182
+ } catch (...) {
183
+ c->error = std::current_exception();
184
+ }
185
+ }, args...);
186
+
187
+ if (call.error) std::rethrow_exception(call.error);
188
+ }
189
+
190
+ } // namespace lucent::swiftui
@@ -0,0 +1,11 @@
1
+ #include "presentation.h"
2
+
3
+ namespace lucent {
4
+
5
+ Error sceneGoneError() { return makeError(String::fromLatin1("AbortError"), String::fromLatin1("The scene disconnected")); }
6
+
7
+ Error noSceneError() {
8
+ return makeError(String::fromLatin1("InvalidStateError"), String::fromLatin1("No scene is in the foreground to present from"));
9
+ }
10
+
11
+ } // namespace lucent