@lucent-lang/lucent 0.1.2 → 0.1.3
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/bin/lucent.cjs +2 -0
- package/dist/{bench-Ec_oPBhN.js → bench-BLZugR80.js} +1 -1
- package/dist/{build-B2E9SkG_.js → build-C1Uct_mH.js} +1 -1
- package/dist/{build-check-DYWM9LHo.js → build-check-hlYTmPRd.js} +1 -1
- package/dist/{check-DzsBZmBa.js → check-BksBP--n.js} +1 -1
- package/dist/cli.js +15 -11
- package/dist/{codes-D-6LxUqD.js → codes-B6AgFgAq.js} +24 -2
- package/dist/compiler.js +3 -3
- package/dist/{dashboard--hlNXMUi.js → dashboard-CzOJghrS.js} +1 -1
- package/dist/{dev-ClJalHD4.js → dev-CtaNF3sP.js} +3 -3
- package/dist/{explain-Byit07F-.js → explain-ImANcx5a.js} +1 -1
- package/dist/{pipeline-rDlFUUa1.js → pipeline-DXFTV3Mg.js} +21 -7
- package/dist/{project-MN6cwMwO.js → project-Bp9I-fLP.js} +254 -5
- package/dist/{sdk-coverage-DL81IAi9.js → sdk-coverage-BpbKBG_O.js} +33 -4
- package/dist/{sdk-diff-DbPQmOSD.js → sdk-diff-BueKDJQB.js} +3 -3
- package/dist/{sdk-lock-BWHZdSu-.js → sdk-lock-CKLnZ0Wl.js} +2 -2
- package/dist/{sdk-prefetch-D-MAqtZs.js → sdk-prefetch-rk6MuMe6.js} +2 -2
- package/dist/{sdk-search-BDercg36.js → sdk-search-U_2M81lq.js} +2 -2
- package/dist/{sdk-show-DEdKobD7.js → sdk-show-DG6H40fp.js} +2 -2
- package/dist/{sdk-usage-D_ly-uLw.js → sdk-usage-Cc7u1i0L.js} +1 -1
- package/dist/{src-Bzpv4-ox.js → src-DMbXwPlK.js} +2156 -522
- package/lib/sdk/android.d.ts +12 -0
- package/lib/sdk/compose.d.ts +17 -4
- package/lib/sdk/ui.d.ts +21 -0
- package/package.json +4 -4
- package/runtime/cpp/lucent/platform/android.cpp +152 -16
- package/runtime/cpp/lucent/platform/android.h +79 -2
- package/runtime/cpp/lucent/platform/ios.h +7 -0
- package/runtime/cpp/lucent/platform/ios_ui.h +20 -0
- package/runtime/cpp/rn/LucentComponentView.mm +12 -0
- package/runtime/cpp/rn/LucentViewSizing.h +9 -1
package/lib/sdk/android.d.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
// lucent:android — Android helpers for platform code.
|
|
2
2
|
import type { Activity, Instrumentation_ActivityResult } from "lucent:android/android.app";
|
|
3
3
|
import type { Context, Intent } from "lucent:android/android.content";
|
|
4
|
+
import type { Throwable } from "lucent:android/java.lang";
|
|
4
5
|
|
|
5
6
|
/** The application Context. */
|
|
6
7
|
export declare function appContext(): Context;
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* The Error Lucent makes of `throwable` when a call throws it: its message
|
|
11
|
+
* the exception's (or its class name, without one) and its `code` the class
|
|
12
|
+
* name (`java.lang.IllegalStateException`). An exception carrying a Lucent
|
|
13
|
+
* error (one a Lucent suspend function ended a Kotlin call with) gives
|
|
14
|
+
* that error.
|
|
15
|
+
* For an adapter whose callback API reports failure with a Throwable:
|
|
16
|
+
* `reject(errorOf(e))` rejects as the call would have thrown.
|
|
17
|
+
*/
|
|
18
|
+
export declare function errorOf(throwable: Throwable): Error;
|
|
19
|
+
|
|
8
20
|
/** Whether the device runs at least API level `api` (`Build.VERSION.SDK_INT >= api`). */
|
|
9
21
|
export declare function available(platform: "android", api: number): boolean;
|
|
10
22
|
|
package/lib/sdk/compose.d.ts
CHANGED
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
*
|
|
33
33
|
* @experimental
|
|
34
34
|
*/
|
|
35
|
-
import type { ViewGroup } from "lucent:android/android.view";
|
|
36
|
-
import type { Bound } from "lucent:ui";
|
|
35
|
+
import type { View, ViewGroup } from "lucent:android/android.view";
|
|
36
|
+
import type { Bound, NativeViewTag } from "lucent:ui";
|
|
37
37
|
|
|
38
38
|
/** Kotlin's Float: a number, converted where the body passes it. */
|
|
39
39
|
export type Float = number & { readonly "lucent:compose.Float"?: never };
|
|
@@ -76,15 +76,28 @@ export type ScopedChildren<S> = Shown | ((scope: S) => Shown);
|
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
78
|
* How TypeScript types an Android file's JSX: an element is a composable,
|
|
79
|
-
* a function of its props whose children are its `children` prop
|
|
79
|
+
* a function of its props whose children are its `children` prop, or a
|
|
80
|
+
* view class (T48), typed with its declarations' `~jsx` attributes, as
|
|
81
|
+
* native-jsx-dts.ts writes the generated toolkits' namespaces.
|
|
80
82
|
*/
|
|
81
83
|
export declare namespace JSX {
|
|
82
|
-
type Element = Composed;
|
|
84
|
+
type Element = Composed & View;
|
|
85
|
+
|
|
86
|
+
type ElementType = ((props: never) => Composed) | NativeViewTag<View>;
|
|
83
87
|
|
|
84
88
|
interface ElementChildrenAttribute {
|
|
85
89
|
children: {};
|
|
86
90
|
}
|
|
87
91
|
|
|
92
|
+
interface ElementAttributesProperty {
|
|
93
|
+
"~jsx": {};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface IntrinsicClassAttributes<T> {
|
|
97
|
+
/** Makes the view, where its class has no frame or Context constructor to make it with. */
|
|
98
|
+
create?: () => T;
|
|
99
|
+
}
|
|
100
|
+
|
|
88
101
|
interface IntrinsicElements {}
|
|
89
102
|
}
|
|
90
103
|
|
package/lib/sdk/ui.d.ts
CHANGED
|
@@ -97,6 +97,27 @@ export interface Children {
|
|
|
97
97
|
*/
|
|
98
98
|
export declare function slot<T extends object>(): T;
|
|
99
99
|
|
|
100
|
+
/** A native view class, as a JSX tag: any class whose instances are `V`. */
|
|
101
|
+
export type NativeViewTag<V> = abstract new (...args: never[]) => V;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The JSX attributes of a native view class: what each class in its
|
|
105
|
+
* hierarchy gives under its own `~jsx:<module>.<class>` key (derived by
|
|
106
|
+
* rule from its declarations), together. Each key's type takes the class
|
|
107
|
+
* itself (`this`), so a control event's handler gets the tag's class.
|
|
108
|
+
*/
|
|
109
|
+
export type NativeAttributes<T> = [
|
|
110
|
+
{
|
|
111
|
+
[K in keyof T]-?: K extends `~jsx:${string}` ? (x: NonNullable<T[K]>) => void : never;
|
|
112
|
+
}[keyof T],
|
|
113
|
+
] extends [never]
|
|
114
|
+
? {}
|
|
115
|
+
: {
|
|
116
|
+
[K in keyof T]-?: K extends `~jsx:${string}` ? (x: NonNullable<T[K]>) => void : never;
|
|
117
|
+
}[keyof T] extends (x: infer I) => void
|
|
118
|
+
? I
|
|
119
|
+
: never;
|
|
120
|
+
|
|
100
121
|
/**
|
|
101
122
|
* Has the view measured again, once the main thread's current work ends:
|
|
102
123
|
* for a change to its native content its host would not hear of. The host
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lucent-lang/lucent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Write native React Native modules in TypeScript: the Lucent compiler, CLI, Metro integration, Expo config plugin and editor plugin",
|
|
5
5
|
"homepage": "https://lucent-lang.dev",
|
|
6
6
|
"bugs": "https://github.com/Fausto95/lucent/issues",
|
|
@@ -53,10 +53,10 @@
|
|
|
53
53
|
"@types/react": "^19.2.0",
|
|
54
54
|
"ajv": "^8",
|
|
55
55
|
"ink-testing-library": "^4.0.0",
|
|
56
|
-
"@lucent-lang/codegen": "0.0.3",
|
|
57
56
|
"@lucent-lang/bindgen": "0.0.3",
|
|
58
|
-
"@lucent-lang/
|
|
59
|
-
"@lucent-lang/runtime": "0.0.3"
|
|
57
|
+
"@lucent-lang/codegen": "0.0.3",
|
|
58
|
+
"@lucent-lang/runtime": "0.0.3",
|
|
59
|
+
"@lucent-lang/compiler": "0.0.3"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {}
|
|
62
62
|
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// Lucent runtime — Android: JNI environment, classes, errors, the main
|
|
2
|
-
// Looper. Built
|
|
3
|
-
|
|
2
|
+
// Looper. Built into Android apps (fbjni comes with React Native); its JNI
|
|
3
|
+
// alone also into the desktop JNI host the tests run on a JVM
|
|
4
|
+
// (LUCENT_JNI_HOST), which defines env(), appContext() and available().
|
|
5
|
+
#if defined(__ANDROID__) || defined(LUCENT_JNI_HOST)
|
|
4
6
|
|
|
5
7
|
#include "android.h"
|
|
6
8
|
|
|
9
|
+
#ifdef __ANDROID__
|
|
7
10
|
#include <fbjni/NativeRunnable.h>
|
|
8
11
|
#include <fbjni/fbjni.h>
|
|
9
12
|
#include <sys/system_properties.h>
|
|
13
|
+
#endif
|
|
10
14
|
|
|
11
15
|
#include <atomic>
|
|
12
16
|
#include <cstdint>
|
|
@@ -19,7 +23,9 @@
|
|
|
19
23
|
|
|
20
24
|
namespace lucent::jni {
|
|
21
25
|
|
|
26
|
+
#ifdef __ANDROID__
|
|
22
27
|
JNIEnv* env() { return facebook::jni::Environment::ensureCurrentThreadIsAttached(); }
|
|
28
|
+
#endif
|
|
23
29
|
|
|
24
30
|
namespace {
|
|
25
31
|
|
|
@@ -103,12 +109,14 @@ std::vector<Thrown>& thrown() {
|
|
|
103
109
|
return *v;
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
/// The Lucent error `t` carries, removed from the list
|
|
107
|
-
|
|
112
|
+
/// The Lucent error `t` carries, removed from the list when `take` (Lucent
|
|
113
|
+
/// has caught the exception); none for other exceptions.
|
|
114
|
+
std::optional<Error> findThrown(JNIEnv* e, jobject t, bool take) {
|
|
108
115
|
std::lock_guard<std::mutex> g(thrownMutex);
|
|
109
116
|
auto& v = thrown();
|
|
110
117
|
for (auto it = v.begin(); it != v.end(); ++it) {
|
|
111
118
|
if (!e->IsSameObject(it->exception, t)) continue;
|
|
119
|
+
if (!take) return it->error;
|
|
112
120
|
|
|
113
121
|
Error error = std::move(it->error);
|
|
114
122
|
e->DeleteWeakGlobalRef(it->exception);
|
|
@@ -120,13 +128,13 @@ std::optional<Error> takeThrown(JNIEnv* e, jobject t) {
|
|
|
120
128
|
|
|
121
129
|
} // namespace
|
|
122
130
|
|
|
123
|
-
|
|
131
|
+
jthrowable throwableOf(JNIEnv* e, Error error) {
|
|
124
132
|
static jclass cls = findClass("java/lang/RuntimeException");
|
|
125
133
|
static jmethodID init = method(cls, "<init>", "(Ljava/lang/String;)V");
|
|
126
134
|
jstring message = toJString(e, error->message);
|
|
127
135
|
auto exception = static_cast<jthrowable>(e->NewObject(cls, init, message));
|
|
128
136
|
e->DeleteLocalRef(message);
|
|
129
|
-
if (!exception) return; // A pending OutOfMemoryError ends the call instead.
|
|
137
|
+
if (!exception) return nullptr; // A pending OutOfMemoryError ends the call instead.
|
|
130
138
|
|
|
131
139
|
{
|
|
132
140
|
std::lock_guard<std::mutex> g(thrownMutex);
|
|
@@ -139,37 +147,159 @@ void throwToJava(JNIEnv* e, Error error) {
|
|
|
139
147
|
});
|
|
140
148
|
v.push_back({e->NewWeakGlobalRef(exception), std::move(error)});
|
|
141
149
|
}
|
|
150
|
+
return exception;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
void throwToJava(JNIEnv* e, Error error) {
|
|
154
|
+
jthrowable exception = throwableOf(e, std::move(error));
|
|
155
|
+
if (!exception) return;
|
|
156
|
+
|
|
142
157
|
e->Throw(exception);
|
|
143
158
|
e->DeleteLocalRef(exception);
|
|
144
159
|
}
|
|
145
160
|
|
|
161
|
+
namespace {
|
|
162
|
+
|
|
163
|
+
/// A primitive of descriptor `d`, from its boxed object.
|
|
164
|
+
jvalue unboxed(JNIEnv* e, char d, jobject boxed) {
|
|
165
|
+
jvalue v{};
|
|
166
|
+
switch (d) {
|
|
167
|
+
case 'Z': v.z = unboxBoolean(e, boxed) ? JNI_TRUE : JNI_FALSE; break;
|
|
168
|
+
case 'J': v.j = unboxLong(e, boxed); break;
|
|
169
|
+
case 'I': v.i = static_cast<jint>(unboxNumber(e, boxed)); break;
|
|
170
|
+
case 'S': v.s = static_cast<jshort>(unboxNumber(e, boxed)); break;
|
|
171
|
+
case 'B': v.b = static_cast<jbyte>(unboxNumber(e, boxed)); break;
|
|
172
|
+
case 'C': v.c = static_cast<jchar>(unboxNumber(e, boxed)); break;
|
|
173
|
+
case 'F': v.f = static_cast<jfloat>(unboxNumber(e, boxed)); break;
|
|
174
|
+
case 'D': v.d = unboxNumber(e, boxed); break;
|
|
175
|
+
default: v.l = boxed;
|
|
176
|
+
}
|
|
177
|
+
return v;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
} // namespace
|
|
181
|
+
|
|
182
|
+
jobject boxValueClass(JNIEnv* e, const char* cls, const char* underlying, jobject value) {
|
|
183
|
+
if (!value && underlying[0] != 'L' && underlying[0] != '[') return nullptr;
|
|
184
|
+
|
|
185
|
+
jclass c = findClass(cls);
|
|
186
|
+
std::string sig = std::string("(") + underlying + ")L" + cls + ";";
|
|
187
|
+
jmethodID box = staticMethod(c, "box-impl", sig.c_str());
|
|
188
|
+
jvalue v = unboxed(e, underlying[0], value);
|
|
189
|
+
jobject r = e->CallStaticObjectMethodA(c, box, &v);
|
|
190
|
+
check(e);
|
|
191
|
+
return r;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
jobject unboxValueClass(JNIEnv* e, const char* cls, const char* underlying, jobject boxed) {
|
|
195
|
+
if (!boxed) return nullptr;
|
|
196
|
+
|
|
197
|
+
jmethodID unbox = method(findClass(cls), "unbox-impl", (std::string("()") + underlying).c_str());
|
|
198
|
+
|
|
199
|
+
// Each read checked before the boxing calls Java again.
|
|
200
|
+
auto read = [e](auto v) {
|
|
201
|
+
check(e);
|
|
202
|
+
return v;
|
|
203
|
+
};
|
|
204
|
+
switch (underlying[0]) {
|
|
205
|
+
case 'Z': return boxBoolean(e, read(e->CallBooleanMethod(boxed, unbox)) == JNI_TRUE);
|
|
206
|
+
case 'J': return boxLong(e, read(e->CallLongMethod(boxed, unbox)));
|
|
207
|
+
case 'I': return boxInt(e, read(e->CallIntMethod(boxed, unbox)));
|
|
208
|
+
case 'S': return boxShort(e, read(e->CallShortMethod(boxed, unbox)));
|
|
209
|
+
case 'B': return boxByte(e, read(e->CallByteMethod(boxed, unbox)));
|
|
210
|
+
case 'C': return boxChar(e, read(e->CallCharMethod(boxed, unbox)));
|
|
211
|
+
case 'F': return boxFloat(e, read(e->CallFloatMethod(boxed, unbox)));
|
|
212
|
+
case 'D': return boxDouble(e, read(e->CallDoubleMethod(boxed, unbox)));
|
|
213
|
+
default: return read(e->CallObjectMethod(boxed, unbox));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
jobject suspendedCall(JNIEnv* e, jobject continuation, const std::function<void(Resume)>& start) {
|
|
218
|
+
static jclass intrinsics = findClass("kotlin/coroutines/intrinsics/IntrinsicsKt");
|
|
219
|
+
static jmethodID intercepted = staticMethod(
|
|
220
|
+
intrinsics, "intercepted", "(Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;");
|
|
221
|
+
static jclass safeClass = findClass("kotlin/coroutines/SafeContinuation");
|
|
222
|
+
static jmethodID safeInit = method(safeClass, "<init>", "(Lkotlin/coroutines/Continuation;)V");
|
|
223
|
+
static jmethodID getOrThrow = method(safeClass, "getOrThrow", "()Ljava/lang/Object;");
|
|
224
|
+
static jmethodID resumeWith = method(safeClass, "resumeWith", "(Ljava/lang/Object;)V");
|
|
225
|
+
static jclass results = findClass("kotlin/ResultKt");
|
|
226
|
+
static jmethodID failure = staticMethod(results, "createFailure", "(Ljava/lang/Throwable;)Ljava/lang/Object;");
|
|
227
|
+
|
|
228
|
+
// As suspendCoroutine does: resumed through its dispatcher, never on the Lucent thread,
|
|
229
|
+
// and a result that arrives before the call returns is its result.
|
|
230
|
+
jobject dispatched = e->CallStaticObjectMethod(intrinsics, intercepted, continuation);
|
|
231
|
+
check(e);
|
|
232
|
+
jobject local = e->NewObject(safeClass, safeInit, dispatched);
|
|
233
|
+
e->DeleteLocalRef(dispatched);
|
|
234
|
+
check(e);
|
|
235
|
+
auto safe = std::make_shared<NativeRef>(wrap(e, e->NewLocalRef(local), "a continuation"));
|
|
236
|
+
|
|
237
|
+
// Kotlin's Result is the value itself, or a failure of the Throwable.
|
|
238
|
+
start([safe](JNIEnv* env, jobject value, const Error* error) {
|
|
239
|
+
jobject result = value;
|
|
240
|
+
if (error) {
|
|
241
|
+
jthrowable t = throwableOf(env, *error);
|
|
242
|
+
result = env->CallStaticObjectMethod(results, failure, t);
|
|
243
|
+
env->DeleteLocalRef(t);
|
|
244
|
+
check(env);
|
|
245
|
+
}
|
|
246
|
+
env->CallVoidMethod(unwrap(*safe), resumeWith, result);
|
|
247
|
+
if (error) env->DeleteLocalRef(result);
|
|
248
|
+
check(env);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// COROUTINE_SUSPENDED, or the result already there; a failure already there stays pending.
|
|
252
|
+
jobject r = e->CallObjectMethod(local, getOrThrow);
|
|
253
|
+
e->DeleteLocalRef(local);
|
|
254
|
+
return r;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
jobject unit(JNIEnv* e) {
|
|
258
|
+
static jclass cls = findClass("kotlin/Unit");
|
|
259
|
+
static jfieldID instance = staticField(cls, "INSTANCE", "Lkotlin/Unit;");
|
|
260
|
+
return e->GetStaticObjectField(cls, instance);
|
|
261
|
+
}
|
|
262
|
+
|
|
146
263
|
void rethrowPending(JNIEnv* e) {
|
|
147
264
|
jthrowable t = e->ExceptionOccurred();
|
|
148
265
|
e->ExceptionClear();
|
|
149
266
|
throw Exception(errorOf(e, t));
|
|
150
267
|
}
|
|
151
268
|
|
|
152
|
-
|
|
153
|
-
|
|
269
|
+
namespace {
|
|
270
|
+
|
|
271
|
+
/// A Java exception as a Lucent error; a Lucent error it carries is
|
|
272
|
+
/// removed from the list when `take`.
|
|
273
|
+
Error errorOf(JNIEnv* e, jobject t, bool take) {
|
|
274
|
+
if (auto own = t ? findThrown(e, t, take) : std::nullopt) return std::move(*own);
|
|
154
275
|
if (!t) {
|
|
155
276
|
// No exception to read: a cancelled task.
|
|
156
277
|
Error err = makeError(String::fromLatin1("Error"), String::fromLatin1("cancelled"));
|
|
157
278
|
err->code = String::fromLatin1("java.util.concurrent.CancellationException");
|
|
158
279
|
return err;
|
|
159
280
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
281
|
+
// These throw only when out of memory, which leaves the defaults below:
|
|
282
|
+
// cleared after each call, before the next JNI call (as CheckJNI requires).
|
|
283
|
+
auto read = [e](jobject o, const char* cls, const char* name, const char* sig) {
|
|
284
|
+
jobject r = e->CallObjectMethod(o, e->GetMethodID(e->FindClass(cls), name, sig));
|
|
285
|
+
e->ExceptionClear();
|
|
286
|
+
return r;
|
|
287
|
+
};
|
|
288
|
+
jobject cls = read(t, "java/lang/Object", "getClass", "()Ljava/lang/Class;");
|
|
289
|
+
auto name = static_cast<jstring>(cls ? read(cls, "java/lang/Class", "getName", "()Ljava/lang/String;") : nullptr);
|
|
290
|
+
auto message = static_cast<jstring>(read(t, "java/lang/Throwable", "getMessage", "()Ljava/lang/String;"));
|
|
167
291
|
String className = name ? fromJString(e, name, "") : String::fromLatin1("java.lang.Throwable");
|
|
168
292
|
Error err = makeError(String::fromLatin1("Error"), message ? fromJString(e, message, "") : className);
|
|
169
293
|
err->code = className;
|
|
170
294
|
return err;
|
|
171
295
|
}
|
|
172
296
|
|
|
297
|
+
} // namespace
|
|
298
|
+
|
|
299
|
+
Error errorOf(JNIEnv* e, jobject t) { return errorOf(e, t, true); }
|
|
300
|
+
|
|
301
|
+
Error errorOf(const NativeRef& throwable) { return errorOf(env(), unwrap(throwable), false); }
|
|
302
|
+
|
|
173
303
|
void close(const NativeRef& closeable) {
|
|
174
304
|
JNIEnv* e = env();
|
|
175
305
|
static jmethodID m = method(findClass("java/lang/AutoCloseable"), "close", "()V");
|
|
@@ -566,6 +696,7 @@ jobject boxDouble(JNIEnv* e, jdouble v) { return boxWith(e, "java/lang/Double",
|
|
|
566
696
|
jobject boxFloat(JNIEnv* e, jfloat v) { return boxWith(e, "java/lang/Float", "(F)Ljava/lang/Float;", jvalue{.f = v}); }
|
|
567
697
|
jobject boxBoolean(JNIEnv* e, bool v) { return boxWith(e, "java/lang/Boolean", "(Z)Ljava/lang/Boolean;", jvalue{.z = static_cast<jboolean>(v ? JNI_TRUE : JNI_FALSE)}); }
|
|
568
698
|
|
|
699
|
+
#ifdef __ANDROID__
|
|
569
700
|
NativeRef appContext() {
|
|
570
701
|
static NativeRef* app = [] {
|
|
571
702
|
JNIEnv* e = env();
|
|
@@ -581,6 +712,7 @@ NativeRef appContext() {
|
|
|
581
712
|
}();
|
|
582
713
|
return *app;
|
|
583
714
|
}
|
|
715
|
+
#endif
|
|
584
716
|
|
|
585
717
|
namespace {
|
|
586
718
|
thread_local jobject hostingView = nullptr;
|
|
@@ -601,6 +733,7 @@ Opt<NativeRef> hostContext() {
|
|
|
601
733
|
return wrapOpt(e, context);
|
|
602
734
|
}
|
|
603
735
|
|
|
736
|
+
#ifdef __ANDROID__
|
|
604
737
|
bool available(double api) {
|
|
605
738
|
static int level = [] {
|
|
606
739
|
char value[PROP_VALUE_MAX] = {0};
|
|
@@ -609,9 +742,11 @@ bool available(double api) {
|
|
|
609
742
|
}();
|
|
610
743
|
return level >= api;
|
|
611
744
|
}
|
|
745
|
+
#endif
|
|
612
746
|
|
|
613
747
|
} // namespace lucent::jni
|
|
614
748
|
|
|
749
|
+
#ifdef __ANDROID__
|
|
615
750
|
namespace lucent {
|
|
616
751
|
|
|
617
752
|
void postToMain(std::function<void()> job) {
|
|
@@ -646,5 +781,6 @@ bool onMainThread() {
|
|
|
646
781
|
}
|
|
647
782
|
|
|
648
783
|
} // namespace lucent
|
|
784
|
+
#endif
|
|
649
785
|
|
|
650
|
-
#endif
|
|
786
|
+
#endif
|
|
@@ -37,6 +37,9 @@ inline void check(JNIEnv* env) {
|
|
|
37
37
|
}
|
|
38
38
|
/// A Java exception (a Throwable) as a Lucent error, coded as rethrowPending does.
|
|
39
39
|
Error errorOf(JNIEnv* env, jobject throwable);
|
|
40
|
+
/// `errorOf(throwable)` from lucent:android: the same error, but a Lucent
|
|
41
|
+
/// error the exception carries stays with it, for the next call to read.
|
|
42
|
+
Error errorOf(const NativeRef& throwable);
|
|
40
43
|
|
|
41
44
|
/// Closes an AutoCloseable (a Closeable, a Cursor…): how a `using`
|
|
42
45
|
/// declaration disposes one. Throws what close() throws.
|
|
@@ -204,12 +207,80 @@ jobject boxDouble(JNIEnv* env, jdouble v);
|
|
|
204
207
|
jobject boxFloat(JNIEnv* env, jfloat v);
|
|
205
208
|
jobject boxBoolean(JNIEnv* env, bool v);
|
|
206
209
|
|
|
210
|
+
/**
|
|
211
|
+
* A Kotlin value class `cls` (`dev/x/Score`) the JVM passes as its
|
|
212
|
+
* underlying value, whose descriptor is `underlying` (`I`): its boxed
|
|
213
|
+
* object of `value` (boxed itself, as a proxy receives it: an Integer),
|
|
214
|
+
* through the class's `box-impl`; and the underlying value of a boxed
|
|
215
|
+
* object, boxed as a proxy returns it, through its `unbox-impl`. Local
|
|
216
|
+
* references.
|
|
217
|
+
*/
|
|
218
|
+
jobject boxValueClass(JNIEnv* env, const char* cls, const char* underlying, jobject value);
|
|
219
|
+
jobject unboxValueClass(JNIEnv* env, const char* cls, const char* underlying, jobject boxed);
|
|
220
|
+
|
|
207
221
|
// --- Kotlin coroutines through generated shims ----------------------------------------
|
|
208
222
|
|
|
209
|
-
///
|
|
210
|
-
///
|
|
223
|
+
/// `error` as a java.lang.RuntimeException carrying its message, which
|
|
224
|
+
/// errorOf reads back as `error` itself. A local reference.
|
|
225
|
+
jthrowable throwableOf(JNIEnv* env, Error error);
|
|
226
|
+
|
|
227
|
+
/// Throws `error` to Java, as throwableOf makes it.
|
|
211
228
|
void throwToJava(JNIEnv* env, Error error);
|
|
212
229
|
|
|
230
|
+
/**
|
|
231
|
+
* How a Kotlin suspend function a Lucent class implements ends: resumes
|
|
232
|
+
* its continuation with a value (a Java object, or null), or with the
|
|
233
|
+
* error (see throwableOf). Any thread may call it, once.
|
|
234
|
+
*/
|
|
235
|
+
using Resume = std::function<void(JNIEnv* env, jobject value, const Error* error)>;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* A suspend function a Lucent class implements, called through a proxy
|
|
239
|
+
* with `continuation` (its last argument): `start` is given what resumes
|
|
240
|
+
* it, and Kotlin is told the call suspended (COROUTINE_SUSPENDED), whose
|
|
241
|
+
* result it waits for.
|
|
242
|
+
*/
|
|
243
|
+
jobject suspendedCall(JNIEnv* env, jobject continuation, const std::function<void(Resume)>& start);
|
|
244
|
+
|
|
245
|
+
/// Kotlin's Unit, what a suspend function giving nothing resumes with. A local reference.
|
|
246
|
+
jobject unit(JNIEnv* env);
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Resumes a suspend call (`resume`) with what `call` gives: a promise's
|
|
250
|
+
* outcome once it settles, else its value now; what it throws is its
|
|
251
|
+
* error. `box(env, value)` makes the Java object of a value. On the
|
|
252
|
+
* Lucent thread, where the call runs.
|
|
253
|
+
*/
|
|
254
|
+
template <class F, class B>
|
|
255
|
+
void resumeWithResult(const Resume& resume, F call, B box) {
|
|
256
|
+
auto settle = [resume, box](const auto& value) {
|
|
257
|
+
JNIEnv* e = env();
|
|
258
|
+
LocalFrame frame(e);
|
|
259
|
+
resume(e, box(e, value), nullptr);
|
|
260
|
+
};
|
|
261
|
+
auto fail = [resume](const Error& error) {
|
|
262
|
+
JNIEnv* e = env();
|
|
263
|
+
LocalFrame frame(e);
|
|
264
|
+
resume(e, nullptr, &error);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
auto r = call();
|
|
269
|
+
if constexpr (IsPromise<decltype(r)>::value) {
|
|
270
|
+
r.onSettled([r, settle, fail] {
|
|
271
|
+
if (r.fulfilled())
|
|
272
|
+
settle(r.value());
|
|
273
|
+
else
|
|
274
|
+
fail(r.error());
|
|
275
|
+
});
|
|
276
|
+
} else {
|
|
277
|
+
settle(r);
|
|
278
|
+
}
|
|
279
|
+
} catch (...) {
|
|
280
|
+
fail(currentError(std::current_exception()));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
213
284
|
/**
|
|
214
285
|
* A Lucent function a generated shim runs as a Kotlin suspend function (a
|
|
215
286
|
* suspend function argument, a fun interface's suspending function), which
|
|
@@ -291,6 +362,12 @@ NativeRef appContext();
|
|
|
291
362
|
/// outside any setup.
|
|
292
363
|
Opt<NativeRef> hostContext();
|
|
293
364
|
|
|
365
|
+
/// The Context a view is made with: the hosting view's (its Activity's theme), else the app's.
|
|
366
|
+
inline NativeRef viewContext() {
|
|
367
|
+
Opt<NativeRef> host = hostContext();
|
|
368
|
+
return host.has() ? host.get() : appContext();
|
|
369
|
+
}
|
|
370
|
+
|
|
294
371
|
/**
|
|
295
372
|
* While one lives, `view` (a local reference) hosts the mount whose setup
|
|
296
373
|
* runs on this thread, and hostContext() is its context: the Android host
|
|
@@ -90,6 +90,13 @@ NSArray* toNSArray(const Array<T>& a, F toObject) {
|
|
|
90
90
|
}
|
|
91
91
|
return out;
|
|
92
92
|
}
|
|
93
|
+
/// An array of `items` (objects, never nil): a Swift tuple's elements, as its shim takes them.
|
|
94
|
+
template <class... T>
|
|
95
|
+
NSArray* toNSArrayOf(T... items) {
|
|
96
|
+
id all[] = {items...};
|
|
97
|
+
return [NSArray arrayWithObjects:all count:sizeof...(T)];
|
|
98
|
+
}
|
|
99
|
+
|
|
93
100
|
template <class T, class F>
|
|
94
101
|
Array<T> fromNSArray(NSArray* a, F fromObject, const char* what) {
|
|
95
102
|
if (!a) returnedNil(what);
|
|
@@ -189,4 +189,24 @@ Fn<void()> onAppEvent(const String& event, Fn<void()> listener, Opt<AbortSignal>
|
|
|
189
189
|
/// scene's session's persistent identifier.
|
|
190
190
|
Fn<void()> onSceneEvent(const String& event, Fn<void(String)> listener, Opt<AbortSignal> signal = {});
|
|
191
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Calls `handler` with the control on each of `events` (a UIControlEvents
|
|
194
|
+
* mask): a JSX element's control event (T48), as the UIAction UIKit
|
|
195
|
+
* registers. Gives the action, for removeControlAction: the control keeps
|
|
196
|
+
* it, and through it the handler, until then.
|
|
197
|
+
*/
|
|
198
|
+
template <class Handler>
|
|
199
|
+
NativeRef addControlAction(id control, NSUInteger events, Handler handler) {
|
|
200
|
+
UIAction* action = [UIAction actionWithHandler:^(UIAction* a) {
|
|
201
|
+
handler(a.sender);
|
|
202
|
+
}];
|
|
203
|
+
[(UIControl*)control addAction:action forControlEvents:(UIControlEvents)events];
|
|
204
|
+
return wrap(action, "a control's action");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Takes back an action addControlAction registered. */
|
|
208
|
+
inline void removeControlAction(id control, const NativeRef& action, NSUInteger events) {
|
|
209
|
+
[(UIControl*)control removeAction:(UIAction*)unwrap(action) forControlEvents:(UIControlEvents)events];
|
|
210
|
+
}
|
|
211
|
+
|
|
192
212
|
} // namespace lucent::objc
|
|
@@ -286,6 +286,18 @@ void inMainContext(const char* where, F&& work) {
|
|
|
286
286
|
LucentComponentView *view = weakSelf;
|
|
287
287
|
|
|
288
288
|
return view ? [view lucentMeasure:constraints] : std::nullopt;
|
|
289
|
+
},
|
|
290
|
+
// The content changed, a native timer's or an animation's code too, not
|
|
291
|
+
// only a commit's or a command's: it may have moved the slot, which lays
|
|
292
|
+
// out again on UIKit's next pass and reports where it is, past the
|
|
293
|
+
// reports' bound (TA26).
|
|
294
|
+
[weakSelf] {
|
|
295
|
+
LucentComponentView *view = weakSelf;
|
|
296
|
+
|
|
297
|
+
if (!view) return;
|
|
298
|
+
|
|
299
|
+
view->_place.contentChanged();
|
|
300
|
+
[view->_children.slot setNeedsLayout];
|
|
289
301
|
});
|
|
290
302
|
_place.start(static_cast<float>(RCTScreenScale()), static_cast<int>(self.tag));
|
|
291
303
|
[self.lucentChildren startMount];
|
|
@@ -199,11 +199,15 @@ class HostSizing {
|
|
|
199
199
|
|
|
200
200
|
/// A mount starts on the view `tag` names, at `scale` pixels per point,
|
|
201
201
|
/// its content measured by `measure`: revision 1, measured from scratch.
|
|
202
|
-
|
|
202
|
+
/// `changed`, if any, runs after each change of the content is measured:
|
|
203
|
+
/// what else the host follows the content for (its slot, which the
|
|
204
|
+
/// content may have moved).
|
|
205
|
+
void start(float scale, int tag, Measure measure, std::function<void()> changed = nullptr) {
|
|
203
206
|
measurer_ = sizing::Measurer(scale);
|
|
204
207
|
lastPost_.reset();
|
|
205
208
|
tag_ = tag;
|
|
206
209
|
measure_ = std::move(measure);
|
|
210
|
+
changed_ = std::move(changed);
|
|
207
211
|
content_ = ui::Content::create([this] { contentChanged(); });
|
|
208
212
|
traceSizing("%d start", tag_);
|
|
209
213
|
}
|
|
@@ -214,6 +218,7 @@ class HostSizing {
|
|
|
214
218
|
|
|
215
219
|
content_.reset();
|
|
216
220
|
measure_ = nullptr;
|
|
221
|
+
changed_ = nullptr;
|
|
217
222
|
state_.reset();
|
|
218
223
|
lastPost_.reset();
|
|
219
224
|
}
|
|
@@ -246,6 +251,8 @@ class HostSizing {
|
|
|
246
251
|
measurer_.contentChanged();
|
|
247
252
|
traceSizing("%d content rev %llu", tag_, static_cast<unsigned long long>(measurer_.revision()));
|
|
248
253
|
measure();
|
|
254
|
+
|
|
255
|
+
if (changed_) changed_();
|
|
249
256
|
}
|
|
250
257
|
|
|
251
258
|
/// Measures if the state asks for constraints this content revision has
|
|
@@ -338,6 +345,7 @@ class HostSizing {
|
|
|
338
345
|
sizing::Measurer measurer_;
|
|
339
346
|
std::optional<Post> lastPost_;
|
|
340
347
|
Measure measure_;
|
|
348
|
+
std::function<void()> changed_;
|
|
341
349
|
std::shared_ptr<ui::Content> content_;
|
|
342
350
|
int tag_ = 0;
|
|
343
351
|
};
|