Skip to content

feat: make event polling opt-in with configurable rate#23

Merged
txbrown merged 4 commits into
mainfrom
feat/configurable-event-polling
May 25, 2026
Merged

feat: make event polling opt-in with configurable rate#23
txbrown merged 4 commits into
mainfrom
feat/configurable-event-polling

Conversation

@txbrown
Copy link
Copy Markdown
Collaborator

@txbrown txbrown commented May 22, 2026

Breaking Change

Event polling is no longer started automatically. Consumers that need el.snapshot / el.meter / el.scope events must explicitly call startEventPolling().

Why

On React Native, el.snapshot/el.meter/el.scope events are produced by the native C++ audio runtime and must cross the RN bridge to reach JavaScript. This requires a polling mechanism (Android Handler, iOS dispatch_source_t) that wakes up every 33ms, drains the event queue, and sends events across the bridge.

Previously, this polling started automatically in init — even for apps that never read snapshot/meter data. On low-end Android devices, each bridge crossing costs 1-5ms, so 30Hz polling with multiple events per tick consumed 12%+ of the JS thread even when no app code was listening to the events.

On web, none of this overhead exists — callbacks fire directly in the same JS thread with zero marshalling.

New APIs

import { startEventPolling, stopEventPolling, configureEventPolling } from "react-native-elementary";

// Start polling at default ~30Hz (33ms)
await startEventPolling();

// Or configure a different rate before starting:
await configureEventPolling(100); // 100ms ≈ 10Hz (drift correction only)
await startEventPolling();

// Stop polling when you don't need events anymore:
await stopEventPolling();
Interval Rate Use case
33ms ~30Hz Smooth metering, playhead UI
100ms ~10Hz Drift correction only, minimal overhead

Values are clamped to 10–1000ms.

Changes

  • Remove auto-start from init on both Android (ElementaryModule.kt) and iOS (Elementary.mm)
  • Expose startEventPolling() as @ReactMethod — opt-in event polling
  • Expose stopEventPolling() as @ReactMethod — halt polling, release Handler/source
  • Expose configureEventPolling(intervalMs) as @ReactMethod — set poll rate (10-1000ms, default 33ms)
  • Batch events into single bridge call per poll cycle (O(1) instead of O(N) per tick)
  • JS exports in index.tsx and NativeElementary.ts spec

Migration

Apps currently relying on automatic event polling need to add one call:

// Before init mount or in useEffect:
await startEventPolling();

Apps that don't use el.snapshot, el.meter, or el.scope (e.g., playhead driven by performance.now()) can skip polling entirely for zero bridge overhead.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes Elementary runtime event polling opt-in (instead of auto-starting during native init) and adds APIs to start/stop polling and configure the polling interval to reduce React Native bridge overhead for apps that don’t consume el.snapshot / el.meter / el.scope / el.fft events.

Changes:

  • Add JS/TurboModule APIs: startEventPolling(), stopEventPolling(), configureEventPolling(intervalMs).
  • Remove native auto-start of event polling on iOS and Android, requiring explicit opt-in.
  • Batch multiple runtime events into a single elementaryEvent bridge emission per poll tick.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/NativeElementary.ts Extends TurboModule spec with event polling control methods.
src/index.tsx Exposes new public JS API wrappers for polling control.
README.md Documents opt-in polling and configurable poll rates.
ios/Elementary.mm Removes auto-start, adds polling interval support, batches events, exports polling control methods.
ios/Elementary.h Adds polling interval property and updates polling timer documentation.
android/src/main/java/com/elementary/ElementaryModule.kt Removes auto-start, adds polling interval support, batches events, exports polling control methods.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ios/Elementary.mm
Comment thread ios/Elementary.mm Outdated
Comment thread android/src/main/java/com/elementary/ElementaryModule.kt Outdated
Comment thread src/index.tsx Outdated
Comment thread README.md Outdated
txbrown added 2 commits May 24, 2026 22:41
BREAKING CHANGE: Event polling is no longer started automatically.
Consumers that need el.snapshot / el.meter / el.scope events must
explicitly call startEventPolling().

New APIs:
- startEventPolling() — opt in to event polling
- stopEventPolling() — halt polling and release Handler/source
- configureEventPolling(intervalMs) — set poll rate (10-1000ms, default 33ms)

Changes:
- Remove auto-startEventPolling() from init (both Android + iOS)
- Expose startEventPolling/stopEventPolling/configureEventPolling as React methods
- Batch events into single bridge call per poll cycle (O(1) instead of O(N))
- Configurable poll interval (default 33ms ≈ 30Hz, use 100ms for drift-only)
- iOS: configurable dispatch_source interval via eventPollIntervalMs property

This eliminates unnecessary JS thread overhead for apps that only use
setProperty for real-time updates and don't need periodic snapshot/meter
data. On RN, events must cross the native↔JS bridge (unlike web where
callbacks fire in the same thread), so polling should be opt-in.

On web, no polling is needed — el.snapshot/el.meter fire JS callbacks
directly in the same thread with zero overhead.
- Add #ifdef RCT_NEW_ARCH_ENABLED implementations for startEventPolling,
  stopEventPolling, configureEventPolling with codegen signatures (reject:
  instead of rejecter:), matching the existing method pattern
- Revert event batching that changed elementaryEvent payload shape from
  individual {type, ...} objects to {events: [...]} wrapper, restoring
  backward compatibility while keeping opt-in polling value
- Fix platform-specific 'Handler' wording to platform-neutral
  'native timer/resources' in JS docstrings
- Document elementaryEvent payload shape and add NativeEventEmitter
  subscription example in README
@txbrown txbrown force-pushed the feat/configurable-event-polling branch from 3a59f79 to 6a8af12 Compare May 24, 2026 21:41
txbrown added 2 commits May 24, 2026 23:16
The TurboModule Spec had activateAudioSession, deactivateAudioSession,
configureAudioSession, and disableAudioSessionManagement defined twice
(from a merge conflict leftover). Also fixed configureAudioSession options
type from any[] to string[] for consistency.

Duplicate interface methods confuse the TurboModule codegen and cause
'undefined is not a function' errors at runtime.
- Add startEventPolling, stopEventPolling, configureEventPolling overrides
  to ElementaryTurboModule.java (fixes Android new arch build)
- Fix Prettier formatting for configureAudioSession params in NativeElementary.ts
@txbrown txbrown merged commit dce6f36 into main May 25, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants