@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.
- package/README.md +97 -0
- package/app.plugin.js +46 -10
- package/dist/{bench-CR3E7e2e.js → bench-eEez4jhQ.js} +10 -6
- package/dist/{build-CAIrRSYA.js → build-CpUjeHFz.js} +1 -1
- package/dist/{build-check-QozuNJm5.js → build-check-BA1ZEp9s.js} +28 -13
- package/dist/{check-1SCmYlqA.js → check-BZx3FgI6.js} +1 -1
- package/dist/cli.js +62 -13
- package/dist/{codes-CIuwXt1S.js → codes-D-6LxUqD.js} +163 -7
- package/dist/compiler.js +4 -4
- package/dist/{dashboard-B-NzjjaN.js → dashboard-CC-HXhcd.js} +2 -2
- package/dist/{dev-BYWMP7oC.js → dev-DI5yL8zx.js} +62 -23
- package/dist/{diagnostic-BM9sXmwl.js → diagnostic-BvwLZQS1.js} +3 -1
- package/dist/{doctor-C0PrfWT6.js → doctor-CTAX3euc.js} +1 -1
- package/dist/{doctor-DdR4JnVc.js → doctor-CrgOAV5L.js} +1 -1
- package/dist/{explain--vV1oaRl.js → explain-Byit07F-.js} +1 -1
- package/dist/{packages-HyskKIsY.js → packages-CSmKeaAh.js} +2 -33
- package/dist/pipeline-BM_VM2X_.js +700 -0
- package/dist/project-BKNoTEEp.js +342 -0
- package/dist/sdk-coverage-iiv3JqMp.js +113 -0
- package/dist/sdk-diff-ByJ1Gw-F.js +109 -0
- package/dist/sdk-lock-BQn7IyRs.js +38 -0
- package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-Bv2pNega.js} +2 -2
- package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-CPGJm_Ne.js} +2 -2
- package/dist/{sdk-show-CkrdlqlP.js → sdk-show-BUkLqRnA.js} +5 -4
- package/dist/sdk-usage-0ZFMAYg1.js +95 -0
- package/dist/src-ChnmWLwn.js +40851 -0
- package/dist/trace-Bi4hkmPx.js +139 -0
- package/lib/core.cjs +368 -1
- package/lib/sdk/android.d.ts +54 -1
- package/lib/sdk/compose.d.ts +94 -0
- package/lib/sdk/compose.schemas.json.gz +0 -0
- package/lib/sdk/core.d.ts +201 -0
- package/lib/sdk/ios.d.ts +75 -2
- package/lib/sdk/ui.d.ts +129 -0
- package/metro/transformer.cjs +11 -2
- package/package.json +4 -3
- package/runtime/cpp/lucent/abort.cpp +59 -11
- package/runtime/cpp/lucent/abort.h +22 -6
- package/runtime/cpp/lucent/array.h +12 -2
- package/runtime/cpp/lucent/async.h +101 -21
- package/runtime/cpp/lucent/bigint.cpp +898 -0
- package/runtime/cpp/lucent/bigint.h +302 -0
- package/runtime/cpp/lucent/buffer.cpp +228 -0
- package/runtime/cpp/lucent/buffer.h +328 -0
- package/runtime/cpp/lucent/builtins.cpp +4 -1
- package/runtime/cpp/lucent/bytes.h +21 -2
- package/runtime/cpp/lucent/callback.cpp +29 -0
- package/runtime/cpp/lucent/callback.h +230 -0
- package/runtime/cpp/lucent/compute.cpp +370 -0
- package/runtime/cpp/lucent/compute.h +455 -0
- package/runtime/cpp/lucent/conversions.h +111 -0
- package/runtime/cpp/lucent/core.h +16 -5
- package/runtime/cpp/lucent/equality.h +107 -28
- package/runtime/cpp/lucent/execution.cpp +416 -0
- package/runtime/cpp/lucent/execution.h +270 -0
- package/runtime/cpp/lucent/extension.cpp +97 -0
- package/runtime/cpp/lucent/extension.h +210 -0
- package/runtime/cpp/lucent/helpers.h +12 -5
- package/runtime/cpp/lucent/items.h +52 -0
- package/runtime/cpp/lucent/jserror.cpp +9 -0
- package/runtime/cpp/lucent/jserror.h +11 -0
- package/runtime/cpp/lucent/jsi/convert.cpp +138 -0
- package/runtime/cpp/lucent/jsi/convert.h +128 -59
- package/runtime/cpp/lucent/jsi/host.cpp +213 -35
- package/runtime/cpp/lucent/jsi/host.h +116 -16
- package/runtime/cpp/lucent/json.h +19 -1
- package/runtime/cpp/lucent/lifecycle.cpp +260 -0
- package/runtime/cpp/lucent/lifecycle.h +124 -0
- package/runtime/cpp/lucent/lucent.h +7 -0
- package/runtime/cpp/lucent/map.h +41 -7
- package/runtime/cpp/lucent/native.cpp +35 -47
- package/runtime/cpp/lucent/native.h +116 -26
- package/runtime/cpp/lucent/number.cpp +38 -38
- package/runtime/cpp/lucent/number.h +33 -0
- package/runtime/cpp/lucent/operation.h +65 -0
- package/runtime/cpp/lucent/ops.h +14 -6
- package/runtime/cpp/lucent/platform/android.cpp +223 -10
- package/runtime/cpp/lucent/platform/android.h +227 -4
- package/runtime/cpp/lucent/platform/android_activity.cpp +258 -0
- package/runtime/cpp/lucent/platform/android_requests.h +140 -0
- package/runtime/cpp/lucent/platform/compose.h +256 -0
- package/runtime/cpp/lucent/platform/ios.h +325 -25
- package/runtime/cpp/lucent/platform/ios.mm +1 -14
- package/runtime/cpp/lucent/platform/ios_ui.h +192 -0
- package/runtime/cpp/lucent/platform/ios_ui.mm +454 -0
- package/runtime/cpp/lucent/platform/swiftui.h +190 -0
- package/runtime/cpp/lucent/presentation.cpp +11 -0
- package/runtime/cpp/lucent/presentation.h +109 -0
- package/runtime/cpp/lucent/reactive.cpp +406 -0
- package/runtime/cpp/lucent/reactive.h +566 -0
- package/runtime/cpp/lucent/report.cpp +40 -0
- package/runtime/cpp/lucent/report.h +16 -0
- package/runtime/cpp/lucent/resource.cpp +83 -0
- package/runtime/cpp/lucent/resource.h +75 -0
- package/runtime/cpp/lucent/scheduler.cpp +38 -124
- package/runtime/cpp/lucent/scheduler.h +102 -83
- package/runtime/cpp/lucent/scope.cpp +308 -0
- package/runtime/cpp/lucent/scope.h +452 -0
- package/runtime/cpp/lucent/sizing.h +263 -0
- package/runtime/cpp/lucent/slots.h +231 -0
- package/runtime/cpp/lucent/trace.cpp +539 -0
- package/runtime/cpp/lucent/trace.h +251 -0
- package/runtime/cpp/lucent/transport.cpp +9 -0
- package/runtime/cpp/lucent/transport.h +341 -0
- package/runtime/cpp/lucent/view.cpp +123 -0
- package/runtime/cpp/lucent/view.h +157 -0
- package/runtime/cpp/rn/LucentComponentView.h +121 -0
- package/runtime/cpp/rn/LucentComponentView.mm +479 -0
- package/runtime/cpp/rn/LucentModule.cpp +37 -7
- package/runtime/cpp/rn/LucentModule.h +3 -0
- package/runtime/cpp/rn/LucentViewChildren.h +80 -0
- package/runtime/cpp/rn/LucentViewChildren.mm +211 -0
- package/runtime/cpp/rn/LucentViewRegistry.h +62 -0
- package/runtime/cpp/rn/LucentViewRequests.cpp +91 -0
- package/runtime/cpp/rn/LucentViewRequests.h +104 -0
- package/runtime/cpp/rn/LucentViewSizing.h +345 -0
- package/runtime/cpp/rn/LucentViewSlots.h +242 -0
- package/runtime/cpp/rn/LucentViewValues.h +125 -0
- package/runtime/cpp/rn/LucentViews.h +440 -0
- package/runtime/cpp/rn/LucentViewsAndroid.cpp +439 -0
- package/runtime/cpp/rn/LucentViewsAndroid.h +138 -0
- package/runtime/cpp/third_party/dragonbox/LICENSE-Boost +23 -0
- package/runtime/cpp/third_party/dragonbox/README.md +16 -0
- package/runtime/cpp/third_party/dragonbox/dragonbox.h +4440 -0
- package/runtime/js/index.d.ts +11 -1
- package/runtime/js/index.js +84 -8
- package/runtime/js/views.d.ts +38 -0
- package/runtime/js/views.js +226 -0
- package/runtime/native/android/CMakeLists.txt +4 -1
- package/runtime/native/android/src/compose/java/dev/lucent/compose/CompositionRules.kt +44 -0
- package/runtime/native/android/src/compose/java/dev/lucent/compose/LucentComposition.kt +161 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentActivities.java +292 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentChildren.java +174 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentHostView.java +232 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentInitializer.java +46 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +4 -3
- package/runtime/native/android/src/main/java/dev/lucent/LucentRequestActivity.java +92 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentSlotView.java +102 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViewManager.java +149 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViewManagers.java +16 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViews.java +44 -0
- package/runtime/test/jsi/harness.cpp +9 -0
- package/schemas/build.schema.json +96 -1
- package/schemas/check.schema.json +56 -1
- package/schemas/lucent.schema.json +495 -9
- package/schemas/sdk-diff.schema.json +70 -0
- package/schemas/sdk-lock.schema.json +67 -0
- package/ts-plugin/index.js +85 -4
- package/dist/pipeline-CH7-2cxu.js +0 -232
- package/dist/project-D8tEet0k.js +0 -150
- package/dist/sdk-coverage-BoW_3Efd.js +0 -64
- package/dist/src-BD7f1_aZ.js +0 -11493
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// Lucent runtime — Android: the app's Activities for Lucent code (the
|
|
2
|
+
// current one, activity results, permissions, lifecycle events), through
|
|
3
|
+
// dev.lucent.LucentActivities. Built only into Android apps.
|
|
4
|
+
#ifdef __ANDROID__
|
|
5
|
+
|
|
6
|
+
#include <array>
|
|
7
|
+
#include <cstdint>
|
|
8
|
+
#include <map>
|
|
9
|
+
#include <mutex>
|
|
10
|
+
#include <string>
|
|
11
|
+
#include <utility>
|
|
12
|
+
#include <vector>
|
|
13
|
+
|
|
14
|
+
#include "android.h"
|
|
15
|
+
#include "android_requests.h"
|
|
16
|
+
|
|
17
|
+
namespace lucent::jni {
|
|
18
|
+
|
|
19
|
+
namespace {
|
|
20
|
+
|
|
21
|
+
/// Lifecycle events, numbered as LucentActivities numbers them.
|
|
22
|
+
constexpr std::array<const char*, 7> kEvents = {"created", "started", "resumed", "paused", "stopped", "destroyed", "newIntent"};
|
|
23
|
+
|
|
24
|
+
jclass activities();
|
|
25
|
+
|
|
26
|
+
void forgetOnPlatform(OperationId id) {
|
|
27
|
+
JNIEnv* e = env();
|
|
28
|
+
static jmethodID forget = staticMethod(activities(), "forget", "(J)V");
|
|
29
|
+
|
|
30
|
+
e->CallStaticVoidMethod(activities(), forget, static_cast<jlong>(id));
|
|
31
|
+
check(e);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Requests<NativeRef>& results() {
|
|
35
|
+
static auto* requests = new Requests<NativeRef>(forgetOnPlatform);
|
|
36
|
+
return *requests;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Requests<Array<bool>>& permissionRequests() {
|
|
40
|
+
static auto* requests = new Requests<Array<bool>>(forgetOnPlatform);
|
|
41
|
+
return *requests;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// What each permission request asked for, to answer in its order.
|
|
45
|
+
std::mutex askedMutex;
|
|
46
|
+
std::map<OperationId, Array<String>>& asked() {
|
|
47
|
+
static auto* m = new std::map<OperationId, Array<String>>();
|
|
48
|
+
return *m;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
struct Subscription {
|
|
52
|
+
size_t event;
|
|
53
|
+
ActivityHandler handler;
|
|
54
|
+
/// Where the handler runs: the context that subscribed.
|
|
55
|
+
ContextRef context;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
std::mutex subscriptionsMutex;
|
|
59
|
+
std::map<uint64_t, Subscription>& subscriptions() {
|
|
60
|
+
static auto* m = new std::map<uint64_t, Subscription>();
|
|
61
|
+
return *m;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Runs a native method's body; a Lucent error is reported, never thrown into Java.
|
|
65
|
+
template <class F>
|
|
66
|
+
void guarded(const char* what, F f) {
|
|
67
|
+
try {
|
|
68
|
+
f();
|
|
69
|
+
} catch (...) {
|
|
70
|
+
reportUncaught(std::current_exception(), what);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
void JNICALL onResult(JNIEnv* e, jclass, jlong id, jint resultCode, jobject data) {
|
|
75
|
+
guarded("activity result", [&] {
|
|
76
|
+
LocalFrame frame(e);
|
|
77
|
+
jclass cls = findClass("android/app/Instrumentation$ActivityResult");
|
|
78
|
+
static jmethodID init = method(cls, "<init>", "(ILandroid/content/Intent;)V");
|
|
79
|
+
|
|
80
|
+
NativeRef result = wrap(e, e->NewObject(cls, init, resultCode, data), "Instrumentation.ActivityResult");
|
|
81
|
+
results().deliver(static_cast<OperationId>(id), std::move(result));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
void JNICALL onPermissions(JNIEnv* e, jclass, jlong id, jobjectArray permissions, jintArray grants) {
|
|
86
|
+
guarded("permission result", [&] {
|
|
87
|
+
Array<String> requested;
|
|
88
|
+
{
|
|
89
|
+
std::lock_guard<std::mutex> g(askedMutex);
|
|
90
|
+
auto it = asked().find(static_cast<OperationId>(id));
|
|
91
|
+
if (it == asked().end()) return;
|
|
92
|
+
|
|
93
|
+
requested = it->second;
|
|
94
|
+
asked().erase(it);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A dismissed dialog answers with nothing: none granted.
|
|
98
|
+
std::map<std::u16string, bool> granted;
|
|
99
|
+
if (permissions && grants) {
|
|
100
|
+
Array<String> names = fromStringArray(e, permissions, "permissions");
|
|
101
|
+
Array<double> codes = fromIntArray(e, grants, "grants");
|
|
102
|
+
|
|
103
|
+
for (size_t i = 0; i < names.size() && i < codes.size(); i++)
|
|
104
|
+
granted[names.at(i).toUtf16()] = codes.at(i) == 0; // PackageManager.PERMISSION_GRANTED
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
Array<bool> answer;
|
|
108
|
+
for (size_t i = 0; i < requested.size(); i++) {
|
|
109
|
+
auto it = granted.find(requested.at(i).toUtf16());
|
|
110
|
+
answer.push(it != granted.end() && it->second);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
permissionRequests().deliver(static_cast<OperationId>(id), std::move(answer));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
void JNICALL onFailed(JNIEnv* e, jclass, jlong id, jstring code, jstring message) {
|
|
118
|
+
guarded("activity request", [&] {
|
|
119
|
+
Error error = makeError(fromJString(e, message, "message"));
|
|
120
|
+
error->code = fromJString(e, code, "code");
|
|
121
|
+
auto op = static_cast<OperationId>(id);
|
|
122
|
+
|
|
123
|
+
if (!results().fail(op, error)) permissionRequests().fail(op, error);
|
|
124
|
+
|
|
125
|
+
std::lock_guard<std::mutex> g(askedMutex);
|
|
126
|
+
asked().erase(op);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
void JNICALL onEvent(JNIEnv* e, jclass, jint kind, jobject activity, jobject intent) {
|
|
131
|
+
guarded("activity event", [&] {
|
|
132
|
+
std::vector<Subscription> matching;
|
|
133
|
+
{
|
|
134
|
+
std::lock_guard<std::mutex> g(subscriptionsMutex);
|
|
135
|
+
for (const auto& [id, s] : subscriptions())
|
|
136
|
+
if (s.event == static_cast<size_t>(kind)) matching.push_back(s);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (matching.empty()) return;
|
|
140
|
+
|
|
141
|
+
// Global references, released on whatever thread drops the last one.
|
|
142
|
+
NativeRef a = wrap(e, e->NewLocalRef(activity), "Activity");
|
|
143
|
+
Opt<NativeRef> i = wrapOpt(e, intent ? e->NewLocalRef(intent) : nullptr);
|
|
144
|
+
|
|
145
|
+
// A turn of the subscribing context (the legacy module context's holds the Lucent lock).
|
|
146
|
+
for (auto& s : matching)
|
|
147
|
+
ExecutionContext::of(s.context).post([handler = s.handler, a, i] {
|
|
148
|
+
try {
|
|
149
|
+
handler(a, i);
|
|
150
|
+
} catch (...) {
|
|
151
|
+
reportUncaught(std::current_exception(), "activity event handler");
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
jclass activities() {
|
|
158
|
+
static jclass cls = [] {
|
|
159
|
+
JNIEnv* e = env();
|
|
160
|
+
jclass c = findClass("dev/lucent/LucentActivities");
|
|
161
|
+
JNINativeMethod natives[] = {
|
|
162
|
+
{const_cast<char*>("onResult"), const_cast<char*>("(JILandroid/content/Intent;)V"), reinterpret_cast<void*>(onResult)},
|
|
163
|
+
{const_cast<char*>("onPermissions"), const_cast<char*>("(J[Ljava/lang/String;[I)V"), reinterpret_cast<void*>(onPermissions)},
|
|
164
|
+
{const_cast<char*>("onFailed"), const_cast<char*>("(JLjava/lang/String;Ljava/lang/String;)V"), reinterpret_cast<void*>(onFailed)},
|
|
165
|
+
{const_cast<char*>("onEvent"), const_cast<char*>("(ILandroid/app/Activity;Landroid/content/Intent;)V"), reinterpret_cast<void*>(onEvent)},
|
|
166
|
+
};
|
|
167
|
+
e->RegisterNatives(c, natives, 4);
|
|
168
|
+
check(e);
|
|
169
|
+
return c;
|
|
170
|
+
}();
|
|
171
|
+
return cls;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// The scope a request belongs to: the calling context's root.
|
|
175
|
+
std::shared_ptr<Scope> requestScope() {
|
|
176
|
+
ExecutionContext* context = ExecutionContext::current();
|
|
177
|
+
return (context ? *context : ExecutionContext::legacy()).root();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
} // namespace
|
|
181
|
+
|
|
182
|
+
Opt<NativeRef> currentActivity() {
|
|
183
|
+
JNIEnv* e = env();
|
|
184
|
+
static jmethodID current = staticMethod(activities(), "current", "()Landroid/app/Activity;");
|
|
185
|
+
|
|
186
|
+
jobject a = e->CallStaticObjectMethod(activities(), current);
|
|
187
|
+
check(e);
|
|
188
|
+
return wrapOpt(e, a);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
Promise<NativeRef> startActivityForResult(const NativeRef& intent, Opt<AbortSignal> signal) {
|
|
192
|
+
return results().start(requestScope(), std::move(signal), [&](OperationId id) {
|
|
193
|
+
JNIEnv* e = env();
|
|
194
|
+
static jmethodID start = staticMethod(activities(), "startForResult", "(JLandroid/content/Intent;)V");
|
|
195
|
+
|
|
196
|
+
e->CallStaticVoidMethod(activities(), start, static_cast<jlong>(id), unwrap(intent));
|
|
197
|
+
check(e);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
Promise<Array<bool>> requestPermissions(const Array<String>& permissions, Opt<AbortSignal> signal) {
|
|
202
|
+
return permissionRequests().start(requestScope(), std::move(signal), [&](OperationId id) {
|
|
203
|
+
{
|
|
204
|
+
std::lock_guard<std::mutex> g(askedMutex);
|
|
205
|
+
asked()[id] = permissions;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
JNIEnv* e = env();
|
|
209
|
+
LocalFrame frame(e);
|
|
210
|
+
static jmethodID request = staticMethod(activities(), "requestPermissions", "(J[Ljava/lang/String;)V");
|
|
211
|
+
|
|
212
|
+
e->CallStaticVoidMethod(activities(), request, static_cast<jlong>(id), toStringArray(e, permissions));
|
|
213
|
+
check(e);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
Fn<void()> onActivityEvent(const String& event, ActivityHandler handler) {
|
|
218
|
+
std::string name = event.toUtf8();
|
|
219
|
+
size_t kind = 0;
|
|
220
|
+
while (kind < kEvents.size() && name != kEvents[kind]) kind++;
|
|
221
|
+
if (kind == kEvents.size()) throwTypeError(("Unknown Activity event: " + name).c_str());
|
|
222
|
+
|
|
223
|
+
static std::atomic<uint64_t> next{1};
|
|
224
|
+
uint64_t id = next++;
|
|
225
|
+
|
|
226
|
+
bool first;
|
|
227
|
+
{
|
|
228
|
+
std::lock_guard<std::mutex> g(subscriptionsMutex);
|
|
229
|
+
first = subscriptions().empty();
|
|
230
|
+
subscriptions().emplace(id, Subscription{kind, std::move(handler), ExecutionContext::currentRef()});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
JNIEnv* e = env();
|
|
234
|
+
static jmethodID listen = staticMethod(activities(), "listen", "(Z)V");
|
|
235
|
+
if (first) {
|
|
236
|
+
e->CallStaticVoidMethod(activities(), listen, JNI_TRUE);
|
|
237
|
+
check(e);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return Fn<void()>([id] {
|
|
241
|
+
bool last;
|
|
242
|
+
{
|
|
243
|
+
std::lock_guard<std::mutex> g(subscriptionsMutex);
|
|
244
|
+
if (!subscriptions().erase(id)) return;
|
|
245
|
+
last = subscriptions().empty();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (last) {
|
|
249
|
+
JNIEnv* env_ = env();
|
|
250
|
+
env_->CallStaticVoidMethod(activities(), listen, JNI_FALSE);
|
|
251
|
+
check(env_);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
} // namespace lucent::jni
|
|
257
|
+
|
|
258
|
+
#endif // __ANDROID__
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Lucent runtime — requests Android answers later: an activity result, a
|
|
2
|
+
// permission request. Each is an Operation under a scope, keyed by its id,
|
|
3
|
+
// which the platform side echoes back with the answer.
|
|
4
|
+
//
|
|
5
|
+
// The answer may come from any thread (Android's main thread): it settles
|
|
6
|
+
// the promise on the context that asked. A request cancelled first (its
|
|
7
|
+
// signal aborted, its scope disposed) is forgotten on the platform side,
|
|
8
|
+
// which drops the answer if it still comes: cancelling does not close a
|
|
9
|
+
// dialog the platform already shows. No JNI here, so the host tests run it.
|
|
10
|
+
#pragma once
|
|
11
|
+
|
|
12
|
+
#include <functional>
|
|
13
|
+
#include <map>
|
|
14
|
+
#include <memory>
|
|
15
|
+
#include <mutex>
|
|
16
|
+
#include <utility>
|
|
17
|
+
|
|
18
|
+
#include "../abort.h"
|
|
19
|
+
#include "../async.h"
|
|
20
|
+
#include "../execution.h"
|
|
21
|
+
#include "../scope.h"
|
|
22
|
+
|
|
23
|
+
namespace lucent::jni {
|
|
24
|
+
|
|
25
|
+
template <class T>
|
|
26
|
+
class Requests {
|
|
27
|
+
public:
|
|
28
|
+
/// Tells the platform side to forget a request (cancelled before its answer).
|
|
29
|
+
using Forget = std::function<void(OperationId)>;
|
|
30
|
+
/// Sends a request to the platform side, under its id.
|
|
31
|
+
using Launch = std::function<void(OperationId)>;
|
|
32
|
+
|
|
33
|
+
explicit Requests(Forget forget) : forget_(std::move(forget)) {}
|
|
34
|
+
|
|
35
|
+
Requests(const Requests&) = delete;
|
|
36
|
+
Requests& operator=(const Requests&) = delete;
|
|
37
|
+
|
|
38
|
+
/// Starts a request under `scope` and returns its promise, settled on the
|
|
39
|
+
/// calling context. An aborted `signal` (now or later) or a disposed scope
|
|
40
|
+
/// cancels it with an AbortError; `launch` throwing rejects it.
|
|
41
|
+
Promise<T> start(const std::shared_ptr<Scope>& scope, Opt<AbortSignal> signal, const Launch& launch) {
|
|
42
|
+
Promise<T> promise;
|
|
43
|
+
AbortSignal aborts = signal.has() ? signal.get() : nullptr;
|
|
44
|
+
|
|
45
|
+
Opt<Error> abortedBy;
|
|
46
|
+
if (aborts && aborts->aborted.load()) abortedBy = aborts->reason;
|
|
47
|
+
|
|
48
|
+
auto op = Operation<T>::start(
|
|
49
|
+
scope,
|
|
50
|
+
[&](const std::shared_ptr<Operation<T>>& started) -> std::function<void()> {
|
|
51
|
+
OperationId id = started->token().operation;
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
std::lock_guard<std::mutex> g(m_);
|
|
55
|
+
pending_.emplace(id, started);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
launch(id);
|
|
59
|
+
|
|
60
|
+
uint64_t listener = 0;
|
|
61
|
+
if (aborts) {
|
|
62
|
+
std::weak_ptr<Operation<T>> weak = started;
|
|
63
|
+
std::weak_ptr<AbortSignalObject> from = aborts;
|
|
64
|
+
listener = aborts->add([weak, from] {
|
|
65
|
+
auto op = weak.lock();
|
|
66
|
+
auto signal = from.lock();
|
|
67
|
+
if (op && signal) op->cancel(signal->reason);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// After the outcome: unlisten, and forget a request the platform did not answer.
|
|
72
|
+
std::weak_ptr<Operation<T>> weak = started;
|
|
73
|
+
return [this, id, aborts, listener, weak] {
|
|
74
|
+
if (aborts && listener) aborts->remove(listener);
|
|
75
|
+
|
|
76
|
+
bool answered;
|
|
77
|
+
{
|
|
78
|
+
std::lock_guard<std::mutex> g(m_);
|
|
79
|
+
answered = pending_.erase(id) == 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
auto op = weak.lock();
|
|
83
|
+
if (!answered && op && op->state() == OperationState::Cancelled) forget_(id);
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
abortedBy);
|
|
87
|
+
|
|
88
|
+
// A launch that threw failed the operation; it never reached the platform.
|
|
89
|
+
if (op->state() != OperationState::Pending) {
|
|
90
|
+
std::lock_guard<std::mutex> g(m_);
|
|
91
|
+
pending_.erase(op->token().operation);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
op->onSettled([promise](const typename Operation<T>::Outcome& outcome) {
|
|
95
|
+
if (outcome.state == OperationState::Succeeded) {
|
|
96
|
+
promise.resolve(*outcome.value);
|
|
97
|
+
} else {
|
|
98
|
+
promise.reject(outcome.error);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return promise;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// The platform's answer: true if it settled a pending request, false
|
|
106
|
+
/// for one already settled or cancelled (a late answer, dropped).
|
|
107
|
+
bool deliver(OperationId id, ::lucent::detail::Stored<T> value) {
|
|
108
|
+
auto op = take(id);
|
|
109
|
+
return op && op->succeed(std::move(value));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// The platform could not answer (no Activity, say).
|
|
113
|
+
bool fail(OperationId id, Error error) {
|
|
114
|
+
auto op = take(id);
|
|
115
|
+
return op && op->fail(std::move(error));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Requests the platform has not answered.
|
|
119
|
+
size_t pending() const {
|
|
120
|
+
std::lock_guard<std::mutex> g(m_);
|
|
121
|
+
return pending_.size();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private:
|
|
125
|
+
std::shared_ptr<Operation<T>> take(OperationId id) {
|
|
126
|
+
std::lock_guard<std::mutex> g(m_);
|
|
127
|
+
auto it = pending_.find(id);
|
|
128
|
+
if (it == pending_.end()) return nullptr;
|
|
129
|
+
|
|
130
|
+
auto op = it->second.lock();
|
|
131
|
+
pending_.erase(it);
|
|
132
|
+
return op;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const Forget forget_;
|
|
136
|
+
mutable std::mutex m_;
|
|
137
|
+
std::map<OperationId, std::weak_ptr<Operation<T>>> pending_;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
} // namespace lucent::jni
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// Lucent runtime — a component's Compose content (lucent:compose), for the
|
|
2
|
+
// generated glue of *.android.lucent.tsx components.
|
|
3
|
+
//
|
|
4
|
+
// The content's body is generated Kotlin: a composable reading a state
|
|
5
|
+
// holder (`<registration>State`), and a host object (`<registration>Host`)
|
|
6
|
+
// that gives it to the runtime's host of Compose content
|
|
7
|
+
// (dev.lucent.compose.LucentComposition), which makes the ComposeView
|
|
8
|
+
// showing it with the context of the view hosting the mount. The
|
|
9
|
+
// component's setup, in C++, writes the holder: an effect per value the
|
|
10
|
+
// body reads (a prop, a signal, a value computed by setup code) sets its
|
|
11
|
+
// Compose state, and each setup function the body calls (an action) is a
|
|
12
|
+
// Kotlin function object whose calls enter the main context. Everything
|
|
13
|
+
// here runs on the main thread, in the main context.
|
|
14
|
+
#pragma once
|
|
15
|
+
|
|
16
|
+
#include <jni.h>
|
|
17
|
+
|
|
18
|
+
#include <initializer_list>
|
|
19
|
+
#include <memory>
|
|
20
|
+
#include <string>
|
|
21
|
+
#include <type_traits>
|
|
22
|
+
#include <utility>
|
|
23
|
+
|
|
24
|
+
#include "../array.h"
|
|
25
|
+
#include "../execution.h"
|
|
26
|
+
#include "../function.h"
|
|
27
|
+
#include "../items.h"
|
|
28
|
+
#include "android.h"
|
|
29
|
+
|
|
30
|
+
namespace lucent::compose {
|
|
31
|
+
|
|
32
|
+
namespace detail {
|
|
33
|
+
|
|
34
|
+
/// The runtime's host of Compose content (runtime/native/android/src/compose), and how a host object makes one.
|
|
35
|
+
constexpr const char* kComposition = "dev/lucent/compose/LucentComposition";
|
|
36
|
+
constexpr const char* kCreate = "(Landroid/content/Context;Ljava/lang/Object;)Ldev/lucent/compose/LucentComposition;";
|
|
37
|
+
|
|
38
|
+
inline jobject box(JNIEnv* e, double v) { return jni::boxDouble(e, v); }
|
|
39
|
+
inline jobject box(JNIEnv* e, bool v) { return jni::boxBoolean(e, v); }
|
|
40
|
+
inline jobject box(JNIEnv* e, const String& v) { return jni::toJString(e, v); }
|
|
41
|
+
|
|
42
|
+
template <class T>
|
|
43
|
+
jobject box(JNIEnv* e, const Opt<T>& v) {
|
|
44
|
+
return v.has() ? box(e, v.get()) : nullptr;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// A Kotlin function's argument (boxed), as the Lucent function takes it.
|
|
48
|
+
template <class T>
|
|
49
|
+
struct Unbox;
|
|
50
|
+
|
|
51
|
+
template <>
|
|
52
|
+
struct Unbox<double> {
|
|
53
|
+
static double from(JNIEnv* e, jobject o) { return jni::unboxNumber(e, o); }
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
template <>
|
|
57
|
+
struct Unbox<bool> {
|
|
58
|
+
static bool from(JNIEnv* e, jobject o) { return jni::unboxBoolean(e, o); }
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
template <>
|
|
62
|
+
struct Unbox<String> {
|
|
63
|
+
static String from(JNIEnv* e, jobject o) { return jni::fromJString(e, static_cast<jstring>(o), "a Compose argument"); }
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
template <class T>
|
|
67
|
+
struct Unbox<Opt<T>> {
|
|
68
|
+
static Opt<T> from(JNIEnv* e, jobject o) { return o ? Opt<T>(Unbox<T>::from(e, o)) : Opt<T>(null); }
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/// `invoke(Ljava/lang/Object;…)`: how NativeProxy keys a FunctionN's method.
|
|
72
|
+
template <size_t N>
|
|
73
|
+
const char* invokeKey() {
|
|
74
|
+
static const std::string key = [] {
|
|
75
|
+
std::string k = "invoke(";
|
|
76
|
+
for (size_t i = 0; i < N; i++) k += "Ljava/lang/Object;";
|
|
77
|
+
return k + ")";
|
|
78
|
+
}();
|
|
79
|
+
return key.c_str();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
} // namespace detail
|
|
83
|
+
|
|
84
|
+
// --- plain data, encoded for the Kotlin side (emit/toolkit-values.ts) -----------------------
|
|
85
|
+
//
|
|
86
|
+
// Each returns a local reference; the Holder's setters free them.
|
|
87
|
+
|
|
88
|
+
inline jobject value(double v) { return jni::boxDouble(jni::env(), v); }
|
|
89
|
+
inline jobject value(bool v) { return jni::boxBoolean(jni::env(), v); }
|
|
90
|
+
inline jobject value(const String& v) { return jni::toJString(jni::env(), v); }
|
|
91
|
+
|
|
92
|
+
/// null when `v` is null or undefined, else `each` of its value.
|
|
93
|
+
template <class T, class F>
|
|
94
|
+
jobject optional(const Opt<T>& v, F each) {
|
|
95
|
+
return v.has() ? each(v.get()) : nullptr;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// An Object[] of `each` of the elements.
|
|
99
|
+
template <class T, class F>
|
|
100
|
+
jobject array(const Array<T>& a, F each) {
|
|
101
|
+
return jni::toObjectArray(jni::env(), a, "java/lang/Object", each);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// An object's fields, in order: an Object[] taking the references.
|
|
105
|
+
inline jobject record(std::initializer_list<jobject> fields) {
|
|
106
|
+
JNIEnv* e = jni::env();
|
|
107
|
+
jobjectArray out = e->NewObjectArray(static_cast<jsize>(fields.size()), jni::findClass("java/lang/Object"), nullptr);
|
|
108
|
+
jsize i = 0;
|
|
109
|
+
|
|
110
|
+
for (jobject f : fields) {
|
|
111
|
+
e->SetObjectArrayElement(out, i++, f);
|
|
112
|
+
if (f) e->DeleteLocalRef(f);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* One mount's content: its Kotlin state holder, and the ComposeView once
|
|
120
|
+
* made. Copies share it. Made, written and disposed on the main context.
|
|
121
|
+
*/
|
|
122
|
+
class Holder {
|
|
123
|
+
public:
|
|
124
|
+
/// A new holder of `stateClass`, shown by `hostClass` (JNI class names).
|
|
125
|
+
static Holder create(const char* stateClass, const char* hostClass) {
|
|
126
|
+
JNIEnv* e = jni::env();
|
|
127
|
+
jni::LocalFrame frame(e);
|
|
128
|
+
jclass cls = jni::findClass(stateClass);
|
|
129
|
+
jobject state = e->NewObject(cls, jni::method(cls, "<init>", "()V"));
|
|
130
|
+
jni::check(e);
|
|
131
|
+
|
|
132
|
+
Holder h;
|
|
133
|
+
jmethodID set = jni::method(cls, "set", "(ILjava/lang/Object;)V");
|
|
134
|
+
jmethodID setAction = jni::method(cls, "setAction", "(ILjava/lang/Object;)V");
|
|
135
|
+
jmethodID listSetter = jni::method(cls, "setList", "(ILjava/lang/Object;)V");
|
|
136
|
+
h.s_ = std::make_shared<Shared>(Shared{jni::wrap(e, state, stateClass), set, setAction, listSetter, {}, {}, hostClass});
|
|
137
|
+
return h;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Sets the Compose state `slot` holds: the body recomposes where it reads it.
|
|
141
|
+
template <class T>
|
|
142
|
+
requires(!std::is_invocable_v<T>)
|
|
143
|
+
void set(int slot, const T& value) const {
|
|
144
|
+
JNIEnv* e = jni::env();
|
|
145
|
+
jni::LocalFrame frame(e);
|
|
146
|
+
call(e, s_->set, slot, detail::box(e, value));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// Sets it to what `encode` makes (a local reference, freed here): encoded plain data.
|
|
150
|
+
template <class F>
|
|
151
|
+
requires std::is_invocable_r_v<jobject, F>
|
|
152
|
+
void set(int slot, F encode) const {
|
|
153
|
+
JNIEnv* e = jni::env();
|
|
154
|
+
jni::LocalFrame frame(e);
|
|
155
|
+
call(e, s_->set, slot, encode());
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Sets list `slot` to the records `encode` makes (a local reference,
|
|
160
|
+
* freed here): the body merges them into its items by key.
|
|
161
|
+
*/
|
|
162
|
+
template <class F>
|
|
163
|
+
void setList(int slot, F encode) const {
|
|
164
|
+
JNIEnv* e = jni::env();
|
|
165
|
+
jni::LocalFrame frame(e);
|
|
166
|
+
call(e, s_->listSetter, slot, encode());
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Gives the body `f` as the Kotlin function action `slot` holds (a
|
|
171
|
+
* kotlin.jvm.functions.FunctionN): a call from Compose (a click) runs it
|
|
172
|
+
* now, in the main context, and what it throws is reported.
|
|
173
|
+
*/
|
|
174
|
+
template <class... A>
|
|
175
|
+
void setAction(int slot, Fn<void(A...)> f, const char* site) const {
|
|
176
|
+
JNIEnv* e = jni::env();
|
|
177
|
+
jni::LocalFrame frame(e);
|
|
178
|
+
std::string iface = "kotlin/jvm/functions/Function" + std::to_string(sizeof...(A));
|
|
179
|
+
const void* identity = f.identity();
|
|
180
|
+
jobject fn = jni::proxyFor(
|
|
181
|
+
e, iface.c_str(), identity,
|
|
182
|
+
{{detail::invokeKey<sizeof...(A)>(),
|
|
183
|
+
[f = std::move(f)](JNIEnv* env, jobjectArray args) -> jobject {
|
|
184
|
+
callNowIn(ExecutionContext::main(), [&] { invoke(env, f, args, std::index_sequence_for<A...>{}); });
|
|
185
|
+
return nullptr;
|
|
186
|
+
}}},
|
|
187
|
+
site);
|
|
188
|
+
call(e, s_->setAction, slot, fn);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The ComposeView showing the content, made once, with the context of
|
|
193
|
+
* the view hosting the mount: setup calls it (the value of compose()).
|
|
194
|
+
*/
|
|
195
|
+
NativeRef content() const {
|
|
196
|
+
if (s_->view) return s_->view;
|
|
197
|
+
|
|
198
|
+
auto context = jni::hostContext();
|
|
199
|
+
|
|
200
|
+
if (!context.has())
|
|
201
|
+
throwError(String::fromLatin1("Error"), String::fromLatin1("compose() runs only in a component's setup"));
|
|
202
|
+
|
|
203
|
+
JNIEnv* e = jni::env();
|
|
204
|
+
jni::LocalFrame frame(e);
|
|
205
|
+
jclass host = jni::findClass(s_->host);
|
|
206
|
+
jmethodID create = jni::staticMethod(host, "create", detail::kCreate);
|
|
207
|
+
jobject composition = e->CallStaticObjectMethod(host, create, jni::unwrap(context), jni::unwrap(s_->state));
|
|
208
|
+
jni::check(e);
|
|
209
|
+
|
|
210
|
+
static jmethodID getView =
|
|
211
|
+
jni::method(jni::findClass(detail::kComposition), "getView", "()Landroid/view/View;");
|
|
212
|
+
jobject view = e->CallObjectMethod(composition, getView);
|
|
213
|
+
jni::check(e);
|
|
214
|
+
|
|
215
|
+
s_->composition = jni::wrap(e, composition, "the host of a component's Compose content");
|
|
216
|
+
s_->view = jni::wrap(e, view, "a ComposeView");
|
|
217
|
+
return s_->view;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// The mount went: its composition ends, or never starts; the view is not shown again.
|
|
221
|
+
void dispose() const {
|
|
222
|
+
if (!s_->composition) return;
|
|
223
|
+
|
|
224
|
+
JNIEnv* e = jni::env();
|
|
225
|
+
static jmethodID end = jni::method(jni::findClass(detail::kComposition), "dispose", "()V");
|
|
226
|
+
|
|
227
|
+
e->CallVoidMethod(jni::unwrap(s_->composition), end);
|
|
228
|
+
jni::check(e);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private:
|
|
232
|
+
struct Shared {
|
|
233
|
+
NativeRef state;
|
|
234
|
+
jmethodID set;
|
|
235
|
+
jmethodID setAction;
|
|
236
|
+
jmethodID listSetter;
|
|
237
|
+
/// Once made: the runtime's host of the content, and its view.
|
|
238
|
+
NativeRef composition;
|
|
239
|
+
NativeRef view;
|
|
240
|
+
const char* host;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
std::shared_ptr<Shared> s_;
|
|
244
|
+
|
|
245
|
+
void call(JNIEnv* e, jmethodID setter, int slot, jobject value) const {
|
|
246
|
+
e->CallVoidMethod(jni::unwrap(s_->state), setter, static_cast<jint>(slot), value);
|
|
247
|
+
jni::check(e);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
template <class... A, size_t... I>
|
|
251
|
+
static void invoke(JNIEnv* e, const Fn<void(A...)>& f, jobjectArray args, std::index_sequence<I...>) {
|
|
252
|
+
f(detail::Unbox<A>::from(e, jni::arg(e, args, static_cast<int>(I)))...);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
} // namespace lucent::compose
|