@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,139 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region src/cli/trace.ts
|
|
4
|
+
const CATEGORIES = [
|
|
5
|
+
"entry",
|
|
6
|
+
"lock",
|
|
7
|
+
"queue",
|
|
8
|
+
"run",
|
|
9
|
+
"native",
|
|
10
|
+
"completion",
|
|
11
|
+
"compute",
|
|
12
|
+
"copy",
|
|
13
|
+
"alloc",
|
|
14
|
+
"build"
|
|
15
|
+
];
|
|
16
|
+
const BUILD_PID = 1;
|
|
17
|
+
function buildEvents(record) {
|
|
18
|
+
const startedAt = record.startedAt ?? {};
|
|
19
|
+
return record.nodes.map((node) => {
|
|
20
|
+
const args = {
|
|
21
|
+
kind: node.kind,
|
|
22
|
+
status: node.status,
|
|
23
|
+
...node.detail ? { detail: node.detail } : {}
|
|
24
|
+
};
|
|
25
|
+
const ms = record.timings[node.id];
|
|
26
|
+
if (ms === void 0 || startedAt[node.id] === void 0) return {
|
|
27
|
+
name: node.id,
|
|
28
|
+
cat: "build",
|
|
29
|
+
ph: "i",
|
|
30
|
+
s: "p",
|
|
31
|
+
ts: 0,
|
|
32
|
+
pid: BUILD_PID,
|
|
33
|
+
tid: 1,
|
|
34
|
+
args
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
name: node.id,
|
|
38
|
+
cat: "build",
|
|
39
|
+
ph: "X",
|
|
40
|
+
ts: startedAt[node.id] * 1e3,
|
|
41
|
+
dur: ms * 1e3,
|
|
42
|
+
pid: BUILD_PID,
|
|
43
|
+
tid: 1,
|
|
44
|
+
args
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const processName = (pid, name) => ({
|
|
49
|
+
name: "process_name",
|
|
50
|
+
ph: "M",
|
|
51
|
+
pid,
|
|
52
|
+
args: { name }
|
|
53
|
+
});
|
|
54
|
+
function mergeTraces(record, runtimes) {
|
|
55
|
+
const events = [];
|
|
56
|
+
if (record) events.push(processName(BUILD_PID, "lucent build"), ...buildEvents(record));
|
|
57
|
+
runtimes.forEach(({ name, trace }, i) => {
|
|
58
|
+
const pid = 2 + i;
|
|
59
|
+
events.push(processName(pid, name));
|
|
60
|
+
for (const e of trace.traceEvents) events.push({
|
|
61
|
+
...e,
|
|
62
|
+
pid
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
traceEvents: events,
|
|
67
|
+
displayTimeUnit: "ms"
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function summarize(trace) {
|
|
71
|
+
const totals = /* @__PURE__ */ new Map();
|
|
72
|
+
for (const e of trace.traceEvents) {
|
|
73
|
+
if (e.ph !== "X" || !e.cat) continue;
|
|
74
|
+
const total = totals.get(e.cat) ?? {
|
|
75
|
+
category: e.cat,
|
|
76
|
+
ms: 0,
|
|
77
|
+
count: 0
|
|
78
|
+
};
|
|
79
|
+
total.ms += (e.dur ?? 0) / 1e3;
|
|
80
|
+
total.count++;
|
|
81
|
+
const bytes = e.cat === "copy" ? Number(e.args?.value ?? 0) : 0;
|
|
82
|
+
if (bytes > 0) total.bytes = (total.bytes ?? 0) + bytes;
|
|
83
|
+
totals.set(e.cat, total);
|
|
84
|
+
}
|
|
85
|
+
const order = (c) => {
|
|
86
|
+
const i = CATEGORIES.indexOf(c);
|
|
87
|
+
return i < 0 ? CATEGORIES.length : i;
|
|
88
|
+
};
|
|
89
|
+
return { categories: [...totals.values()].sort((a, b) => order(a.category) - order(b.category)).map((t) => ({
|
|
90
|
+
...t,
|
|
91
|
+
ms: Math.round(t.ms * 1e3) / 1e3
|
|
92
|
+
})) };
|
|
93
|
+
}
|
|
94
|
+
function formatMs(ms) {
|
|
95
|
+
if (ms >= 1e3) return `${(ms / 1e3).toFixed(1)} s`;
|
|
96
|
+
return `${Number(ms.toPrecision(3))} ms`;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/cli/commands/trace.ts
|
|
100
|
+
const HOW = "run the app or its tests with LUCENT_TRACE=<file>.json and pass --runtime <file>; on a device, LUCENT_TRACE=platform (iOS, Instruments' os_signpost) or `adb shell setprop debug.lucent.trace 1` (Android, Perfetto's atrace)";
|
|
101
|
+
function run({ root, flags, out }) {
|
|
102
|
+
const t = out.theme;
|
|
103
|
+
const recordFile = path.join(root, ".lucent", "build-record.json");
|
|
104
|
+
const record = fs.existsSync(recordFile) ? JSON.parse(fs.readFileSync(recordFile, "utf8")) : void 0;
|
|
105
|
+
const runtimeFiles = typeof flags.runtime === "string" ? flags.runtime.split(",").map((f) => path.resolve(root, f.trim())) : [];
|
|
106
|
+
if (!record && !runtimeFiles.length) {
|
|
107
|
+
out.error(`${t.error(t.symbols.fail)} no build record in ${path.join(root, ".lucent")} and no --runtime trace: build first, or ${HOW}`);
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
const runtimes = [];
|
|
111
|
+
for (const file of runtimeFiles) {
|
|
112
|
+
if (!fs.existsSync(file)) {
|
|
113
|
+
out.error(`${t.error(t.symbols.fail)} no runtime trace at ${file}: ${HOW}`);
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
runtimes.push({
|
|
117
|
+
name: path.basename(file),
|
|
118
|
+
trace: JSON.parse(fs.readFileSync(file, "utf8"))
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const merged = mergeTraces(record, runtimes);
|
|
122
|
+
const target = typeof flags.out === "string" ? path.resolve(root, flags.out) : path.join(root, ".lucent", "trace.json");
|
|
123
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
124
|
+
fs.writeFileSync(target, `${JSON.stringify(merged)}\n`);
|
|
125
|
+
const shown = path.relative(root, target) || target;
|
|
126
|
+
out.print(`${t.success(t.symbols.ok)} wrote ${shown} ${t.dim("open it in ui.perfetto.dev or chrome://tracing")}`);
|
|
127
|
+
if (record) {
|
|
128
|
+
const steps = Object.entries(record.timings).sort(([a], [b]) => (record.startedAt?.[a] ?? 0) - (record.startedAt?.[b] ?? 0)).map(([id, ms]) => `${id} ${formatMs(ms)}`);
|
|
129
|
+
out.print(` ${"build".padEnd(16)} ${steps.join(" · ") || t.dim("nothing timed")}`);
|
|
130
|
+
}
|
|
131
|
+
for (const { name, trace } of runtimes) {
|
|
132
|
+
const causes = summarize(trace).categories.map((c) => `${c.category} ${formatMs(c.ms)}${c.bytes ? ` (${(c.bytes / 1e6).toFixed(1)} MB)` : ""}`);
|
|
133
|
+
const dropped = Number(trace.otherData?.dropped ?? 0);
|
|
134
|
+
out.print(` ${name.padEnd(16)} ${causes.join(" · ") || t.dim("no spans")}${dropped ? t.dim(` (${dropped} events dropped)`) : ""}`);
|
|
135
|
+
}
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
export { run };
|
package/lib/core.cjs
CHANGED
|
@@ -103,4 +103,371 @@ function now() {
|
|
|
103
103
|
return typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
// An error nobody can catch (a cleanup's): the host's uncaught error
|
|
107
|
+
// handler gets it, as native code's goes to the platform log.
|
|
108
|
+
function reportUncaught(e) {
|
|
109
|
+
setTimeout(() => {
|
|
110
|
+
throw e;
|
|
111
|
+
}, 0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// One promise that settles once, at the first of what `start`'s callbacks
|
|
115
|
+
// report and the signal aborting; the cleanup `start` returns runs once, as
|
|
116
|
+
// soon as it settles.
|
|
117
|
+
function compose(signal, start) {
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
if (signal && signal.aborted) return void reject(signal.reason);
|
|
120
|
+
|
|
121
|
+
let settled = false;
|
|
122
|
+
let registered = false;
|
|
123
|
+
let cleanup;
|
|
124
|
+
|
|
125
|
+
const runCleanup = () => {
|
|
126
|
+
const f = cleanup;
|
|
127
|
+
cleanup = undefined;
|
|
128
|
+
if (typeof f !== "function") return;
|
|
129
|
+
try {
|
|
130
|
+
f();
|
|
131
|
+
} catch (e) {
|
|
132
|
+
reportUncaught(e);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const settle = (how, value) => {
|
|
137
|
+
if (settled) return;
|
|
138
|
+
settled = true;
|
|
139
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
140
|
+
how(value);
|
|
141
|
+
if (registered) runCleanup();
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const onAbort = () => settle(reject, signal.reason);
|
|
145
|
+
if (signal) signal.addEventListener("abort", onAbort);
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
cleanup = start({
|
|
149
|
+
open: () => !settled,
|
|
150
|
+
resolve: (value) => settle(resolve, value),
|
|
151
|
+
reject: (reason) => settle(reject, reason),
|
|
152
|
+
});
|
|
153
|
+
} catch (e) {
|
|
154
|
+
settle(reject, e);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
registered = true;
|
|
158
|
+
if (settled) runCleanup();
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function fromCallback(register, signal) {
|
|
163
|
+
return compose(signal, (c) =>
|
|
164
|
+
register(
|
|
165
|
+
(value) => void c.resolve(value),
|
|
166
|
+
(reason) => void c.reject(reason),
|
|
167
|
+
),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function subscribe(register, onValue, signal) {
|
|
172
|
+
return compose(signal, (c) => {
|
|
173
|
+
const next = (value) => {
|
|
174
|
+
if (!c.open()) return;
|
|
175
|
+
try {
|
|
176
|
+
onValue(value);
|
|
177
|
+
} catch (e) {
|
|
178
|
+
c.reject(e);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
return register(
|
|
182
|
+
next,
|
|
183
|
+
() => void c.resolve(undefined),
|
|
184
|
+
(error) => void c.reject(error),
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// --- native buffers ---------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
const MAX_SIZE = Number.MAX_SAFE_INTEGER;
|
|
192
|
+
|
|
193
|
+
const bufferCounts = { allocated: 0, adopted: 0, transfers: 0, copies: 0, bytesCopied: 0 };
|
|
194
|
+
|
|
195
|
+
function countCopy(bytes) {
|
|
196
|
+
bufferCounts.copies++;
|
|
197
|
+
bufferCounts.bytesCopied += bytes;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Each buffer's state, out of reach of the code using it: its bytes, open,
|
|
201
|
+
// transferred or closed, and its borrows.
|
|
202
|
+
const buffers = new WeakMap();
|
|
203
|
+
const creating = Symbol("NativeBuffer");
|
|
204
|
+
|
|
205
|
+
function invalidState(why) {
|
|
206
|
+
const e = new Error(`NativeBuffer ${why}`);
|
|
207
|
+
e.name = "InvalidStateError";
|
|
208
|
+
return e;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function stateOf(buffer) {
|
|
212
|
+
const s = buffers.get(buffer);
|
|
213
|
+
if (!s) throw new TypeError("not a NativeBuffer");
|
|
214
|
+
return s;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Refuses a use unless the buffer is open and, for `write`, unborrowed (reads need no writer). */
|
|
218
|
+
function borrowable(s, write) {
|
|
219
|
+
if (s.state !== "open")
|
|
220
|
+
throw invalidState(s.state === "closed" ? "is closed" : "was transferred");
|
|
221
|
+
if (s.writing || (write && s.readers > 0)) throw invalidState("is borrowed");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Bytes native code owns, as the native runtime keeps them: borrowed for
|
|
226
|
+
* one call, moved by transfer() and compute, copied only by from() and
|
|
227
|
+
* toUint8Array(). Here the bytes a borrow lends are the buffer's own.
|
|
228
|
+
*/
|
|
229
|
+
class NativeBuffer {
|
|
230
|
+
constructor(token, bytes) {
|
|
231
|
+
if (token !== creating) throw new TypeError("Illegal constructor");
|
|
232
|
+
buffers.set(this, { bytes, state: "open", readers: 0, writing: false });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
static allocate(size) {
|
|
236
|
+
if (!(size >= 0) || Math.trunc(size) !== size || size > MAX_SIZE)
|
|
237
|
+
throw new RangeError("Invalid buffer size");
|
|
238
|
+
|
|
239
|
+
bufferCounts.allocated++;
|
|
240
|
+
return new NativeBuffer(creating, new Uint8Array(size));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
static from(bytes) {
|
|
244
|
+
const buffer = NativeBuffer.allocate(bytes.length);
|
|
245
|
+
|
|
246
|
+
stateOf(buffer).bytes.set(bytes);
|
|
247
|
+
countCopy(bytes.length);
|
|
248
|
+
return buffer;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
static stats() {
|
|
252
|
+
return { ...bufferCounts };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
get byteLength() {
|
|
256
|
+
const s = stateOf(this);
|
|
257
|
+
return s.state === "open" ? s.bytes.length : 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
withRead(read) {
|
|
261
|
+
const s = stateOf(this);
|
|
262
|
+
|
|
263
|
+
borrowable(s, false);
|
|
264
|
+
s.readers++;
|
|
265
|
+
try {
|
|
266
|
+
return read(s.bytes);
|
|
267
|
+
} finally {
|
|
268
|
+
s.readers--;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
withWrite(write) {
|
|
273
|
+
const s = stateOf(this);
|
|
274
|
+
|
|
275
|
+
borrowable(s, true);
|
|
276
|
+
s.writing = true;
|
|
277
|
+
try {
|
|
278
|
+
return write(s.bytes);
|
|
279
|
+
} finally {
|
|
280
|
+
s.writing = false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
toUint8Array() {
|
|
285
|
+
return this.withRead((bytes) => {
|
|
286
|
+
countCopy(bytes.length);
|
|
287
|
+
return bytes.slice();
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
transfer() {
|
|
292
|
+
const moved = beginTransfer(this);
|
|
293
|
+
|
|
294
|
+
commitTransfer(this);
|
|
295
|
+
return moved;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
close() {
|
|
299
|
+
const s = stateOf(this);
|
|
300
|
+
|
|
301
|
+
if (s.state !== "open") return;
|
|
302
|
+
borrowable(s, true);
|
|
303
|
+
|
|
304
|
+
s.state = "closed";
|
|
305
|
+
s.bytes = new Uint8Array(0);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
[Symbol.dispose]() {
|
|
309
|
+
this.close();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// A move in two steps, as a task's input is copied: the new buffer takes
|
|
314
|
+
// the bytes at once, and the old one gives them up for good only once the
|
|
315
|
+
// whole input has been copied.
|
|
316
|
+
function beginTransfer(buffer) {
|
|
317
|
+
const s = stateOf(buffer);
|
|
318
|
+
|
|
319
|
+
borrowable(s, true);
|
|
320
|
+
s.state = "transferring";
|
|
321
|
+
return new NativeBuffer(creating, s.bytes);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function commitTransfer(buffer) {
|
|
325
|
+
const s = stateOf(buffer);
|
|
326
|
+
|
|
327
|
+
s.state = "transferred";
|
|
328
|
+
s.bytes = new Uint8Array(0);
|
|
329
|
+
bufferCounts.transfers++;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function abortTransfer(buffer) {
|
|
333
|
+
stateOf(buffer).state = "open";
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** What a copy refuses to copy: a DataCloneError, as structuredClone throws. */
|
|
337
|
+
function dataCloneError(message) {
|
|
338
|
+
const e = new Error(message);
|
|
339
|
+
e.name = "DataCloneError";
|
|
340
|
+
return e;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* A compute task's input, copied as the native runtime copies it: every
|
|
345
|
+
* object, array, map, set, date and byte buffer reached is copied once
|
|
346
|
+
* (aliases stay aliases, cycles survive), objects keep their class, and
|
|
347
|
+
* views of one buffer stay views of one copied buffer. A NativeBuffer
|
|
348
|
+
* moves instead, once: `moved` collects the moves to commit or undo.
|
|
349
|
+
*/
|
|
350
|
+
function transportCopy(value, copies = new Map(), moved = []) {
|
|
351
|
+
if (value === null || (typeof value !== "object" && typeof value !== "function")) return value;
|
|
352
|
+
if (typeof value === "function")
|
|
353
|
+
throw dataCloneError("A function cannot be copied to a compute task");
|
|
354
|
+
|
|
355
|
+
const known = copies.get(value);
|
|
356
|
+
if (known) return known;
|
|
357
|
+
|
|
358
|
+
if (value instanceof NativeBuffer) {
|
|
359
|
+
const successor = beginTransfer(value);
|
|
360
|
+
moved.push(value);
|
|
361
|
+
copies.set(value, successor);
|
|
362
|
+
return successor;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (value instanceof Uint8Array) {
|
|
366
|
+
let buffer = copies.get(value.buffer);
|
|
367
|
+
if (!buffer) {
|
|
368
|
+
buffer = value.buffer.slice(0);
|
|
369
|
+
copies.set(value.buffer, buffer);
|
|
370
|
+
}
|
|
371
|
+
const view = new Uint8Array(buffer, value.byteOffset, value.length);
|
|
372
|
+
copies.set(value, view);
|
|
373
|
+
return view;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (value instanceof Date) {
|
|
377
|
+
const date = new Date(value.getTime());
|
|
378
|
+
copies.set(value, date);
|
|
379
|
+
return date;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (value instanceof Map) {
|
|
383
|
+
const map = new Map();
|
|
384
|
+
copies.set(value, map);
|
|
385
|
+
for (const [k, v] of value)
|
|
386
|
+
map.set(transportCopy(k, copies, moved), transportCopy(v, copies, moved));
|
|
387
|
+
return map;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (value instanceof Set) {
|
|
391
|
+
const set = new Set();
|
|
392
|
+
copies.set(value, set);
|
|
393
|
+
for (const v of value) set.add(transportCopy(v, copies, moved));
|
|
394
|
+
return set;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (Array.isArray(value)) {
|
|
398
|
+
const array = [];
|
|
399
|
+
copies.set(value, array);
|
|
400
|
+
for (const v of value) array.push(transportCopy(v, copies, moved));
|
|
401
|
+
return array;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (value instanceof Promise || value instanceof AbortSignal || value instanceof AbortController)
|
|
405
|
+
throw dataCloneError(`A ${value.constructor.name} cannot be copied to a compute task`);
|
|
406
|
+
|
|
407
|
+
const object = Object.create(Object.getPrototypeOf(value));
|
|
408
|
+
copies.set(value, object);
|
|
409
|
+
// Defined, not assigned: an own "__proto__" key stays a key.
|
|
410
|
+
for (const key of Object.keys(value))
|
|
411
|
+
Object.defineProperty(object, key, {
|
|
412
|
+
value: transportCopy(value[key], copies, moved),
|
|
413
|
+
writable: true,
|
|
414
|
+
enumerable: true,
|
|
415
|
+
configurable: true,
|
|
416
|
+
});
|
|
417
|
+
return object;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Runs `task(input)` as a compute task would, on this thread: the input is
|
|
422
|
+
* copied now, the task runs in a later turn, and aborting the signal first
|
|
423
|
+
* rejects with its reason (the task then never runs).
|
|
424
|
+
*/
|
|
425
|
+
function compute(task, input, options) {
|
|
426
|
+
const signal = options && options.signal;
|
|
427
|
+
|
|
428
|
+
return new Promise((resolve, reject) => {
|
|
429
|
+
let copy;
|
|
430
|
+
const moved = [];
|
|
431
|
+
try {
|
|
432
|
+
copy = transportCopy(input, new Map(), moved);
|
|
433
|
+
} catch (e) {
|
|
434
|
+
moved.forEach(abortTransfer);
|
|
435
|
+
return void reject(e);
|
|
436
|
+
}
|
|
437
|
+
moved.forEach(commitTransfer);
|
|
438
|
+
|
|
439
|
+
if (signal && signal.aborted) return void reject(signal.reason);
|
|
440
|
+
|
|
441
|
+
let settled = false;
|
|
442
|
+
const onAbort = () => {
|
|
443
|
+
if (settled) return;
|
|
444
|
+
settled = true;
|
|
445
|
+
clearTimeout(timer);
|
|
446
|
+
reject(signal.reason);
|
|
447
|
+
};
|
|
448
|
+
const timer = setTimeout(() => {
|
|
449
|
+
if (settled) return;
|
|
450
|
+
settled = true;
|
|
451
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
452
|
+
try {
|
|
453
|
+
resolve(task(copy));
|
|
454
|
+
} catch (e) {
|
|
455
|
+
reject(e);
|
|
456
|
+
}
|
|
457
|
+
}, 0);
|
|
458
|
+
if (signal) signal.addEventListener("abort", onAbort);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
module.exports = {
|
|
463
|
+
delay,
|
|
464
|
+
error,
|
|
465
|
+
errorCode,
|
|
466
|
+
utf8Encode,
|
|
467
|
+
utf8Decode,
|
|
468
|
+
now,
|
|
469
|
+
fromCallback,
|
|
470
|
+
subscribe,
|
|
471
|
+
compute,
|
|
472
|
+
NativeBuffer,
|
|
473
|
+
};
|
package/lib/sdk/android.d.ts
CHANGED
|
@@ -1,8 +1,61 @@
|
|
|
1
1
|
// lucent:android — Android helpers for platform code.
|
|
2
|
-
import type {
|
|
2
|
+
import type { Activity, Instrumentation_ActivityResult } from "lucent:android/android.app";
|
|
3
|
+
import type { Context, Intent } from "lucent:android/android.content";
|
|
3
4
|
|
|
4
5
|
/** The application Context. */
|
|
5
6
|
export declare function appContext(): Context;
|
|
6
7
|
|
|
7
8
|
/** Whether the device runs at least API level `api` (`Build.VERSION.SDK_INT >= api`). */
|
|
8
9
|
export declare function available(platform: "android", api: number): boolean;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The Activity in front: the one last created, started or resumed and not
|
|
13
|
+
* destroyed, or null (before the first, between two, after the last).
|
|
14
|
+
* Lucent holds Activities weakly: use this one now, on the main thread
|
|
15
|
+
* (inside `main()`), and ask again later rather than keeping it.
|
|
16
|
+
*/
|
|
17
|
+
export declare function currentActivity(): Activity | null;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Starts `intent` from the Activity in front and resolves with its result
|
|
21
|
+
* (`getResultCode()`, `getResultData()`). The request survives the app's
|
|
22
|
+
* Activity being recreated meanwhile. Rejects with `ERR_NO_ACTIVITY` without
|
|
23
|
+
* an Activity, `ERR_ACTIVITY_NOT_FOUND` when no app handles the intent, and
|
|
24
|
+
* the signal's reason if it aborts first: the answer, if it still comes, is
|
|
25
|
+
* dropped (Lucent closes what it started when Android lets it).
|
|
26
|
+
*/
|
|
27
|
+
export declare function startActivityForResult(
|
|
28
|
+
intent: Intent,
|
|
29
|
+
signal?: AbortSignal,
|
|
30
|
+
): Promise<Instrumentation_ActivityResult>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Asks for runtime permissions (`"android.permission.CAMERA"`), and
|
|
34
|
+
* resolves with whether each was granted, in order. Requests wait for the
|
|
35
|
+
* one before them, as Android asks one at a time. Rejects like
|
|
36
|
+
* `startActivityForResult`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function requestPermissions(
|
|
39
|
+
permissions: string[],
|
|
40
|
+
signal?: AbortSignal,
|
|
41
|
+
): Promise<boolean[]>;
|
|
42
|
+
|
|
43
|
+
/** The lifecycle events of the app's Activities, and a new intent sent to one. */
|
|
44
|
+
export type ActivityEvent =
|
|
45
|
+
| "created"
|
|
46
|
+
| "started"
|
|
47
|
+
| "resumed"
|
|
48
|
+
| "paused"
|
|
49
|
+
| "stopped"
|
|
50
|
+
| "destroyed"
|
|
51
|
+
| "newIntent";
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Runs `handler` after each `event` of the app's Activities, with the
|
|
55
|
+
* Activity (and the intent, for "newIntent"), in a turn of the calling
|
|
56
|
+
* context. Returns the function that stops it.
|
|
57
|
+
*/
|
|
58
|
+
export declare function onActivityEvent(
|
|
59
|
+
event: ActivityEvent,
|
|
60
|
+
handler: (activity: Activity, intent: Intent | null) => void,
|
|
61
|
+
): () => void;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lucent:compose — Jetpack Compose, for a component's content on Android.
|
|
3
|
+
*
|
|
4
|
+
* Experimental and internal: this module resolves only when the
|
|
5
|
+
* LUCENT_VIEWS environment variable is `fabric`, and changes without notice
|
|
6
|
+
* until Lucent's views are public.
|
|
7
|
+
*
|
|
8
|
+
* A component returns its content: JSX, compiled to Kotlin. A composable
|
|
9
|
+
* showing UI is an element (`<Box modifier={…}>`), whose props are
|
|
10
|
+
* Kotlin's named arguments and whose children are its trailing content
|
|
11
|
+
* lambda, or a function of the lambda's scope (`{(row) => …}`); a lazy
|
|
12
|
+
* list's items are elements of its scope (`{(list) => <list.items
|
|
13
|
+
* items={…} key={…}>…</list.items>}`). The component's statements that
|
|
14
|
+
* compose (`animateDpAsState(…)`, `remember(…)`, `LaunchedEffect(…)`)
|
|
15
|
+
* run in the content, in order, before what the JSX shows; the rest of
|
|
16
|
+
* its code is its setup, which runs once and reads none of their values.
|
|
17
|
+
* The content reads the setup's props, signals and functions: a number,
|
|
18
|
+
* boolean or string the setup computes is Compose state, and a setup
|
|
19
|
+
* function the content calls from a callback runs as Lucent code. Other
|
|
20
|
+
* functions take Kotlin's parameters in order, an options object holding
|
|
21
|
+
* the defaulted ones where Kotlin's order cannot (`spring({ stiffness })`);
|
|
22
|
+
* modifiers, animations and effects are Compose's.
|
|
23
|
+
*
|
|
24
|
+
* A value Compose pairs with the callback of its changes (`value` and
|
|
25
|
+
* `onValueChange`) takes a setup signal bound to both instead,
|
|
26
|
+
* `bind(signal)` from lucent:ui.
|
|
27
|
+
*
|
|
28
|
+
* Below lucent:compose's own declarations come Compose's, under their own
|
|
29
|
+
* names, made from the bindings of the Compose release Lucent builds with.
|
|
30
|
+
* Float, Int, Long, Short and Byte are Kotlin's number types: a number the
|
|
31
|
+
* body passes to one is converted.
|
|
32
|
+
*
|
|
33
|
+
* @experimental
|
|
34
|
+
*/
|
|
35
|
+
import type { ViewGroup } from "lucent:android/android.view";
|
|
36
|
+
import type { Bound } from "lucent:ui";
|
|
37
|
+
|
|
38
|
+
/** Kotlin's Float: a number, converted where the body passes it. */
|
|
39
|
+
export type Float = number & { readonly "lucent:compose.Float"?: never };
|
|
40
|
+
/** Kotlin's Int. */
|
|
41
|
+
export type Int = number & { readonly "lucent:compose.Int"?: never };
|
|
42
|
+
/** Kotlin's Long. */
|
|
43
|
+
export type Long = number & { readonly "lucent:compose.Long"?: never };
|
|
44
|
+
/** Kotlin's Short. */
|
|
45
|
+
export type Short = number & { readonly "lucent:compose.Short"?: never };
|
|
46
|
+
/** Kotlin's Byte. */
|
|
47
|
+
export type Byte = number & { readonly "lucent:compose.Byte"?: never };
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* What an element shows (`<Box …/>`): an item of content, the value of
|
|
51
|
+
* JSX, never a value setup holds.
|
|
52
|
+
*/
|
|
53
|
+
export interface Composed {
|
|
54
|
+
readonly "lucent:compose.Composed": true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Content: what composables show, in order, or nothing where a condition is false. */
|
|
58
|
+
export type Shown = Composed | false | null | undefined | readonly Shown[];
|
|
59
|
+
|
|
60
|
+
/** A @Composable content lambda given as a prop: its statements compose, and it returns what it shows. */
|
|
61
|
+
export type Content = () => Shown;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A content lambda composed in a receiver scope (a Row's RowScope), given
|
|
65
|
+
* as a prop: its first parameter is the scope, whose methods and Modifier
|
|
66
|
+
* run in it.
|
|
67
|
+
*/
|
|
68
|
+
export type ScopedContent<S> = (scope: S) => Shown;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* An element's children composed in a receiver scope: what they show, or
|
|
72
|
+
* a function of the scope showing it, for children that use the scope
|
|
73
|
+
* (`{(row) => <Text modifier={row.Modifier.weight(1)} … />}`).
|
|
74
|
+
*/
|
|
75
|
+
export type ScopedChildren<S> = Shown | ((scope: S) => Shown);
|
|
76
|
+
|
|
77
|
+
/**
|
|
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.
|
|
80
|
+
*/
|
|
81
|
+
export declare namespace JSX {
|
|
82
|
+
type Element = Composed;
|
|
83
|
+
|
|
84
|
+
interface ElementChildrenAttribute {
|
|
85
|
+
children: {};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface IntrinsicElements {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The Android view hosting a composition: the view a component returning JSX makes. */
|
|
92
|
+
export declare class ComposeView extends ViewGroup {
|
|
93
|
+
private constructor();
|
|
94
|
+
}
|
|
Binary file
|