Skip to content

fix(nif): double-buffer the tap-handle registry (Android + iOS)#45

Merged
GenericJam merged 2 commits into
masterfrom
feat/tap-handle-double-buffer
Jun 20, 2026
Merged

fix(nif): double-buffer the tap-handle registry (Android + iOS)#45
GenericJam merged 2 commits into
masterfrom
feat/tap-handle-double-buffer

Conversation

@GenericJam

Copy link
Copy Markdown
Owner

Problem

clear_taps (called every render) reset the tap-handle count to 0 and then
re-registered every handler in tree order. A high-frequency event — a finger
drag or scroll dispatched from the UI thread during a render — could
observe a transiently-small count and a half-rebuilt table, and get dropped.
The hazard grew with registration order: a widget registered late in the tree
(e.g. a Canvas after a row of Buttons) was the most likely to be missed.

This surfaced while wiring finger-drawing on a real device: drags onto a canvas
intermittently vanished depending on what else was on screen.

Fix

Double-buffer the tap-handle registry. register_tap builds into the inactive
table while readers keep resolving the last committed (active) table;
set_root swaps them atomically under tap_mutex. A concurrent event now always
resolves against a complete table on either side of the swap — never a torn,
mid-rebuild one.

  • Android (mob_nif.zig): tap_tables[2] + tap_active / tap_active_count
    / tap_build_count; snapTap / throttleCheck / set_throttle_config read
    the active table; register_tap / clear_taps write the building table;
    set_root swaps under the mutex.
  • iOS (mob_nif.m): tap_handles / tap_handle_next become a pointer+count
    to the active table (readers unchanged); register/clear use the building table;
    the pointer is repointed at set_root.

No API change — purely an internal correctness fix to event delivery.

Verification

  • Compiles clean on both platforms (mob_nif exports unchanged: 95 on iOS).
  • Verified on-device (moto g, Android, mob 0.7.3 via path-dep + explicit
    mix deps.compile mob --force): a finger-drag canvas routed 74 drag samples
    through the swapped table and rendered the stroke correctly — drags resolve
    through the active table across renders with no drops.

Docs (so users can track this)

  • CHANGELOG.md [Unreleased] → Fixed.
  • guides/troubleshooting.md: a path-dep mob_nif:log undef-at-boot entry —
    recompile the path-dep as its own step before deploy (and match the
    elixir_lib toolchain). Encountered while verifying this change on-device.

🤖 Generated with Claude Code

GenericJam and others added 2 commits June 20, 2026 00:48
clear_taps reset the handle count to 0 then re-registered every handler in
tree order, so a high-frequency event (drag/scroll) firing from the UI thread
*during* a render saw a transiently-small count and a half-rebuilt table, and
got dropped — worse the later a widget registered (e.g. a canvas after a row
of buttons).

Double-buffer the registry: register_tap builds into the INACTIVE table while
readers keep resolving the last committed (ACTIVE) table; set_root swaps them
atomically under tap_mutex. A concurrent send now always sees a complete table
on either side of the swap.

- Android (mob_nif.zig): tap_tables[2] + tap_active/tap_active_count/
  tap_build_count; snapTap/throttleCheck/set_throttle_config read the active
  table.
- iOS (mob_nif.m): tap_handles/tap_handle_next become a pointer+count to the
  active table (readers unchanged); register/clear use the building table; the
  pointer is repointed at set_root.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CHANGELOG [Unreleased]: the double-buffer fix (no API change), so users
  can track it landing in the next release.
- troubleshooting.md: path-dep mob 'mob_nif:log undef' at boot → recompile
  the path-dep as its own step before deploy (and match the elixir_lib
  toolchain). Saves the next person the same multi-hour misdiagnosis.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@GenericJam GenericJam merged commit 37dd02d into master Jun 20, 2026
4 checks passed
@GenericJam GenericJam deleted the feat/tap-handle-double-buffer branch June 20, 2026 18:20
GenericJam added a commit that referenced this pull request Jun 20, 2026
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant