@sigx/lynx-runtime-main 0.2.7 → 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-bridge-mt.js +228 -0
- package/dist/animated-style-mappers.js +154 -0
- package/dist/element-registry.js +16 -0
- package/dist/entry-main.js +229 -1
- package/dist/event-slots.js +111 -0
- package/dist/hybrid-worklet.js +73 -0
- package/dist/index.js +22 -63
- package/dist/install-hybrid-worklet.js +19 -6
- package/dist/mt-element.js +203 -0
- package/dist/ops-apply.js +449 -0
- package/dist/run-on-background-mt.js +91 -0
- package/dist/upstream/observers.js +39 -0
- package/dist/upstream/processGesture.js +147 -0
- package/dist/worklet-events.js +47 -0
- package/dist/worklet-refs.js +43 -0
- package/package.json +8 -8
- package/dist/entry-main-CBM2DHsU.js +0 -615
- package/dist/entry-main-CBM2DHsU.js.map +0 -1
- package/dist/hybrid-worklet-nTcCh-mA.js +0 -58
- package/dist/hybrid-worklet-nTcCh-mA.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/install-hybrid-worklet.js.map +0 -1
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Thread ops executor.
|
|
3
|
+
*
|
|
4
|
+
* Receives the flat-array ops buffer sent by the Background Thread via
|
|
5
|
+
* callLepusMethod('sigxPatchUpdate', { data: JSON.stringify(ops) }) and applies
|
|
6
|
+
* each operation using Lynx PAPI.
|
|
7
|
+
*/
|
|
8
|
+
import { OP } from '@sigx/lynx-runtime-internal';
|
|
9
|
+
import { processGesture } from './upstream/processGesture.js';
|
|
10
|
+
import { elements, pageUniqueId, setPageUniqueId, } from './element-registry.js';
|
|
11
|
+
import { resetWorkletEvents } from './worklet-events.js';
|
|
12
|
+
import { setSlotBgSign, setSlotWorklet, flushDirtySlots, resetSlotStates, } from './event-slots.js';
|
|
13
|
+
import { flushAvBridgePublishes, flushAnimatedStyleBindings, registerAnimatedStyleBinding, unregisterAnimatedStyleBinding, resetAnimatedStyleBindings, } from './animated-bridge-mt.js';
|
|
14
|
+
/**
|
|
15
|
+
* Placeholder element inserted by renderPage() to give the host a non-empty
|
|
16
|
+
* tree immediately, suppressing the "loadCard failed USER_RUNTIME_ERROR"
|
|
17
|
+
* timeout. Removed on the first applyOps() call.
|
|
18
|
+
*/
|
|
19
|
+
let placeholderParent = null;
|
|
20
|
+
let placeholderEl = null;
|
|
21
|
+
/**
|
|
22
|
+
* SharedValue bridge state — registered wvids and last-published snapshots.
|
|
23
|
+
* The op handlers (`OP.REGISTER_AV_BRIDGE` / `OP.UNREGISTER_AV_BRIDGE` below)
|
|
24
|
+
* mutate these collections; `animated-bridge-mt.ts:flushAvBridgePublishes`
|
|
25
|
+
* reads them on every flush boundary to compute the diff to publish to BG.
|
|
26
|
+
*/
|
|
27
|
+
export const bridgedAvWvids = new Set();
|
|
28
|
+
export const bridgedAvLastValues = new Map();
|
|
29
|
+
/**
|
|
30
|
+
* Gesture-detector tracking — per-element wvid → set of attached gesture ids.
|
|
31
|
+
* Used to drive the `has-react-gesture` setup attribute lifecycle: set on the
|
|
32
|
+
* first SET_GESTURE_DETECTOR for an element, cleared when the last gesture is
|
|
33
|
+
* removed via REMOVE_GESTURE_DETECTOR.
|
|
34
|
+
*/
|
|
35
|
+
const gesturesByElementWvid = new Map();
|
|
36
|
+
/**
|
|
37
|
+
* Last-set BaseGesture tree per element wvid. Vendored upstream
|
|
38
|
+
* `processGesture` takes an `oldGesture` arg to diff against — pass the prior
|
|
39
|
+
* tree so callback updates wire correctly through `onWorkletCtxUpdate`.
|
|
40
|
+
*/
|
|
41
|
+
const lastTreeByElementWvid = new Map();
|
|
42
|
+
/**
|
|
43
|
+
* elementWvid → elementId mapping populated by SET_MT_REF when a
|
|
44
|
+
* `main-thread:ref` binds to an element. Gesture ops carry the elRef's wvid
|
|
45
|
+
* (set at BG-side useGestureDetector time, before the renderer assigns an
|
|
46
|
+
* element id), so resolution is wvid → elementId → raw MainThreadElement.
|
|
47
|
+
*
|
|
48
|
+
* We deliberately do NOT use `lynxWorkletImpl._refImpl._workletRefMap[wvid].current`:
|
|
49
|
+
* that map stores the upstream-wrapped `Element` class (with `setStyleProperties`
|
|
50
|
+
* etc.) which is what worklets need, but the platform's `__SetAttribute` /
|
|
51
|
+
* `__SetGestureDetector` PAPI expect the raw RefCounted element handle.
|
|
52
|
+
* Passing the wrapper trips the `FiberSetAttribute param 0 should be RefCounted`
|
|
53
|
+
* native error.
|
|
54
|
+
*/
|
|
55
|
+
const elementIdByWvid = new Map();
|
|
56
|
+
function resolveElementByWvid(wvid) {
|
|
57
|
+
const elementId = elementIdByWvid.get(wvid);
|
|
58
|
+
if (elementId === undefined)
|
|
59
|
+
return undefined;
|
|
60
|
+
return elements.get(elementId);
|
|
61
|
+
}
|
|
62
|
+
export function setPlaceholder(parent, el) {
|
|
63
|
+
placeholderParent = parent;
|
|
64
|
+
placeholderEl = el;
|
|
65
|
+
}
|
|
66
|
+
function removePlaceholderOnce() {
|
|
67
|
+
if (placeholderEl != null && placeholderParent != null) {
|
|
68
|
+
__RemoveElement(placeholderParent, placeholderEl);
|
|
69
|
+
placeholderParent = null;
|
|
70
|
+
placeholderEl = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Use typed PAPI creators for known element types.
|
|
75
|
+
* Native Lynx may set up type-specific internals (e.g. overflow clipping
|
|
76
|
+
* for View, hardware-accelerated decoding for Image) via the typed functions
|
|
77
|
+
* that the generic __CreateElement does not.
|
|
78
|
+
*/
|
|
79
|
+
function createTypedElement(type, parentComponentUniqueId) {
|
|
80
|
+
switch (type) {
|
|
81
|
+
case 'view':
|
|
82
|
+
return __CreateView(parentComponentUniqueId);
|
|
83
|
+
case 'text':
|
|
84
|
+
return __CreateText(parentComponentUniqueId);
|
|
85
|
+
case 'image':
|
|
86
|
+
return __CreateImage(parentComponentUniqueId);
|
|
87
|
+
case 'scroll-view':
|
|
88
|
+
return __CreateScrollView(parentComponentUniqueId);
|
|
89
|
+
case 'page':
|
|
90
|
+
// The page root is special — it's created once by __CreatePage() in
|
|
91
|
+
// renderPage() and aliased to ShadowElement id=1 in BG. Lynx hosts
|
|
92
|
+
// (e.g. Lynx Go) have no behavior class for a second `page` element
|
|
93
|
+
// and will throw "No BehaviorController defined for class page".
|
|
94
|
+
// If user code wraps content in <page>...</page>, treat it as a
|
|
95
|
+
// transparent <view> on the Main Thread so the tree stays valid.
|
|
96
|
+
return __CreateView(parentComponentUniqueId);
|
|
97
|
+
default:
|
|
98
|
+
return __CreateElement(type, parentComponentUniqueId);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export function applyOps(ops) {
|
|
102
|
+
const len = ops.length;
|
|
103
|
+
if (len === 0)
|
|
104
|
+
return;
|
|
105
|
+
// On the first real ops batch, remove the placeholder element that
|
|
106
|
+
// renderPage() inserted to suppress the host's USER_RUNTIME_ERROR timeout.
|
|
107
|
+
removePlaceholderOnce();
|
|
108
|
+
// Detect duplicate batch from double BG bundle evaluation.
|
|
109
|
+
// Each __init_card_bundle__ invocation gets a fresh webpack module cache, so
|
|
110
|
+
// ShadowElement.nextId resets to 2, producing the same element IDs.
|
|
111
|
+
// If the first CREATE op targets an ID that already exists in our elements Map,
|
|
112
|
+
// this is a duplicate batch — skip it entirely.
|
|
113
|
+
if (len >= 3 && ops[0] === OP.CREATE) {
|
|
114
|
+
const firstId = ops[1];
|
|
115
|
+
if (elements.has(firstId)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
let i = 0;
|
|
120
|
+
while (i < len) {
|
|
121
|
+
const code = ops[i++];
|
|
122
|
+
switch (code) {
|
|
123
|
+
case OP.CREATE: {
|
|
124
|
+
const id = ops[i++];
|
|
125
|
+
const type = ops[i++];
|
|
126
|
+
let el;
|
|
127
|
+
if (type === '__comment') {
|
|
128
|
+
el = __CreateRawText('');
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
el = createTypedElement(type, pageUniqueId);
|
|
132
|
+
__SetCSSId([el], 0);
|
|
133
|
+
}
|
|
134
|
+
elements.set(id, el);
|
|
135
|
+
if (type !== '__comment') {
|
|
136
|
+
__SetAttribute(el, `sigx-ref-${id}`, 1);
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case OP.CREATE_TEXT: {
|
|
141
|
+
const id = ops[i++];
|
|
142
|
+
const el = __CreateText(pageUniqueId);
|
|
143
|
+
__SetCSSId([el], 0);
|
|
144
|
+
elements.set(id, el);
|
|
145
|
+
__SetAttribute(el, `sigx-ref-${id}`, 1);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case OP.INSERT: {
|
|
149
|
+
const parentId = ops[i++];
|
|
150
|
+
const childId = ops[i++];
|
|
151
|
+
const anchorId = ops[i++];
|
|
152
|
+
const parent = elements.get(parentId);
|
|
153
|
+
const child = elements.get(childId);
|
|
154
|
+
if (parent && child) {
|
|
155
|
+
if (anchorId === -1) {
|
|
156
|
+
__AppendElement(parent, child);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
const anchor = elements.get(anchorId);
|
|
160
|
+
if (anchor)
|
|
161
|
+
__InsertElementBefore(parent, child, anchor);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case OP.REMOVE: {
|
|
167
|
+
const _parentId = ops[i++];
|
|
168
|
+
const childId = ops[i++];
|
|
169
|
+
const parent = elements.get(_parentId);
|
|
170
|
+
const child = elements.get(childId);
|
|
171
|
+
if (parent && child) {
|
|
172
|
+
__RemoveElement(parent, child);
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case OP.SET_PROP: {
|
|
177
|
+
const id = ops[i++];
|
|
178
|
+
const key = ops[i++];
|
|
179
|
+
const value = ops[i++];
|
|
180
|
+
const el = elements.get(id);
|
|
181
|
+
if (el)
|
|
182
|
+
__SetAttribute(el, key, value);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case OP.SET_TEXT: {
|
|
186
|
+
const id = ops[i++];
|
|
187
|
+
const text = ops[i++];
|
|
188
|
+
const el = elements.get(id);
|
|
189
|
+
if (el)
|
|
190
|
+
__SetAttribute(el, 'text', text);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case OP.SET_EVENT: {
|
|
194
|
+
const id = ops[i++];
|
|
195
|
+
const eventType = ops[i++];
|
|
196
|
+
const eventName = ops[i++];
|
|
197
|
+
const sign = ops[i++];
|
|
198
|
+
// Defer __AddEvent to flushDirtySlots (end of batch). When a worklet
|
|
199
|
+
// is also registered for the same slot, the slot machine combines
|
|
200
|
+
// them into a single hybrid registration.
|
|
201
|
+
setSlotBgSign(id, eventType, eventName, sign);
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case OP.REMOVE_EVENT: {
|
|
205
|
+
const id = ops[i++];
|
|
206
|
+
const eventType = ops[i++];
|
|
207
|
+
const eventName = ops[i++];
|
|
208
|
+
// Clear the BG side of this slot. The MT worklet (if any) survives.
|
|
209
|
+
// No worklet-removal op exists yet (see plan Open items).
|
|
210
|
+
setSlotBgSign(id, eventType, eventName, undefined);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case OP.SET_STYLE: {
|
|
214
|
+
const id = ops[i++];
|
|
215
|
+
const value = ops[i++];
|
|
216
|
+
const el = elements.get(id);
|
|
217
|
+
if (el)
|
|
218
|
+
__SetInlineStyles(el, value);
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case OP.SET_CLASS: {
|
|
222
|
+
const id = ops[i++];
|
|
223
|
+
const cls = ops[i++];
|
|
224
|
+
const el = elements.get(id);
|
|
225
|
+
if (el)
|
|
226
|
+
__SetClasses(el, cls);
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
case OP.SET_ID: {
|
|
230
|
+
const id = ops[i++];
|
|
231
|
+
const idStr = ops[i++];
|
|
232
|
+
const el = elements.get(id);
|
|
233
|
+
if (el)
|
|
234
|
+
__SetID(el, idStr ?? undefined);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
case OP.SET_WORKLET_EVENT: {
|
|
238
|
+
const id = ops[i++];
|
|
239
|
+
const eventType = ops[i++];
|
|
240
|
+
const eventName = ops[i++];
|
|
241
|
+
const ctx = ops[i++];
|
|
242
|
+
if (ctx && ctx._wkltId) {
|
|
243
|
+
ctx['_workletType'] = 'main-thread';
|
|
244
|
+
// Defer __AddEvent — flushDirtySlots will pick the right shape:
|
|
245
|
+
// worklet-only ({type:'worklet', value: ctx}) when no BG handler
|
|
246
|
+
// shares this slot, or hybrid ctx when one does.
|
|
247
|
+
setSlotWorklet(id, eventType, eventName, ctx);
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
case OP.SET_MT_REF: {
|
|
252
|
+
const id = ops[i++];
|
|
253
|
+
const wvid = ops[i++];
|
|
254
|
+
const el = elements.get(id);
|
|
255
|
+
if (el) {
|
|
256
|
+
// Delegate to upstream's worklet-runtime. updateWorkletRef wraps the
|
|
257
|
+
// element in its own Element class and stores it under _wvid.
|
|
258
|
+
const impl = globalThis['lynxWorkletImpl'];
|
|
259
|
+
if (impl?._refImpl) {
|
|
260
|
+
const refMap = impl._refImpl._workletRefMap;
|
|
261
|
+
if (!(wvid in refMap)) {
|
|
262
|
+
refMap[wvid] = { current: null, _wvid: wvid };
|
|
263
|
+
}
|
|
264
|
+
impl._refImpl.updateWorkletRef({ _wvid: wvid }, el);
|
|
265
|
+
}
|
|
266
|
+
// Record wvid → raw elementId so SET_GESTURE_DETECTOR can resolve
|
|
267
|
+
// the unwrapped MainThreadElement for `__SetAttribute` /
|
|
268
|
+
// `__SetGestureDetector` (which require RefCounted handles, not
|
|
269
|
+
// upstream's Element wrapper).
|
|
270
|
+
elementIdByWvid.set(wvid, id);
|
|
271
|
+
}
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
case OP.INIT_MT_REF: {
|
|
275
|
+
const wvid = ops[i++];
|
|
276
|
+
const initValue = ops[i++];
|
|
277
|
+
const impl = globalThis['lynxWorkletImpl'];
|
|
278
|
+
if (impl?._refImpl) {
|
|
279
|
+
const refMap = impl._refImpl._workletRefMap;
|
|
280
|
+
if (!(wvid in refMap)) {
|
|
281
|
+
refMap[wvid] = { current: initValue, _wvid: wvid };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
case OP.RELEASE_MT_REF: {
|
|
287
|
+
// Owning component unmounted on BG; drop the MT-side holder so the
|
|
288
|
+
// worklet ref map doesn't grow unbounded across navigation. Mirrors
|
|
289
|
+
// upstream's WorkletEvents.releaseWorkletRef path (we don't dispatch
|
|
290
|
+
// upstream's event because we manage the map ourselves via ops).
|
|
291
|
+
const wvid = ops[i++];
|
|
292
|
+
const impl = globalThis['lynxWorkletImpl'];
|
|
293
|
+
if (impl?._refImpl) {
|
|
294
|
+
delete impl._refImpl._workletRefMap[wvid];
|
|
295
|
+
}
|
|
296
|
+
elementIdByWvid.delete(wvid);
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
case OP.REGISTER_AV_BRIDGE: {
|
|
300
|
+
const wvid = ops[i++];
|
|
301
|
+
const initValue = ops[i++];
|
|
302
|
+
bridgedAvWvids.add(wvid);
|
|
303
|
+
bridgedAvLastValues.set(wvid, initValue);
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
case OP.UNREGISTER_AV_BRIDGE: {
|
|
307
|
+
const wvid = ops[i++];
|
|
308
|
+
bridgedAvWvids.delete(wvid);
|
|
309
|
+
bridgedAvLastValues.delete(wvid);
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
case OP.REGISTER_AV_STYLE_BINDING: {
|
|
313
|
+
const bindingId = ops[i++];
|
|
314
|
+
const elementWvid = ops[i++];
|
|
315
|
+
const avWvid = ops[i++];
|
|
316
|
+
const mapperName = ops[i++];
|
|
317
|
+
const params = ops[i++];
|
|
318
|
+
registerAnimatedStyleBinding(bindingId, elementWvid, avWvid, mapperName, params);
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
case OP.UNREGISTER_AV_STYLE_BINDING: {
|
|
322
|
+
const bindingId = ops[i++];
|
|
323
|
+
unregisterAnimatedStyleBinding(bindingId);
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
case OP.SET_GESTURE_DETECTOR: {
|
|
327
|
+
// Wire format: [op, wvid, gestureId, type, config, relationMap].
|
|
328
|
+
// We reconstruct upstream's BaseGesture shape and delegate to vendored
|
|
329
|
+
// `processGesture` so the platform-call sequence is byte-for-byte
|
|
330
|
+
// identical to `@lynx-js/react`'s snapshot pipeline. Per-base wire
|
|
331
|
+
// means we register one base per op; processGesture handles the
|
|
332
|
+
// single-base fast path. Composed gestures arrive as multiple ops,
|
|
333
|
+
// each carrying its relationMap.
|
|
334
|
+
const elementWvid = ops[i++];
|
|
335
|
+
const gestureId = ops[i++];
|
|
336
|
+
const type = ops[i++];
|
|
337
|
+
const config = ops[i++];
|
|
338
|
+
const relationMap = ops[i++];
|
|
339
|
+
const el = resolveElementByWvid(elementWvid);
|
|
340
|
+
if (!el)
|
|
341
|
+
break;
|
|
342
|
+
// Reconstruct callbacks Record from the wire's array shape.
|
|
343
|
+
const callbacksRecord = {};
|
|
344
|
+
for (const cb of config.callbacks) {
|
|
345
|
+
callbacksRecord[cb.name] = cb.callback;
|
|
346
|
+
}
|
|
347
|
+
// Build a fake BaseGesture: relation arrays are id-stubs `[{id}]`
|
|
348
|
+
// because vendored `getGestureInfo` reads `.id` off each entry to
|
|
349
|
+
// produce the relationMap. The platform never sees these objects.
|
|
350
|
+
const stub = (ids) => ids.map((id) => ({ id }));
|
|
351
|
+
const fakeBaseGesture = {
|
|
352
|
+
__isSerialized: true,
|
|
353
|
+
type,
|
|
354
|
+
id: gestureId,
|
|
355
|
+
callbacks: callbacksRecord,
|
|
356
|
+
waitFor: stub(relationMap.waitFor),
|
|
357
|
+
simultaneousWith: stub(relationMap.simultaneous),
|
|
358
|
+
continueWith: stub(relationMap.continueWith),
|
|
359
|
+
...(config.config ? { config: config.config } : {}),
|
|
360
|
+
};
|
|
361
|
+
// Phase 2.12.1 bug fix: pass `undefined` as oldGesture, NOT the last
|
|
362
|
+
// tree we saw on this element.
|
|
363
|
+
//
|
|
364
|
+
// Our wire format is one SET_GESTURE_DETECTOR op per BaseGesture.
|
|
365
|
+
// When `<Pressable>` registers `Simultaneous(Tap, LongPress)`, two
|
|
366
|
+
// ops arrive in sequence on the same element. If we pass the previous
|
|
367
|
+
// tree to `processGesture`, its diff path treats the second op as
|
|
368
|
+
// "Tap → LongPress" and emits a `__RemoveGestureDetector` for Tap
|
|
369
|
+
// before installing LongPress. Result: only the last gesture stays
|
|
370
|
+
// registered; all earlier gestures are silently uninstalled.
|
|
371
|
+
//
|
|
372
|
+
// The right model for our wire is additive: each op installs ONE
|
|
373
|
+
// gesture without disturbing siblings. Removal is explicit via the
|
|
374
|
+
// REMOVE_GESTURE_DETECTOR op (emitted from `useGestureDetector`'s
|
|
375
|
+
// unmount cleanup), which calls `__RemoveGestureDetector` directly.
|
|
376
|
+
// Note: this means we don't get diff-based callback updates if the
|
|
377
|
+
// BG side re-emits a gesture with the same id — but our wire never
|
|
378
|
+
// does that today; on prop changes BG emits REMOVE then SET.
|
|
379
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
380
|
+
processGesture(el, fakeBaseGesture, undefined, false);
|
|
381
|
+
lastTreeByElementWvid.set(elementWvid, fakeBaseGesture);
|
|
382
|
+
// Track for REMOVE op cleanup and to drive `has-react-gesture` toggle
|
|
383
|
+
// when the last gesture goes (vendored function clears it on remove).
|
|
384
|
+
let attached = gesturesByElementWvid.get(elementWvid);
|
|
385
|
+
if (!attached) {
|
|
386
|
+
attached = new Set();
|
|
387
|
+
gesturesByElementWvid.set(elementWvid, attached);
|
|
388
|
+
}
|
|
389
|
+
attached.add(gestureId);
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
case OP.REMOVE_GESTURE_DETECTOR: {
|
|
393
|
+
const elementWvid = ops[i++];
|
|
394
|
+
const gestureId = ops[i++];
|
|
395
|
+
const el = resolveElementByWvid(elementWvid);
|
|
396
|
+
if (el && typeof __RemoveGestureDetector === 'function') {
|
|
397
|
+
__RemoveGestureDetector(el, gestureId);
|
|
398
|
+
const attached = gesturesByElementWvid.get(elementWvid);
|
|
399
|
+
if (attached) {
|
|
400
|
+
attached.delete(gestureId);
|
|
401
|
+
if (attached.size === 0) {
|
|
402
|
+
gesturesByElementWvid.delete(elementWvid);
|
|
403
|
+
lastTreeByElementWvid.delete(elementWvid);
|
|
404
|
+
__SetAttribute(el, 'has-react-gesture', null);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
default:
|
|
411
|
+
// Unknown op – skip (future-compat)
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// Commit deferred __AddEvent registrations now that the entire batch is
|
|
416
|
+
// processed — this is what lets worklet + BG handler on the same slot
|
|
417
|
+
// coexist via the hybrid worklet ctx, without one overwriting the other.
|
|
418
|
+
flushDirtySlots();
|
|
419
|
+
// Diff registered SharedValues against their last-published snapshots
|
|
420
|
+
// and dispatch a batched Lynx.Sigx.AvPublish event with anything that
|
|
421
|
+
// changed during this op batch. See animated-bridge-mt.ts for details.
|
|
422
|
+
flushAvBridgePublishes();
|
|
423
|
+
// Apply any useAnimatedStyle bindings whose source SharedValue changed
|
|
424
|
+
// during this batch. Runs after flushAvBridgePublishes so the BG mirror
|
|
425
|
+
// stays consistent with the styles we're about to commit.
|
|
426
|
+
flushAnimatedStyleBindings();
|
|
427
|
+
// Flush all pending PAPI changes to the native layer in one shot.
|
|
428
|
+
__FlushElementTree();
|
|
429
|
+
}
|
|
430
|
+
/** Reset module state — for testing and hot reload. */
|
|
431
|
+
export function resetMainThreadState() {
|
|
432
|
+
elements.clear();
|
|
433
|
+
setPageUniqueId(1);
|
|
434
|
+
placeholderParent = null;
|
|
435
|
+
placeholderEl = null;
|
|
436
|
+
// Also defined in this module's imports — reset worklet state
|
|
437
|
+
resetWorkletEvents();
|
|
438
|
+
resetSlotStates();
|
|
439
|
+
bridgedAvWvids.clear();
|
|
440
|
+
bridgedAvLastValues.clear();
|
|
441
|
+
gesturesByElementWvid.clear();
|
|
442
|
+
lastTreeByElementWvid.clear();
|
|
443
|
+
elementIdByWvid.clear();
|
|
444
|
+
resetAnimatedStyleBindings();
|
|
445
|
+
// Clear upstream's worklet ref map too on hard reset (HMR / test).
|
|
446
|
+
const impl = globalThis['lynxWorkletImpl'];
|
|
447
|
+
if (impl?._refImpl)
|
|
448
|
+
impl._refImpl._workletRefMap = {};
|
|
449
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MT-side runOnBackground — dispatches function calls from the Main Thread
|
|
3
|
+
* to the Background Thread via 'Lynx.Sigx.RunOnBackground' events.
|
|
4
|
+
*
|
|
5
|
+
* Called inside extracted worklet bodies on the Main Thread. The SWC LEPUS
|
|
6
|
+
* pass leaves bare `runOnBackground(_jsFnK)` references in the registered
|
|
7
|
+
* worklet body; we install this implementation as `globalThis.runOnBackground`
|
|
8
|
+
* from `entry-main.ts` so the bare identifier resolves at runtime.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors @lynx-js/react/runtime/lib/worklet/call/runOnMainThread (the dual
|
|
11
|
+
* direction) and vue-lynx's run-on-background-mt.ts. Sigx-namespaced event
|
|
12
|
+
* types so we don't conflict with upstream's own bridge if it ships in the
|
|
13
|
+
* same lynx process.
|
|
14
|
+
*/
|
|
15
|
+
const RUN_ON_BACKGROUND = 'Lynx.Sigx.RunOnBackground';
|
|
16
|
+
const FUNCTION_CALL_RET = 'Lynx.Sigx.FunctionCallRet';
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Return-value resolver — correlates resolveId → Promise resolve callback
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
let resolveMap;
|
|
21
|
+
let nextResolveId = 1;
|
|
22
|
+
function getJSContext() {
|
|
23
|
+
// On MT, `lynx` is a globalThis property (no closure injection like BG).
|
|
24
|
+
const lynxObj = globalThis.lynx;
|
|
25
|
+
return lynxObj?.getJSContext?.();
|
|
26
|
+
}
|
|
27
|
+
function initReturnListener() {
|
|
28
|
+
resolveMap = new Map();
|
|
29
|
+
getJSContext()?.addEventListener?.(FUNCTION_CALL_RET, (event) => {
|
|
30
|
+
let payload;
|
|
31
|
+
try {
|
|
32
|
+
payload = JSON.parse(event.data);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const resolve = resolveMap?.get(payload.resolveId);
|
|
38
|
+
if (resolve) {
|
|
39
|
+
resolveMap.delete(payload.resolveId);
|
|
40
|
+
resolve(payload.returnValue);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// dispatch — ship the call across to BG
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
function dispatch(fnId, params, execId, resolveId) {
|
|
48
|
+
getJSContext()?.dispatchEvent?.({
|
|
49
|
+
type: RUN_ON_BACKGROUND,
|
|
50
|
+
data: JSON.stringify({
|
|
51
|
+
obj: { _jsFnId: fnId, _execId: execId },
|
|
52
|
+
params,
|
|
53
|
+
resolveId,
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// runOnBackground — the global function called in extracted LEPUS code
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
export function runOnBackground(handle) {
|
|
61
|
+
return (...params) => {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
if (!resolveMap)
|
|
64
|
+
initReturnListener();
|
|
65
|
+
const resolveId = nextResolveId++;
|
|
66
|
+
resolveMap.set(resolveId, resolve);
|
|
67
|
+
if (handle._isFirstScreen
|
|
68
|
+
&& typeof lynxWorkletImpl !== 'undefined'
|
|
69
|
+
&& lynxWorkletImpl?._runOnBackgroundDelayImpl) {
|
|
70
|
+
lynxWorkletImpl._runOnBackgroundDelayImpl.delayRunOnBackground(handle, (fnId, execId) => dispatch(fnId, params, execId, resolveId));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (handle._jsFnId == null || handle._execId == null) {
|
|
74
|
+
// Handle never carried a (fnId, execId) pair — most likely the BG
|
|
75
|
+
// sender did not call registerWorkletCtx. Resolve undefined so the
|
|
76
|
+
// worklet promise settles instead of leaking.
|
|
77
|
+
resolveMap.delete(resolveId);
|
|
78
|
+
resolve(undefined);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
dispatch(handle._jsFnId, params, handle._execId, resolveId);
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Reset — for testing only
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
export function resetRunOnBackgroundMtState() {
|
|
89
|
+
resolveMap = undefined;
|
|
90
|
+
nextResolveId = 1;
|
|
91
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Copyright 2025 The Lynx Authors. All rights reserved.
|
|
2
|
+
// TypeScript types added 2026 by SignalX contributors.
|
|
3
|
+
//
|
|
4
|
+
// Licensed under the Apache License, Version 2.0. The full license text and
|
|
5
|
+
// upstream attribution are reproduced in `THIRD_PARTY_NOTICES.md` at the
|
|
6
|
+
// root of this package (`@sigx/lynx-runtime-main`). The MIT LICENSE at the
|
|
7
|
+
// repository root governs the rest of this repository; it does NOT apply
|
|
8
|
+
// to this file.
|
|
9
|
+
//
|
|
10
|
+
// Vendored from `@lynx-js/react@0.120.0`'s
|
|
11
|
+
// `runtime/lib/worklet-runtime/bindings/observers.js`. Source preserved
|
|
12
|
+
// verbatim; only types added. We vendor this 15-LOC helper to drop the
|
|
13
|
+
// `@lynx-js/react` runtime dependency from `@sigx/lynx-runtime-main`, which
|
|
14
|
+
// in turn keeps `@types/react` out of the install tree of consumer apps
|
|
15
|
+
// (sigx-lynx apps use sigx primitives, not ReactLynx JSX).
|
|
16
|
+
function impl() {
|
|
17
|
+
return globalThis.lynxWorkletImpl;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Must be called when a worklet context is updated. Mirrors the upstream
|
|
21
|
+
* ReactLynx behaviour:
|
|
22
|
+
* 1. Register the new worklet with the JS-side lifecycle manager (so the
|
|
23
|
+
* background thread can free it).
|
|
24
|
+
* 2. On first screen with a previous ctx, hydrate the new ctx from the old.
|
|
25
|
+
* 3. On first screen, flush any worklets that were delayed waiting for this
|
|
26
|
+
* element (legacy dynamic-component compat path).
|
|
27
|
+
*/
|
|
28
|
+
export function onWorkletCtxUpdate(worklet, oldWorklet, isFirstScreen, element) {
|
|
29
|
+
const w = impl();
|
|
30
|
+
if (worklet._execId !== undefined) {
|
|
31
|
+
w?._jsFunctionLifecycleManager?.addRef(worklet._execId, worklet);
|
|
32
|
+
}
|
|
33
|
+
if (isFirstScreen && oldWorklet) {
|
|
34
|
+
w?._hydrateCtx(worklet, oldWorklet);
|
|
35
|
+
}
|
|
36
|
+
if (isFirstScreen) {
|
|
37
|
+
w?._eventDelayImpl.runDelayedWorklet(worklet, element);
|
|
38
|
+
}
|
|
39
|
+
}
|