react-native-image-stitcher 0.16.2 → 0.18.0

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/RNImageStitcher.podspec +26 -1
  3. package/android/build.gradle +20 -0
  4. package/android/src/main/cpp/CMakeLists.txt +46 -3
  5. package/android/src/main/cpp/stitcher_jsi_install_jni.cpp +436 -0
  6. package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +6 -0
  7. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +711 -6
  8. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +156 -0
  9. package/android/src/main/java/io/imagestitcher/rn/StitcherJsiInstallerModule.kt +103 -0
  10. package/android/src/main/java/io/imagestitcher/rn/StitcherWorkletRuntime.kt +338 -0
  11. package/cpp/{stitcher_frame_data.hpp → camera_frame_data.hpp} +96 -13
  12. package/cpp/camera_frame_jsi.cpp +357 -0
  13. package/cpp/camera_frame_jsi.hpp +108 -0
  14. package/cpp/stitcher_proxy_jsi.cpp +140 -0
  15. package/cpp/stitcher_proxy_jsi.hpp +62 -0
  16. package/cpp/stitcher_worklet_dispatch.cpp +103 -0
  17. package/cpp/stitcher_worklet_dispatch.hpp +71 -0
  18. package/cpp/stitcher_worklet_registry.cpp +91 -0
  19. package/cpp/stitcher_worklet_registry.hpp +146 -0
  20. package/dist/camera/ARCameraView.d.ts +77 -0
  21. package/dist/camera/ARCameraView.js +90 -1
  22. package/dist/camera/Camera.d.ts +63 -4
  23. package/dist/camera/Camera.js +2 -2
  24. package/dist/camera/CaptureMemoryPill.d.ts +4 -3
  25. package/dist/camera/CaptureMemoryPill.js +4 -3
  26. package/dist/index.d.ts +2 -1
  27. package/dist/stitching/ARFrameMeta.d.ts +100 -0
  28. package/dist/stitching/{StitcherFrame.js → ARFrameMeta.js} +1 -1
  29. package/dist/stitching/{StitcherFrame.d.ts → CameraFrame.d.ts} +70 -11
  30. package/dist/stitching/CameraFrame.js +4 -0
  31. package/dist/stitching/ensureStitcherProxyInstalled.d.ts +8 -0
  32. package/dist/stitching/ensureStitcherProxyInstalled.js +81 -0
  33. package/dist/stitching/useStitcherWorklet.d.ts +4 -4
  34. package/dist/stitching/useStitcherWorklet.js +4 -4
  35. package/ios/Sources/RNImageStitcher/ARSessionBridge.m +23 -1
  36. package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +137 -2
  37. package/ios/Sources/RNImageStitcher/CameraFrameHostObject.h +83 -0
  38. package/ios/Sources/RNImageStitcher/CameraFrameHostObject.mm +760 -0
  39. package/ios/Sources/RNImageStitcher/RNSARSession.swift +336 -40
  40. package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.h +128 -0
  41. package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.mm +313 -0
  42. package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.h +42 -0
  43. package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.mm +160 -0
  44. package/package.json +1 -1
  45. package/src/camera/ARCameraView.tsx +211 -2
  46. package/src/camera/Camera.tsx +81 -4
  47. package/src/camera/CaptureMemoryPill.tsx +4 -3
  48. package/src/index.ts +7 -3
  49. package/src/stitching/ARFrameMeta.ts +107 -0
  50. package/src/stitching/{StitcherFrame.ts → CameraFrame.ts} +79 -11
  51. package/src/stitching/ensureStitcherProxyInstalled.ts +141 -0
  52. package/src/stitching/useStitcherWorklet.ts +9 -9
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ensureStitcherProxyInstalled = ensureStitcherProxyInstalled;
5
+ exports._resetStitcherProxyInstallStateForTests = _resetStitcherProxyInstallStateForTests;
6
+ const react_native_1 = require("react-native");
7
+ /**
8
+ * `__DEV__` is RN's global dev-flag. Guard the read with `typeof`
9
+ * so the helper works in any environment that imports it without
10
+ * defining __DEV__ (jest, SSR, custom tooling). Same pattern RN's
11
+ * own debug code uses.
12
+ */
13
+ function isDev() {
14
+ return typeof __DEV__ !== 'undefined' && __DEV__;
15
+ }
16
+ let installed = false;
17
+ function ensureStitcherProxyInstalled() {
18
+ if (installed)
19
+ return true;
20
+ // Already installed by an earlier hook mount. Cheap fast-path.
21
+ if (typeof globalThis.__stitcherProxy !== 'undefined') {
22
+ installed = true;
23
+ return true;
24
+ }
25
+ const mod = react_native_1.NativeModules
26
+ .StitcherJsiInstaller;
27
+ if (mod == null || typeof mod.install !== 'function') {
28
+ // Module not present — Android until Phase 4b.ii lands, or
29
+ // an old iOS build. Surface this once at debug-info level so
30
+ // the host can see "your worklets are JS-registered only" in
31
+ // logcat / Console.app without a noisy per-frame warning.
32
+ if (isDev() && !warnedAboutMissingModule) {
33
+ warnedAboutMissingModule = true;
34
+ console.info('[react-native-image-stitcher] StitcherJsiInstaller native ' +
35
+ 'module not found; host worklets registered in JS-side ' +
36
+ 'registry only. AR-mode dispatch requires the native install ' +
37
+ '(iOS Phase 4b.i — included in v0.8.0; Android Phase 4b.ii ' +
38
+ '— follow-up release).');
39
+ }
40
+ return false;
41
+ }
42
+ try {
43
+ const ok = mod.install();
44
+ if (!ok) {
45
+ // Native module ran but couldn't install (JSI runtime
46
+ // unreachable). Same fallback as the missing-module case.
47
+ if (isDev() && !warnedAboutFailedInstall) {
48
+ warnedAboutFailedInstall = true;
49
+ console.info('[react-native-image-stitcher] StitcherJsiInstaller.install() ' +
50
+ 'returned false (JSI runtime unreachable — remote debug ' +
51
+ 'mode?). Falling back to JS-side host worklet registry.');
52
+ }
53
+ return false;
54
+ }
55
+ installed = true;
56
+ return true;
57
+ }
58
+ catch (err) {
59
+ if (isDev() && !warnedAboutFailedInstall) {
60
+ warnedAboutFailedInstall = true;
61
+ console.info('[react-native-image-stitcher] StitcherJsiInstaller.install() ' +
62
+ 'threw: ' +
63
+ String(err) +
64
+ '. Falling back to JS-side host worklet registry.');
65
+ }
66
+ return false;
67
+ }
68
+ }
69
+ let warnedAboutMissingModule = false;
70
+ let warnedAboutFailedInstall = false;
71
+ /**
72
+ * Test-only — reset module-internal state. Used by jest to allow
73
+ * multiple test cases to re-trigger the install path independently.
74
+ * NOT exported from `src/index.ts`.
75
+ */
76
+ function _resetStitcherProxyInstallStateForTests() {
77
+ installed = false;
78
+ warnedAboutMissingModule = false;
79
+ warnedAboutFailedInstall = false;
80
+ }
81
+ //# sourceMappingURL=ensureStitcherProxyInstalled.js.map
@@ -106,17 +106,17 @@
106
106
  * their own worklet via this hook must do the wiring themselves.
