Stop map pins swapping and truncating each other's images - #81
Merged
Conversation
react-native-view-shot's Android module compresses every snapshot through a single static byte buffer and dispatches captures on an unbounded thread pool, with no synchronisation anywhere between the two. We started a capture for every distinct pin emoji in one animation frame, so they raced through that one array: the data URI that came back for a pin held another pin's PNG bytes with its own spliced through the tail. On the map that decoded as the wrong emoji, ending in a hard horizontal edge partway down where the bytes stopped making sense — a bed showing a tree with no teardrop tip, which reads as a pin that is also too big and off its spot. Capture through a serial queue instead: at most one snapshot in flight, so no two ever share the buffer. Pins now rasterise one per frame, and until one lands its element keeps the plain teardrop that already stands in for an unrasterised icon. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Summary
A selected element rendered with another element's emoji, cut off by a hard horizontal edge partway down — no teardrop tip, so it also read as too large and off its coordinate. Neither the data nor MapLibre was at fault.
react-native-view-shot's Android module compresses every snapshot through a single staticbyte[](ViewShot.java:142) and dispatches captures onExecutors.newCachedThreadPool(), with no synchronisation between the two. It then base64-encodes that static field using its own capture's length:usePinImagesstarted a capture for every distinct pin emoji in onerequestAnimationFrametick, so they raced through that one array. The URI returned for one pin held another's PNG bytes with its own spliced through the tail, which decodes to the wrong emoji ending where the bytes stopped making sense.Measured against the reported screenshot: the marker's size and position were correct to within a few pixels of where the element's coordinates put it — only the bottom 40% of the image was missing.
Changes
usePinImagescaptures through a serial queue: at most one snapshot in flight, so no two share the buffer.null, so the queue can tell "unmounted" from "not yet attached" and re-attempt a pin that comes back.__tests__/usePinImages.test.tsx: asserts captures never overlap (fails on the previous implementation — 4 concurrent captures), and that each pin is registered under the name of the icon it actually drew.Trade-off
Pins rasterise one per frame, so a trip with ~20 distinct emoji takes ~20 frames to fully populate. Until a pin's own image lands its element shows the plain teardrop — the fallback that was already designed in.
Upstream
The library is unsafe for any concurrent
captureRefon Android, not just this use. Worth reporting separately; this change only fixes our side.Checks
bunx tsc --noEmitclean ·bun run lintclean ·bun run test -- --ci59 passed / 9 suites