feat: make event polling opt-in with configurable rate#23
Merged
Conversation
There was a problem hiding this comment.
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
elementaryEventbridge 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.
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
3a59f79 to
6a8af12
Compare
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Breaking Change
Event polling is no longer started automatically. Consumers that need
el.snapshot/el.meter/el.scopeevents must explicitly callstartEventPolling().Why
On React Native,
el.snapshot/el.meter/el.scopeevents are produced by the native C++ audio runtime and must cross the RN bridge to reach JavaScript. This requires a polling mechanism (AndroidHandler, iOSdispatch_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
Values are clamped to 10–1000ms.
Changes
initon both Android (ElementaryModule.kt) and iOS (Elementary.mm)startEventPolling()as@ReactMethod— opt-in event pollingstopEventPolling()as@ReactMethod— halt polling, release Handler/sourceconfigureEventPolling(intervalMs)as@ReactMethod— set poll rate (10-1000ms, default 33ms)index.tsxandNativeElementary.tsspecMigration
Apps currently relying on automatic event polling need to add one call:
Apps that don't use
el.snapshot,el.meter, orel.scope(e.g., playhead driven byperformance.now()) can skip polling entirely for zero bridge overhead.