@sigx/lynx-runtime 0.4.0 → 0.4.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/dist/animated/animated-value.d.ts +2 -2
- package/dist/animated/animated-value.js +15 -0
- package/dist/animated/shared-value.d.ts +1 -1
- package/dist/animated/shared-value.js +94 -0
- package/dist/animated/use-animated-style.d.ts +3 -3
- package/dist/animated/use-animated-style.js +53 -0
- package/dist/animated-bridge.js +71 -0
- package/dist/bg-bridge.js +63 -0
- package/dist/event-registry.js +75 -0
- package/dist/flush.d.ts +1 -1
- package/dist/flush.js +8 -0
- package/dist/hmr.js +119 -39
- package/dist/index.d.ts +24 -22
- package/dist/index.js +37 -849
- package/dist/jsx.d.ts +29 -3
- package/dist/jsx.js +19 -0
- package/dist/main-thread-ref.js +134 -0
- package/dist/model-processor.js +76 -0
- package/dist/mt-hmr-bridge.js +125 -53
- package/dist/native/gesture-detector.d.ts +1 -1
- package/dist/native/gesture-detector.js +340 -0
- package/dist/native/index.d.ts +2 -2
- package/dist/native/index.js +1 -0
- package/dist/nodeOps.d.ts +1 -1
- package/dist/nodeOps.js +319 -0
- package/dist/op-queue.js +213 -0
- package/dist/render.d.ts +1 -1
- package/dist/render.js +125 -0
- package/dist/run-on-background.d.ts +1 -1
- package/dist/run-on-background.js +201 -0
- package/dist/shadow-element.js +91 -0
- package/dist/threading.d.ts +1 -1
- package/dist/threading.js +124 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +10 -0
- package/dist/use-element-layout.d.ts +72 -0
- package/dist/use-element-layout.js +40 -0
- package/package.json +10 -8
- package/dist/hmr.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mt-hmr-bridge.js.map +0 -1
|
@@ -12,5 +12,5 @@
|
|
|
12
12
|
*
|
|
13
13
|
* The old names continue to work via these re-exports for one minor cycle.
|
|
14
14
|
*/
|
|
15
|
-
export { SharedValue as AnimatedValue, useSharedValue as useAnimatedValue, } from './shared-value';
|
|
16
|
-
export type { SharedValueState as AnimatedValueState } from './shared-value';
|
|
15
|
+
export { SharedValue as AnimatedValue, useSharedValue as useAnimatedValue, } from './shared-value.js';
|
|
16
|
+
export type { SharedValueState as AnimatedValueState } from './shared-value.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated since Phase 2.8 — renamed to `SharedValue` to reflect that the
|
|
3
|
+
* primitive is a general MT-writeable, BG-observable cross-thread value, not
|
|
4
|
+
* an animation-specific construct. Animation is one customer; scroll, sensors,
|
|
5
|
+
* and gestures are equally first-class consumers.
|
|
6
|
+
*
|
|
7
|
+
* Import from `@sigx/lynx` directly:
|
|
8
|
+
*
|
|
9
|
+
* - `useAnimatedValue` → `useSharedValue`
|
|
10
|
+
* - `AnimatedValue` → `SharedValue`
|
|
11
|
+
* - `AnimatedValueState` → `SharedValueState`
|
|
12
|
+
*
|
|
13
|
+
* The old names continue to work via these re-exports for one minor cycle.
|
|
14
|
+
*/
|
|
15
|
+
export { SharedValue as AnimatedValue, useSharedValue as useAnimatedValue, } from './shared-value.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PrimitiveSignal } from '@sigx/reactivity';
|
|
2
|
-
import { MainThreadRef } from '../main-thread-ref';
|
|
2
|
+
import { MainThreadRef } from '../main-thread-ref.js';
|
|
3
3
|
/**
|
|
4
4
|
* Internal envelope shape stored under `MainThreadRef.current`. Wrapping the
|
|
5
5
|
* value in `{ value: T }` (rather than a bare `T`) lets MT worklets mutate
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { onUnmounted } from '@sigx/runtime-core';
|
|
2
|
+
import { MainThreadRef } from '../main-thread-ref.js';
|
|
3
|
+
import { pushOp, scheduleFlush } from '../op-queue.js';
|
|
4
|
+
import { registerBgSink, unregisterBgSink } from '../animated-bridge.js';
|
|
5
|
+
import { OP } from '@sigx/lynx-runtime-internal';
|
|
6
|
+
/**
|
|
7
|
+
* MT-writeable, BG-readable cross-thread value with sigx reactive tracking.
|
|
8
|
+
*
|
|
9
|
+
* **Not animation-specific.** Animation is one customer (see `@sigx/motion`),
|
|
10
|
+
* gestures is another (`<Draggable translateX={sv} />`), scroll is another
|
|
11
|
+
* (`useScrollViewOffset`). Anywhere fast-or-frequent state lives natively on
|
|
12
|
+
* MT and you want BG to observe it reactively, this is the primitive.
|
|
13
|
+
*
|
|
14
|
+
* **MT side** — inside `'main thread'` worklets, mutate via `sv.current.value
|
|
15
|
+
* = newValue`. The MainThreadRef machinery makes the mutation stick across
|
|
16
|
+
* worklet invocations on the same wvid; the bridge picks up the new value on
|
|
17
|
+
* the next `__FlushElementTree` boundary.
|
|
18
|
+
*
|
|
19
|
+
* **BG side** — read via `sv.value` to get the latest published snapshot.
|
|
20
|
+
* Sigx tracking applies, so an `effect(() => console.log(sv.value))` re-runs
|
|
21
|
+
* whenever an MT publish ingests a new value. Writes from BG are read-only;
|
|
22
|
+
* dev-mode warns to point users back at the MT path.
|
|
23
|
+
*
|
|
24
|
+
* The class extends `MainThreadRef<{value:T}>` so that BG-side serialization
|
|
25
|
+
* (`nodeOps.ts:sanitizeCaptured`) recognizes it as a worklet ref and ships
|
|
26
|
+
* `{_wvid, _initValue}` over the wire to the MT bundle.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* const tx = useSharedValue(0);
|
|
31
|
+
*
|
|
32
|
+
* <Draggable translateX={tx} />
|
|
33
|
+
* <text>x = {tx.value}</text> // BG-reactive, updates per drag frame
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export class SharedValue extends MainThreadRef {
|
|
37
|
+
/** @internal — populated by `useSharedValue`. Reads here back the BG signal. */
|
|
38
|
+
_bgSignal;
|
|
39
|
+
constructor(initial) {
|
|
40
|
+
super({ value: initial });
|
|
41
|
+
}
|
|
42
|
+
/** @internal */
|
|
43
|
+
_bind(sig) {
|
|
44
|
+
this._bgSignal = sig;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* BG-side reactive read. Returns the latest snapshot published by the MT
|
|
48
|
+
* bridge (or the initial value before any publish has occurred).
|
|
49
|
+
*/
|
|
50
|
+
get value() {
|
|
51
|
+
return this._bgSignal.value;
|
|
52
|
+
}
|
|
53
|
+
set value(_v) {
|
|
54
|
+
// Reach for process via globalThis so the package doesn't pull in
|
|
55
|
+
// @types/node just for a dev-mode warning. NODE_ENV defaults to a
|
|
56
|
+
// non-production string, so the warning fires unless the bundler has
|
|
57
|
+
// explicitly substituted it to 'production'.
|
|
58
|
+
const env = globalThis
|
|
59
|
+
.process?.env?.['NODE_ENV'];
|
|
60
|
+
if (env !== 'production') {
|
|
61
|
+
console.warn('[sigx] SharedValue.value is read-only on the BG thread. ' +
|
|
62
|
+
'Mutate via the MT worklet (sv.current.value = v).');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Allocate a `SharedValue<T>` whose MT-side mutations are observable on the
|
|
68
|
+
* BG thread via sigx reactivity. Bind it to a component prop or read its
|
|
69
|
+
* `.value` from a BG `effect`.
|
|
70
|
+
*
|
|
71
|
+
* Lifecycle:
|
|
72
|
+
* - On allocate: pushes `INIT_MT_REF` (creates the MT-side refMap holder)
|
|
73
|
+
* and `REGISTER_AV_BRIDGE` (tells MT to track this wvid in its diff/
|
|
74
|
+
* publish pass). Allocates a BG-side signal mirror keyed by wvid.
|
|
75
|
+
* - On the owning component's unmount: pushes `UNREGISTER_AV_BRIDGE` and
|
|
76
|
+
* `RELEASE_MT_REF`, drops the BG signal.
|
|
77
|
+
*/
|
|
78
|
+
export function useSharedValue(initial) {
|
|
79
|
+
const sv = new SharedValue(initial);
|
|
80
|
+
// BG: register the signal mirror under the wvid allocated in super().
|
|
81
|
+
sv._bind(registerBgSink(sv._wvid, initial));
|
|
82
|
+
// MT: create the refMap entry (envelope shape) and begin tracking this
|
|
83
|
+
// wvid in the diff/publish pass.
|
|
84
|
+
pushOp(OP.INIT_MT_REF, sv._wvid, sv._initValue);
|
|
85
|
+
pushOp(OP.REGISTER_AV_BRIDGE, sv._wvid, initial);
|
|
86
|
+
scheduleFlush();
|
|
87
|
+
onUnmounted(() => {
|
|
88
|
+
pushOp(OP.UNREGISTER_AV_BRIDGE, sv._wvid);
|
|
89
|
+
pushOp(OP.RELEASE_MT_REF, sv._wvid);
|
|
90
|
+
scheduleFlush();
|
|
91
|
+
unregisterBgSink(sv._wvid);
|
|
92
|
+
});
|
|
93
|
+
return sv;
|
|
94
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { MainThreadRef } from '../main-thread-ref';
|
|
2
|
-
import type { MainThread } from '../jsx';
|
|
1
|
+
import type { MainThreadRef } from '../main-thread-ref.js';
|
|
2
|
+
import type { MainThread } from '../jsx.js';
|
|
3
3
|
import type { BuiltinMapperName, MapperParams } from '@sigx/lynx-runtime-internal';
|
|
4
|
-
import type { SharedValue } from './shared-value';
|
|
4
|
+
import type { SharedValue } from './shared-value.js';
|
|
5
5
|
export type { BuiltinMapperName, MapperParams };
|
|
6
6
|
/** Reset hook for tests. */
|
|
7
7
|
export declare function resetAnimatedStyleBindingIds(): void;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { onUnmounted } from '@sigx/runtime-core';
|
|
2
|
+
import { pushOp, scheduleFlush } from '../op-queue.js';
|
|
3
|
+
import { OP } from '@sigx/lynx-runtime-internal';
|
|
4
|
+
let nextBindingId = 1;
|
|
5
|
+
/** Reset hook for tests. */
|
|
6
|
+
export function resetAnimatedStyleBindingIds() {
|
|
7
|
+
nextBindingId = 1;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Returns the next binding id without incrementing — internal helper used by
|
|
11
|
+
* the `useAnimatedStyle` overloads to share allocation. Not exported.
|
|
12
|
+
*/
|
|
13
|
+
function allocBindingId() {
|
|
14
|
+
return nextBindingId++;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Bind a `MainThreadRef`'s element style to a `SharedValue` via a named
|
|
18
|
+
* mapper. The MT-side runtime applies the mapper's output via
|
|
19
|
+
* `setStyleProperties` on every flush boundary where the SharedValue's value changed.
|
|
20
|
+
*
|
|
21
|
+
* The mapper runs on the **Main Thread** with no thread crossing per frame —
|
|
22
|
+
* the only inputs are the SharedValue's value (already on MT) and the `params` shipped
|
|
23
|
+
* once in the registration op. Because mappers are looked up by *name*, the
|
|
24
|
+
* SWC worklet transform doesn't have to capture a function reference (which
|
|
25
|
+
* it can't), so this fits the existing worklet pipeline cleanly.
|
|
26
|
+
*
|
|
27
|
+
* Multiple bindings on the same element compose into a single
|
|
28
|
+
* `setStyleProperties` call per flush. `transform` outputs concatenate in
|
|
29
|
+
* registration order (e.g. `translateX(50px) translateY(20px)`); other style
|
|
30
|
+
* keys merge by last-write-wins. When ANY binding on an element is dirty,
|
|
31
|
+
* ALL of that element's bindings re-run so partial outputs don't drop the
|
|
32
|
+
* unchanged-axis contribution.
|
|
33
|
+
*
|
|
34
|
+
* @example Ghost follower at 0.5×
|
|
35
|
+
* ```tsx
|
|
36
|
+
* const tx = useSharedValue(0);
|
|
37
|
+
* const ghostRef = useMainThreadRef<MainThread.Element | null>(null);
|
|
38
|
+
*
|
|
39
|
+
* useAnimatedStyle(ghostRef, tx, 'translateX', { factor: 0.5 });
|
|
40
|
+
*
|
|
41
|
+
* <Draggable translateX={tx} />
|
|
42
|
+
* <view main-thread:ref={ghostRef} style={...} />
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function useAnimatedStyle(elRef, sv, mapperName, params) {
|
|
46
|
+
const bindingId = allocBindingId();
|
|
47
|
+
pushOp(OP.REGISTER_AV_STYLE_BINDING, bindingId, elRef._wvid, sv._wvid, mapperName, params ?? null);
|
|
48
|
+
scheduleFlush();
|
|
49
|
+
onUnmounted(() => {
|
|
50
|
+
pushOp(OP.UNREGISTER_AV_STYLE_BINDING, bindingId);
|
|
51
|
+
scheduleFlush();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BG-side SharedValue bridge — receives MT-thread publishes.
|
|
3
|
+
*
|
|
4
|
+
* Maintains a `wvid → signal<T>` registry. The MT-side bridge in
|
|
5
|
+
* `@sigx/lynx-runtime-main/animated-bridge-mt.ts` dispatches batched
|
|
6
|
+
* `Lynx.Sigx.AvPublish` events with `[wvid, value]` tuples; `bg-bridge.ts`
|
|
7
|
+
* routes them through `ingestAvPublishes()` here, which writes each new
|
|
8
|
+
* value into the corresponding signal. Sigx reactivity tracking then re-runs
|
|
9
|
+
* any `effect` that read `sv.value` on BG.
|
|
10
|
+
*
|
|
11
|
+
* Producer: the `useSharedValue` hook in `@sigx/lynx` allocates the
|
|
12
|
+
* BG-side signal via `registerBgSink(wvid, initial)` and tears it down via
|
|
13
|
+
* `unregisterBgSink(wvid)` on component unmount.
|
|
14
|
+
*
|
|
15
|
+
* Naming note: the wire-format ops (`REGISTER_AV_BRIDGE`, `UNREGISTER_AV_BRIDGE`,
|
|
16
|
+
* `Lynx.Sigx.AvPublish`) keep their original `Av` prefix as infrastructure
|
|
17
|
+
* constants. The user-facing primitive renamed from `AnimatedValue` to
|
|
18
|
+
* `SharedValue` in Phase 2.8.
|
|
19
|
+
*/
|
|
20
|
+
import { signal } from '@sigx/reactivity';
|
|
21
|
+
const bgRegistry = new Map();
|
|
22
|
+
/**
|
|
23
|
+
* Allocate a BG-side signal mirror for the given wvid. Returns the signal so
|
|
24
|
+
* the caller can read it via `.value` — sigx tracking applies as usual.
|
|
25
|
+
*
|
|
26
|
+
* Idempotent on the wvid: if a signal is already registered, returns the
|
|
27
|
+
* existing one without resetting its value (avoids losing in-flight publishes
|
|
28
|
+
* during HMR-triggered re-registration).
|
|
29
|
+
*/
|
|
30
|
+
export function registerBgSink(wvid, initial) {
|
|
31
|
+
const existing = bgRegistry.get(wvid);
|
|
32
|
+
if (existing)
|
|
33
|
+
return existing;
|
|
34
|
+
// sigx's `signal` has Primitive vs object overloads; SharedValues hold
|
|
35
|
+
// primitives in practice (numbers, strings) and the bridge only ever
|
|
36
|
+
// writes through `.value`, so a single-overload selection via cast keeps
|
|
37
|
+
// the unconstrained T usable here without forcing SharedValue consumers
|
|
38
|
+
// to type-narrow at every call site.
|
|
39
|
+
const s = signal(initial);
|
|
40
|
+
bgRegistry.set(wvid, s);
|
|
41
|
+
return s;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Drop the BG-side signal for this wvid. Called on component unmount.
|
|
45
|
+
* Subsequent `ingestPublish` entries for this wvid become no-ops.
|
|
46
|
+
*/
|
|
47
|
+
export function unregisterBgSink(wvid) {
|
|
48
|
+
bgRegistry.delete(wvid);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Ingest a batch of `[wvid, value]` tuples from the MT bridge. Writes each
|
|
52
|
+
* into the corresponding signal so any `effect` that read it re-runs on the
|
|
53
|
+
* sigx scheduler's next tick. Tuples for unregistered wvids (race with
|
|
54
|
+
* unmount) are silently dropped.
|
|
55
|
+
*/
|
|
56
|
+
export function ingestAvPublishes(updates) {
|
|
57
|
+
for (const [wvid, value] of updates) {
|
|
58
|
+
const s = bgRegistry.get(wvid);
|
|
59
|
+
if (!s)
|
|
60
|
+
continue;
|
|
61
|
+
s.value = value;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Reset hook — for testing. Drops every registered sink. */
|
|
65
|
+
export function resetBgAvBridge() {
|
|
66
|
+
bgRegistry.clear();
|
|
67
|
+
}
|
|
68
|
+
/** Test hook — number of currently registered sinks. */
|
|
69
|
+
export function bgAvSinkCount() {
|
|
70
|
+
return bgRegistry.size;
|
|
71
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BG-side listener for the MT→BG `Lynx.Sigx.PublishEvent` channel.
|
|
3
|
+
*
|
|
4
|
+
* The MT-side hybrid worklet (`lynx-runtime-main/src/hybrid-worklet.ts`) calls
|
|
5
|
+
* `lynx.getJSContext().dispatchEvent({ type: 'Lynx.Sigx.PublishEvent', data })`
|
|
6
|
+
* to fire the BG handler whose sign is captured in the hybrid ctx. We listen
|
|
7
|
+
* for that event here and route through the existing event-registry's
|
|
8
|
+
* `publishEvent` — the same dispatcher Lynx native calls when a normal
|
|
9
|
+
* `bindtap` fires on BG. The user's BG handler runs in the same call-stack
|
|
10
|
+
* shape it always has, so signal updates / `count.value++` etc. work without
|
|
11
|
+
* any awareness that the trigger came from MT.
|
|
12
|
+
*
|
|
13
|
+
* Side-effect import from `index.ts` so the listener is wired before any
|
|
14
|
+
* user code runs.
|
|
15
|
+
*
|
|
16
|
+
* CROSS-THREAD ASYMMETRY (per @lynx-js/react/runtime/lib/worklet/call/runOnBackground.js):
|
|
17
|
+
* - MT → BG dispatch: MT calls `lynx.getJSContext().dispatchEvent(...)`,
|
|
18
|
+
* BG listens via `lynx.getCoreContext().addEventListener(...)`.
|
|
19
|
+
* - BG → MT dispatch: BG calls `lynx.getCoreContext().dispatchEvent(...)`,
|
|
20
|
+
* MT listens via `lynx.getJSContext().addEventListener(...)`.
|
|
21
|
+
* Each side calls a DIFFERENT method to reach the other thread — they're not
|
|
22
|
+
* symmetric. Listening on `lynx.getJSContext()` from BG just listens on BG's
|
|
23
|
+
* own context (no cross-thread events arrive).
|
|
24
|
+
*
|
|
25
|
+
* `lynx` is closure-injected by RuntimeWrapperWebpackPlugin (declared in
|
|
26
|
+
* shims.d.ts). It is NOT available as `globalThis.lynx` — use the free
|
|
27
|
+
* identifier directly.
|
|
28
|
+
*/
|
|
29
|
+
import { publishEvent } from './event-registry.js';
|
|
30
|
+
import { ingestAvPublishes } from './animated-bridge.js';
|
|
31
|
+
const lynxObj = typeof lynx !== 'undefined'
|
|
32
|
+
? lynx
|
|
33
|
+
: undefined;
|
|
34
|
+
const ctx = lynxObj?.getCoreContext?.();
|
|
35
|
+
if (ctx?.addEventListener) {
|
|
36
|
+
ctx.addEventListener('Lynx.Sigx.PublishEvent', (e) => {
|
|
37
|
+
let payload;
|
|
38
|
+
try {
|
|
39
|
+
payload = JSON.parse(e.data);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return; // malformed bridge message — drop
|
|
43
|
+
}
|
|
44
|
+
if (typeof payload.sign === 'string') {
|
|
45
|
+
publishEvent(payload.sign, payload.event);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
// SharedValue bridge: each event payload is an array of
|
|
49
|
+
// `[wvid, value]` tuples coalesced from one MT flush window. See
|
|
50
|
+
// `animated-bridge.ts` and `@sigx/lynx-runtime-main/animated-bridge-mt.ts`.
|
|
51
|
+
ctx.addEventListener('Lynx.Sigx.AvPublish', (e) => {
|
|
52
|
+
let updates;
|
|
53
|
+
try {
|
|
54
|
+
updates = JSON.parse(e.data);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(updates)) {
|
|
60
|
+
ingestAvPublishes(updates);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sign-based event handler registry for the Background Thread.
|
|
3
|
+
*
|
|
4
|
+
* When patchProp registers an event handler, it gets a unique sign string.
|
|
5
|
+
* The Main Thread stores this sign with __AddEvent(). When Native fires an
|
|
6
|
+
* event it calls publishEvent(sign, data) on the BG Thread, which looks up
|
|
7
|
+
* the handler and executes it directly — no cross-thread round-trip.
|
|
8
|
+
*
|
|
9
|
+
* sigx uses signal closures, so event handlers are plain functions captured by the component's
|
|
10
|
+
* render closure.
|
|
11
|
+
*/
|
|
12
|
+
// The consumer bundle can materialise multiple event-registry module instances
|
|
13
|
+
// (for example via separate entry-background/runtime graphs). Store the state
|
|
14
|
+
// on globalThis so register() and publishEvent() still see the same handlers.
|
|
15
|
+
const REGISTRY_STATE_KEY = '__SIGX_LYNX_EVENT_REGISTRY__';
|
|
16
|
+
function getRegistryState() {
|
|
17
|
+
const g = globalThis;
|
|
18
|
+
let state = g[REGISTRY_STATE_KEY];
|
|
19
|
+
if (!state) {
|
|
20
|
+
state = {
|
|
21
|
+
signCounter: 0,
|
|
22
|
+
handlers: new Map(),
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(g, REGISTRY_STATE_KEY, {
|
|
25
|
+
value: state,
|
|
26
|
+
configurable: true,
|
|
27
|
+
enumerable: false,
|
|
28
|
+
writable: true,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return state;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Register a handler and return a unique sign string.
|
|
35
|
+
* The sign is passed to __AddEvent so that the MTS can route events back.
|
|
36
|
+
*/
|
|
37
|
+
export function register(handler) {
|
|
38
|
+
const state = getRegistryState();
|
|
39
|
+
const sign = `sigx:${state.signCounter++}`;
|
|
40
|
+
state.handlers.set(sign, handler);
|
|
41
|
+
return sign;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Update the handler for an existing sign without changing the sign.
|
|
45
|
+
* Used on re-renders: keeps the same sign registered on the Main Thread
|
|
46
|
+
* while pointing it to the freshest handler closure.
|
|
47
|
+
*/
|
|
48
|
+
export function updateHandler(sign, handler) {
|
|
49
|
+
getRegistryState().handlers.set(sign, handler);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get the current handler for a sign.
|
|
53
|
+
*/
|
|
54
|
+
export function getHandler(sign) {
|
|
55
|
+
return getRegistryState().handlers.get(sign);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Unregister a handler by its sign.
|
|
59
|
+
*/
|
|
60
|
+
export function unregister(sign) {
|
|
61
|
+
getRegistryState().handlers.delete(sign);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Called by Lynx Native when an event fires on the BG Thread.
|
|
65
|
+
* Looks up the handler by sign and invokes it with the event data.
|
|
66
|
+
*/
|
|
67
|
+
export function publishEvent(sign, data) {
|
|
68
|
+
getRegistryState().handlers.get(sign)?.(data);
|
|
69
|
+
}
|
|
70
|
+
/** Reset all state — for testing only. */
|
|
71
|
+
export function resetRegistry() {
|
|
72
|
+
const state = getRegistryState();
|
|
73
|
+
state.signCounter = 0;
|
|
74
|
+
state.handlers.clear();
|
|
75
|
+
}
|
package/dist/flush.d.ts
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
* directly. Instead, ops are batched in a queue and flushed to the Main Thread
|
|
6
6
|
* via sigxPatchUpdate. This file exists for backwards-compatible imports.
|
|
7
7
|
*/
|
|
8
|
-
export { scheduleFlush, flushNow, resetOpQueue as resetFlushState } from './op-queue';
|
|
8
|
+
export { scheduleFlush, flushNow, resetOpQueue as resetFlushState } from './op-queue.js';
|
package/dist/flush.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flush scheduler — re-exports from op-queue.ts.
|
|
3
|
+
*
|
|
4
|
+
* In the new architecture, the BG thread no longer calls __FlushElementTree
|
|
5
|
+
* directly. Instead, ops are batched in a queue and flushed to the Main Thread
|
|
6
|
+
* via sigxPatchUpdate. This file exists for backwards-compatible imports.
|
|
7
|
+
*/
|
|
8
|
+
export { scheduleFlush, flushNow, resetOpQueue as resetFlushState } from './op-queue.js';
|
package/dist/hmr.js
CHANGED
|
@@ -1,42 +1,122 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
/**
|
|
2
|
+
* HMR runtime for sigx-lynx (rspack/rsbuild).
|
|
3
|
+
*
|
|
4
|
+
* The `@sigx/lynx` meta package inlines runtime-core into a single bundle,
|
|
5
|
+
* so `@sigx/runtime-core/internals` would resolve to a *different* copy of
|
|
6
|
+
* the plugin registry than the one used by `component()`. To work around
|
|
7
|
+
* this the HMR loader injects a call to `initHMR(registerComponentPlugin)`
|
|
8
|
+
* where `registerComponentPlugin` is imported from `@sigx/lynx` (the same
|
|
9
|
+
* bundle the app uses), ensuring a single shared plugin array.
|
|
10
|
+
*
|
|
11
|
+
* Flow:
|
|
12
|
+
* 1. Loader prepends:
|
|
13
|
+
* import { __registerComponentPlugin } from '@sigx/lynx';
|
|
14
|
+
* import { initHMR, registerHMRModule } from '@sigx/lynx-runtime/hmr';
|
|
15
|
+
* initHMR(__registerComponentPlugin);
|
|
16
|
+
* registerHMRModule('<moduleId>');
|
|
17
|
+
* 2. On first call, `initHMR` installs the onDefine plugin.
|
|
18
|
+
* 3. On HMR update the module re-executes, `registerHMRModule` resets
|
|
19
|
+
* the per-module index, `component()` fires `onDefine`, and existing
|
|
20
|
+
* instances are patched in-place (property-ops only — no crash).
|
|
21
|
+
*/
|
|
22
|
+
// Track instances by component ID (moduleId:index)
|
|
23
|
+
const instancesByComponentId = new Map();
|
|
24
|
+
// Track component definition order within each module
|
|
25
|
+
const moduleComponentIndex = new Map();
|
|
26
|
+
// Current module being registered
|
|
27
|
+
let currentModuleId = null;
|
|
28
|
+
// The renderer's currentInstance setter — captured from the app-side bundle
|
|
29
|
+
// so push/pop targets the SAME instance stack the renderer reads from. If
|
|
30
|
+
// missing (older app version that doesn't inject it), HMR patches skip the
|
|
31
|
+
// push/pop and rely on the caller having no context-dependent hooks.
|
|
32
|
+
let setCurrentInstance = null;
|
|
33
|
+
let installed = false;
|
|
34
|
+
/**
|
|
35
|
+
* Initialise the HMR plugin using the *app-side* registerComponentPlugin.
|
|
36
|
+
* Called once by the loader-injected preamble. Idempotent.
|
|
37
|
+
*
|
|
38
|
+
* `setCurrentInstanceFn` is the renderer's instance-stack push/pop helper
|
|
39
|
+
* (re-exported from `@sigx/lynx` as `__setCurrentInstanceForHMR`). Without
|
|
40
|
+
* it, re-running a screen's setup during HMR throws on hooks that depend on
|
|
41
|
+
* provide/inject (`useNav`, etc.) because the renderer's currentInstance is
|
|
42
|
+
* `null` when called outside the normal mount path.
|
|
43
|
+
*/
|
|
44
|
+
export function initHMR(registerComponentPlugin, setCurrentInstanceFn) {
|
|
45
|
+
if (installed)
|
|
46
|
+
return;
|
|
47
|
+
installed = true;
|
|
48
|
+
if (setCurrentInstanceFn) {
|
|
49
|
+
setCurrentInstance = setCurrentInstanceFn;
|
|
50
|
+
}
|
|
51
|
+
registerComponentPlugin({
|
|
52
|
+
onDefine(name, factory, setup) {
|
|
53
|
+
const componentId = getNextComponentId();
|
|
54
|
+
if (!componentId)
|
|
55
|
+
return;
|
|
56
|
+
factory.__hmrId = componentId;
|
|
57
|
+
const existingInstances = instancesByComponentId.get(componentId);
|
|
58
|
+
if (existingInstances && existingInstances.size > 0) {
|
|
59
|
+
// HMR update: patch all existing instances with the new setup.
|
|
60
|
+
// The renderer pushes the active instance onto a stack before
|
|
61
|
+
// calling setup so that hooks like `useNav()` can resolve
|
|
62
|
+
// provide/inject up the parent chain. We're calling setup
|
|
63
|
+
// *outside* the renderer's mount path here, so we mirror the
|
|
64
|
+
// push/pop ourselves — otherwise context-dependent hooks
|
|
65
|
+
// throw with messages like "no <NavigationRoot> is mounted".
|
|
66
|
+
existingInstances.forEach(instance => {
|
|
67
|
+
const prev = setCurrentInstance ? setCurrentInstance(instance.ctx) : null;
|
|
68
|
+
try {
|
|
69
|
+
const newRenderFn = setup(instance.ctx);
|
|
70
|
+
instance.ctx.renderFn = newRenderFn;
|
|
71
|
+
instance.ctx.update();
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
const msg = e?.message ?? String(e);
|
|
75
|
+
const stack = e?.stack ?? '<no stack>';
|
|
76
|
+
console.error(`[sigx-hmr] Failed to update ${name || 'component'}: ${msg}\n${stack}`);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
if (setCurrentInstance)
|
|
80
|
+
setCurrentInstance(prev);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Wrap setup to track future instances
|
|
85
|
+
const originalSetup = setup;
|
|
86
|
+
factory.__setup = (ctx) => {
|
|
87
|
+
const renderFn = originalSetup(ctx);
|
|
88
|
+
const instance = { ctx };
|
|
89
|
+
let instances = instancesByComponentId.get(componentId);
|
|
90
|
+
if (!instances) {
|
|
91
|
+
instances = new Set();
|
|
92
|
+
instancesByComponentId.set(componentId, instances);
|
|
93
|
+
}
|
|
94
|
+
instances.add(instance);
|
|
95
|
+
ctx.onUnmounted(() => {
|
|
96
|
+
const instances = instancesByComponentId.get(componentId);
|
|
97
|
+
if (instances)
|
|
98
|
+
instances.delete(instance);
|
|
99
|
+
});
|
|
100
|
+
return renderFn;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
});
|
|
30
104
|
}
|
|
31
|
-
|
|
32
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Register the current module for HMR tracking.
|
|
107
|
+
* Called at the top of each transformed module by the HMR loader.
|
|
108
|
+
*/
|
|
109
|
+
export function registerHMRModule(moduleId) {
|
|
110
|
+
currentModuleId = moduleId;
|
|
111
|
+
moduleComponentIndex.set(moduleId, 0);
|
|
33
112
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Get the next component ID for the current module.
|
|
115
|
+
*/
|
|
116
|
+
function getNextComponentId() {
|
|
117
|
+
if (!currentModuleId)
|
|
118
|
+
return null;
|
|
119
|
+
const index = moduleComponentIndex.get(currentModuleId) || 0;
|
|
120
|
+
moduleComponentIndex.set(currentModuleId, index + 1);
|
|
121
|
+
return `${currentModuleId}:${index}`;
|
|
38
122
|
}
|
|
39
|
-
//#endregion
|
|
40
|
-
export { a as initHMR, o as registerHMRModule };
|
|
41
|
-
|
|
42
|
-
//# sourceMappingURL=hmr.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
import './jsx';
|
|
2
|
-
import './types';
|
|
3
|
-
import './model-processor';
|
|
4
|
-
import './bg-bridge';
|
|
5
|
-
import './run-on-background';
|
|
6
|
-
export { render, lynxMount } from './render';
|
|
7
|
-
export { nodeOps } from './nodeOps';
|
|
8
|
-
export type { LynxNode, LynxElement } from './nodeOps';
|
|
9
|
-
export { ShadowElement, createPageRoot, resetShadowState } from './shadow-element';
|
|
10
|
-
export { pushOp, takeOps, scheduleFlush, flushNow, resetOpQueue } from './op-queue';
|
|
1
|
+
import './jsx.js';
|
|
2
|
+
import './types.js';
|
|
3
|
+
import './model-processor.js';
|
|
4
|
+
import './bg-bridge.js';
|
|
5
|
+
import './run-on-background.js';
|
|
6
|
+
export { render, lynxMount } from './render.js';
|
|
7
|
+
export { nodeOps } from './nodeOps.js';
|
|
8
|
+
export type { LynxNode, LynxElement } from './nodeOps.js';
|
|
9
|
+
export { ShadowElement, createPageRoot, resetShadowState } from './shadow-element.js';
|
|
10
|
+
export { pushOp, takeOps, scheduleFlush, flushNow, resetOpQueue } from './op-queue.js';
|
|
11
11
|
export { OP } from '@sigx/lynx-runtime-internal';
|
|
12
12
|
export type { OpCode, MapperParams, RangeParams, BuiltinMapperName, AnimatedStyleMapper, } from '@sigx/lynx-runtime-internal';
|
|
13
|
-
export { register, updateHandler, unregister, getHandler, publishEvent, resetRegistry, } from './event-registry';
|
|
14
|
-
export { MainThreadRef, useMainThreadRef, resetWvidCounter, } from './main-thread-ref';
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export
|
|
21
|
-
export {
|
|
22
|
-
export {
|
|
23
|
-
export
|
|
24
|
-
export
|
|
13
|
+
export { register, updateHandler, unregister, getHandler, publishEvent, resetRegistry, } from './event-registry.js';
|
|
14
|
+
export { MainThreadRef, useMainThreadRef, resetWvidCounter, } from './main-thread-ref.js';
|
|
15
|
+
export { useElementLayout } from './use-element-layout.js';
|
|
16
|
+
export type { ElementLayout, LayoutChangeEvent, UseElementLayoutResult, } from './use-element-layout.js';
|
|
17
|
+
export { registerBgSink, unregisterBgSink, ingestAvPublishes, resetBgAvBridge, bgAvSinkCount, } from './animated-bridge.js';
|
|
18
|
+
export { useSharedValue, SharedValue, } from './animated/shared-value.js';
|
|
19
|
+
export type { SharedValueState } from './animated/shared-value.js';
|
|
20
|
+
export { useAnimatedStyle, resetAnimatedStyleBindingIds, } from './animated/use-animated-style.js';
|
|
21
|
+
export { useAnimatedValue, AnimatedValue, } from './animated/animated-value.js';
|
|
22
|
+
export type { AnimatedValueState } from './animated/animated-value.js';
|
|
23
|
+
export { runOnMainThread, runOnBackground, resetThreading, transformToWorklet, resetRunOnBackgroundState, } from './threading.js';
|
|
24
|
+
export { Gesture, GestureType, useGestureDetector, resetGestureIdCounter, } from './native/index.js';
|
|
25
|
+
export type { GestureTypeValue, GestureWorklet, GestureCallback, BaseGesture, ComposedGesture, AnyGesture, } from './native/index.js';
|
|
26
|
+
export type { LynxEventHandler, LynxCommonAttributes, ViewAttributes, TextAttributes, ImageAttributes, ScrollViewAttributes, ListAttributes, ListItemAttributes, InputAttributes, TextAreaAttributes, PageAttributes, SvgAttributes, FilterImageAttributes, MainThread, } from './jsx.js';
|