107
107
  */
108
108
  import type { Frame } from 'react-native-vision-camera';
109
- import type { StitcherFrame } from './StitcherFrame';
109
+ import type { CameraFrame } from './CameraFrame';
110
110
  /**
111
111
  * Frames the lib's stitching worklet accepts. Accepting either a
112
112
  * vc `Frame` (what the host's `useFrameProcessor` body sees) or the
113
- * lib's `StitcherFrame` (what the lib's `useFrameProcessor` body
113
+ * lib's `CameraFrame` (what the lib's `useFrameProcessor` body
114
114
  * sees) keeps the same `useStitcherWorklet` usable from both kinds
115
115
  * of host worklet bodies without a cast on the call site. The
116
116
  * worklet only reads `width` / `height`; the rest of the frame
117
117
  * object is forwarded verbatim to the native plugin.
118
118
  */
119
- export type StitcherWorkletInput = Frame | StitcherFrame;
119
+ export type StitcherWorkletInput = Frame | CameraFrame;
120
120
  export interface UseStitcherWorkletOptions {
121
121
  /**
122
122
  * Gyro sample interval in ms (~30 Hz default). Drives the JS-
@@ -145,7 +145,7 @@ export interface UseStitcherWorkletOptions {
145
145
  }
146
146
  export interface StitcherWorkletHandle {
147
147
  /**
148
- * Worklet function: pass a `StitcherFrame` to perform one frame of
148
+ * Worklet function: pass a `CameraFrame` to perform one frame of
149
149
  * the lib's first-party stitching (throttle + pose synthesis +
150
150
  * native plugin call). Safe to call from inside another
151
151
  * `'worklet'`-prefixed function (this is the canonical
@@ -228,7 +228,7 @@ function useStitcherWorklet(options = {}) {
228
228
  // party callback installed in `RNSARWorkletRuntime`). Calling
229
229
  // the vc Frame Processor plugin here would throw
230
230
  // `getPropertyAsObject: property '__frame' is undefined`
231
- // because AR frames are `StitcherFrameHostObject` instances
231
+ // because AR frames are `CameraFrameHostObject` instances
232
232
  // and don't carry the vc `Frame` proxy's JSI marker. The
233
233
  // throw is caught silently by the per-worklet error handler
234
234
  // (`RNSARWorkletRuntime.mm:284-301`) and bubbles up only to
@@ -240,12 +240,12 @@ function useStitcherWorklet(options = {}) {
240
240
  // hook (the AR-side stitching path runs natively, independent
241
241
  // of the composed worklet body).
242
242
  //
243
- // The `(frame as StitcherFrame).source` cast is safe: vc
243
+ // The `(frame as CameraFrame).source` cast is safe: vc
244
244
  // `Frame` doesn't carry a `source` property so the check
245
245
  // returns `undefined !== 'ar'` → `true`, and the worklet
246
246
  // proceeds normally. Only frames that explicitly tag
247
247
  // themselves as AR-source (which our native AR dispatcher
248
- // does — see `StitcherFrameHostObject.mm`) get short-circuited.
248
+ // does — see `CameraFrameHostObject.mm`) get short-circuited.
249
249
  if (frame.source === 'ar')
250
250
  return;
251
251
  // Throttle (verbatim from useFrameProcessorDriver).
@@ -273,7 +273,7 @@ function useStitcherWorklet(options = {}) {
273
273
  const fx = w * sharedFxNumerator.value;
274
274
  const fy = h * sharedFyNumerator.value;
275
275
  // vc's `plugin.call` is typed against vc's `Frame`. The worklet
276
- // accepts the union (`Frame | StitcherFrame`); cast through
276
+ // accepts the union (`Frame | CameraFrame`); cast through
277
277
  // `unknown` because the union doesn't satisfy vc's interface
278
278
  // even though structurally both members do.
279
279
  plugin.call(frame, {
@@ -5,6 +5,7 @@
5
5
  // imports as `NativeModules.RNSARSession`.
6
6
 
7
7
  #import <React/RCTBridgeModule.h>
8
+ #import <React/RCTEventEmitter.h>
8
9
 
9
10
  // REMAP form, NOT EXTERN_MODULE. The Swift singleton in
10
11
  // RNSARSession.swift takes the @objc name "RNSARSession"
@@ -20,7 +21,14 @@
20
21
  // `RNSARSessionBridge` and dispatch methods against THAT
21
22
  // class — where takePhoto / startRecording / stopRecording etc.
22
23
  // actually live.
23
- @interface RCT_EXTERN_REMAP_MODULE(RNSARSession, RNSARSessionBridge, NSObject)
24
+ //
25
+ // v0.18.0 — base class is now `RCTEventEmitter` (was `NSObject`) so the
26
+ // "RNSARSession" module can emit the `RNImageStitcherARFrame` device
27
+ // event for the `onArFrame` channel. RN auto-provides the
28
+ // `addListener:` / `removeListeners:` emitter selectors for a module
29
+ // whose remap base is RCTEventEmitter; the Swift class supplies
30
+ // `supportedEvents` / `startObserving` / `stopObserving`.
31
+ @interface RCT_EXTERN_REMAP_MODULE(RNSARSession, RNSARSessionBridge, RCTEventEmitter)
24
32
 
25
33
  RCT_EXTERN_METHOD(isSupported:(RCTPromiseResolveBlock)resolver
26
34
  rejecter:(RCTPromiseRejectBlock)rejecter)
@@ -34,6 +42,20 @@ RCT_EXTERN_METHOD(stop:(RCTPromiseResolveBlock)resolver
34
42
  RCT_EXTERN_METHOD(getState:(RCTPromiseResolveBlock)resolver
35
43
  rejecter:(RCTPromiseRejectBlock)rejecter)
36
44
 
45
+ RCT_EXTERN_METHOD(setSceneReconstructionEnabled:(nonnull NSNumber *)enabled
46
+ resolver:(RCTPromiseResolveBlock)resolver
47
+ rejecter:(RCTPromiseRejectBlock)rejecter)
48
+
49
+ RCT_EXTERN_METHOD(setPlaneDetection:(nonnull NSString *)mode
50
+ resolver:(RCTPromiseResolveBlock)resolver
51
+ rejecter:(RCTPromiseRejectBlock)rejecter)
52
+
53
+ // v0.18.0 — toggle the onArFrame LIGHT-metadata channel + its throttle.
54
+ RCT_EXTERN_METHOD(setArFrameMetaEnabled:(nonnull NSNumber *)enabled
55
+ intervalMs:(nonnull NSNumber *)intervalMs
56
+ resolver:(RCTPromiseResolveBlock)resolver
57
+ rejecter:(RCTPromiseRejectBlock)rejecter)
58
+
37
59
  RCT_EXTERN_METHOD(snapshotPoseLog:(RCTPromiseResolveBlock)resolver
38
60
  rejecter:(RCTPromiseRejectBlock)rejecter)
39
61
 
@@ -17,15 +17,109 @@
17
17
  import Foundation
18
18
  import React
19
19
 
20
+ // v0.18.0 — `RNSARSessionBridge` is now an `RCTEventEmitter` (was a
21
+ // plain `NSObject`) so it can deliver the `onArFrame` LIGHT-metadata
22
+ // channel as the JS `RNImageStitcherARFrame` device event. The JS side
23
+ // subscribes via `new NativeEventEmitter(NativeModules.RNSARSession)` —
24
+ // the same module name this bridge is remapped to (see ARSessionBridge.m).
25
+ //
26
+ // Pattern mirrors `IncrementalStitcherBridge`: observe a NotificationCenter
27
+ // post from the framework-free `RNSARSession` engine, then re-emit on the
28
+ // main queue via `bridge.enqueueJSCall("RCTDeviceEventEmitter", "emit", …)`
29
+ // rather than `RCTEventEmitter.sendEvent(…)`, because under RN bridgeless
30
+ // interop `sendEvent` silently no-ops for some event-body shapes (see the
31
+ // IncrementalStitcherBridge.handleStateUpdate docstring).
20
32
  @objc(RNSARSessionBridge)
21
- public final class RNSARSessionBridge: NSObject {
33
+ public final class RNSARSessionBridge: RCTEventEmitter {
34
+
35
+ /// Whether at least one JS listener is attached to the AR-frame event.
36
+ /// RN's EventEmitter contract: don't emit when no listeners are
37
+ /// registered. Toggled by `startObserving` / `stopObserving`.
38
+ private var hasListeners: Bool = false
39
+
40
+ private static let arFrameEvent = "RNImageStitcherARFrame"
41
+
42
+ public override init() {
43
+ super.init()
44
+ // Defensively de-dupe the observer: under RN bridgeless interop a
45
+ // bridge's init() can run twice on the same instance. Remove any
46
+ // prior registration for this notification before adding, so the
47
+ // observer fires at most once per post regardless.
48
+ NotificationCenter.default.removeObserver(
49
+ self,
50
+ name: .retailensARFrameMeta,
51
+ object: nil
52
+ )
53
+ NotificationCenter.default.addObserver(
54
+ self,
55
+ selector: #selector(handleArFrameMeta(_:)),
56
+ name: .retailensARFrameMeta,
57
+ object: nil
58
+ )
59
+ }
22
60
 
23
- @objc public static func requiresMainQueueSetup() -> Bool {
61
+ deinit {
62
+ NotificationCenter.default.removeObserver(self)
63
+ }
64
+
65
+ // MARK: - RCTEventEmitter protocol
66
+
67
+ public override static func requiresMainQueueSetup() -> Bool {
24
68
  // ARSession.start() must be called on the main thread —
25
69
  // ARKit needs to attach to the active CVDisplayLink.
26
70
  return true
27
71
  }
28
72
 
73
+ public override func supportedEvents() -> [String]! {
74
+ return [Self.arFrameEvent]
75
+ }
76
+
77
+ public override func startObserving() {
78
+ hasListeners = true
79
+ }
80
+
81
+ public override func stopObserving() {
82
+ hasListeners = false
83
+ }
84
+
85
+ /// Forward a posted `ARFrameMeta` dictionary to JS as the
86
+ /// `RNImageStitcherARFrame` device event. Dropped when no JS listener
87
+ /// is attached. Emits via `enqueueJSCall` on the main queue (see the
88
+ /// class docstring for why not `sendEvent`).
89
+ @objc private func handleArFrameMeta(_ notification: Notification) {
90
+ guard hasListeners else { return }
91
+ guard let userInfo = notification.userInfo else { return }
92
+ DispatchQueue.main.async { [weak self] in
93
+ guard let self = self, let bridge = self.bridge else { return }
94
+ bridge.enqueueJSCall(
95
+ "RCTDeviceEventEmitter",
96
+ method: "emit",
97
+ args: [Self.arFrameEvent, userInfo],
98
+ completion: nil
99
+ )
100
+ }
101
+ }
102
+
103
+ // MARK: - Module methods
104
+
105
+ /// v0.18.0 — toggle the `onArFrame` LIGHT-metadata channel. Called
106
+ /// from JS with `true` + the throttle interval (ms) when a host
107
+ /// supplies `<Camera onArFrame={...}>`, and `false` on
108
+ /// unmount / prop-removal. Resolves with no value.
109
+ @objc(setArFrameMetaEnabled:intervalMs:resolver:rejecter:)
110
+ public func setArFrameMetaEnabled(
111
+ enabled: NSNumber,
112
+ intervalMs: NSNumber,
113
+ resolver: @escaping RCTPromiseResolveBlock,
114
+ rejecter: @escaping RCTPromiseRejectBlock
115
+ ) {
116
+ RNSARSession.shared.setArFrameMetaEnabled(
117
+ enabled.boolValue,
118
+ intervalMs: intervalMs.doubleValue
119
+ )
120
+ resolver(nil)
121
+ }
122
+
29
123
  @objc(isSupported:rejecter:)
30
124
  public func isSupported(
31
125
  resolver: @escaping RCTPromiseResolveBlock,
@@ -68,6 +162,47 @@ public final class RNSARSessionBridge: NSObject {
68
162
  ])
69
163
  }
70
164
 
165
+ /// Toggle ARKit scene reconstruction (LiDAR mesh / `ARMeshAnchor`s).
166
+ /// Driven by the <Camera> `enableMesh` prop; gates the
167
+ /// StitcherFrame `meshGeometry` extraction at the SESSION level
168
+ /// (the per-frame `__stitcherProxy.setExtractionConfig(...mesh)`
169
+ /// gates the marshaling — both must be on for a host to receive
170
+ /// mesh). Resolves with no value.
171
+ ///
172
+ /// Hops to the main queue: `setSceneReconstructionEnabled` may call
173
+ /// `arSession.run(config)` to reconfigure a live session, and
174
+ /// ARKit session lifecycle must run on the main thread (same
175
+ /// constraint as `start`).
176
+ @objc(setSceneReconstructionEnabled:resolver:rejecter:)
177
+ public func setSceneReconstructionEnabled(
178
+ enabled: NSNumber,
179
+ resolver: @escaping RCTPromiseResolveBlock,
180
+ rejecter: @escaping RCTPromiseRejectBlock
181
+ ) {
182
+ let on = enabled.boolValue
183
+ DispatchQueue.main.async {
184
+ RNSARSession.shared.setSceneReconstructionEnabled(on)
185
+ resolver(nil)
186
+ }
187
+ }
188
+
189
+ /// Hops to the main queue: `setPlaneDetection` may call
190
+ /// `arSession.run(config)` to reconfigure a live session, and ARKit
191
+ /// session lifecycle must run on the main thread (same constraint as
192
+ /// `start` / `setSceneReconstructionEnabled`).
193
+ @objc(setPlaneDetection:resolver:rejecter:)
194
+ public func setPlaneDetection(
195
+ mode: NSString,
196
+ resolver: @escaping RCTPromiseResolveBlock,
197
+ rejecter: @escaping RCTPromiseRejectBlock
198
+ ) {
199
+ let m = mode as String
200
+ DispatchQueue.main.async {
201
+ RNSARSession.shared.setPlaneDetection(m)
202
+ resolver(nil)
203
+ }
204
+ }
205
+
71
206
  @objc(snapshotPoseLog:rejecter:)
72
207
  public func snapshotPoseLog(
73
208
  resolver: @escaping RCTPromiseResolveBlock,
@@ -0,0 +1,83 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // CameraFrameHostObject.h — Obj-C facade for the v0.8.0
4
+ // `StitcherFrame` JSI host object. Header is intentionally
5
+ // Obj-C-only (no `<jsi/jsi.h>` import) so this can land in the
6
+ // public CocoaPods umbrella without breaking `use_frameworks!` hosts
7
+ // (same rationale as `KeyframeGateBridge.h`).
8
+ //
9
+ // The C++ JSI host object class lives in the .mm; this facade
10
+ // exposes only what cross-module callers need:
11
+ //
12
+ // - Factory `+ fromARFrame:pose:` that the AR worklet runtime
13
+ // calls per ARFrame to construct a host object backed by the
14
+ // current AR session's frame.
15
+ // - Opaque accessor `- (void *)jsiHostObjectPtr` returning the
16
+ // `std::shared_ptr<facebook::jsi::HostObject> *` (boxed) that
17
+ // the worklet runtime hands to `jsi::Object::createFromHostObject`.
18
+ //
19
+ // Lifetime: the Obj-C wrapper holds the C++ shared_ptr; ARC frees
20
+ // the wrapper when nothing references it. Worklet runtime
21
+ // invalidates the underlying ARFrame retain when the dispatch
22
+ // returns; after invalidation, JSI access throws.
23
+
24
+ #pragma once
25
+
26
+ #import <Foundation/Foundation.h>
27
+ #import <ARKit/ARKit.h>
28
+
29
+ @class RNSARFramePose;
30
+
31
+ NS_ASSUME_NONNULL_BEGIN
32
+
33
+ NS_SWIFT_NAME(CameraFrameHostObject)
34
+ @interface CameraFrameHostObject : NSObject
35
+
36
+ /// Construct a host object backed by the supplied ARFrame + pose.
37
+ /// Retains the ARFrame for the host object's lifetime — caller can
38
+ /// safely release their reference.
39
+ ///
40
+ /// Thread: safe to call from the ARSession delegate queue; the
41
+ /// resulting host object's JSI access must happen on the worklet
42
+ /// runtime's thread (separate queue).
43
+ + (instancetype)fromARFrame:(ARFrame *)arFrame pose:(RNSARFramePose *)pose;
44
+
45
+ /// Mark the host object's underlying ARFrame as no longer accessible.
46
+ /// Subsequent JSI property reads return `undefined` or throw,
47
+ /// depending on the property. Idempotent.
48
+ - (void)invalidate;
49
+
50
+ /// Opaque pointer to a `std::shared_ptr<facebook::jsi::HostObject>`.
51
+ /// The worklet runtime (Obj-C++ context with JSI available) casts
52
+ /// this back via `*reinterpret_cast<std::shared_ptr<facebook::jsi::HostObject>*>(ptr)`
53
+ /// to hand to `jsi::Object::createFromHostObject`.
54
+ ///
55
+ /// Returns `NULL` if the host object has been invalidated.
56
+ - (nullable void *)jsiHostObjectPtr;
57
+
58
+ /// Build the LIGHT per-frame AR metadata dictionary for the `onArFrame`
59
+ /// callback (the `ARFrameMeta` TS shape). Distinct from the full
60
+ /// host-object factory above: this copies NO pixel / vertex / face bytes
61
+ /// — only scalars, dimensions, anchor transforms, and mesh COUNTS — so
62
+ /// it's cheap enough to run at the throttled `onArFrame` cadence.
63
+ ///
64
+ /// Gating mirrors the full extraction path: `depth` only when the JS
65
+ /// `enableDepth` flag is on (read from the shared C++ extraction config),
66
+ /// `anchors` only when `enableAnchors`, `mesh` (counts) only when
67
+ /// `enableMesh`. `intrinsics` / `pose` / `trackingState` / `timestamp`
68
+ /// are always populated. `intrinsics` is `NSNull` only when the frame
69
+ /// reported a degenerate (zero) resolution.
70
+ ///
71
+ /// Returns a JSON-safe `NSDictionary` (NSNumber / NSString / NSArray /
72
+ /// NSDictionary / NSNull leaves) ready to hand to
73
+ /// `bridge.enqueueJSCall("RCTDeviceEventEmitter", "emit", ...)`.
74
+ ///
75
+ /// Thread: safe to call from the ARSession delegate queue (reads the
76
+ /// frame synchronously; copies nothing that outlives the call).
77
+ + (NSDictionary *)lightArFrameMetaFromARFrame:(ARFrame *)arFrame
78
+ pose:(RNSARFramePose *)pose
79
+ NS_SWIFT_NAME(lightArFrameMeta(from:pose:));
80
+
81
+ @end
82
+
83
+ NS_ASSUME_NONNULL_END