Skip to content

Match the React Native throughput path with PHP - #3

Open
shanerbaner82 wants to merge 4 commits into
margelo:nativephp-zxingfrom
shanerbaner82:nativephp-native-reporting
Open

shanerbaner82 wants to merge 4 commits into
margelo:nativephp-zxingfrom
shanerbaner82:nativephp-native-reporting

Conversation

@shanerbaner82

@shanerbaner82 shanerbaner82 commented Sep 16, 2026

Copy link
Copy Markdown

The original version moved the report loop into Kotlin, forced a smaller/faster camera stream, and started its clock on the first accepted scan. That did not measure the same work as the React Native throughput screen.

This revision makes the Android benchmark path equivalent:

  • opens the live camera before the timed run
  • starts from the on-camera Start 1000 QRs button
  • has PHP await POST /reset, then start the monotonic clock
  • sends every decoded frame into PHP, where PHP deduplicates values, performs POST /scan, checks the server's ok response, and increments the count
  • keeps the native overlay display-only; PHP owns the authoritative count and elapsed result
  • requests a 16:9 1280x720 YUV analysis stream and no longer forces 480p or 60 FPS
  • uses the same zxing-cpp 3.1.1 source revision (287c85df6f961c8efbfb5ffd736cd9457b8b890e) and the same live decoding ladder: plain scan every frame, rotation-aware fallback every fourth unsuccessful frame

Same-device Galaxy A15 results with the same QR display and positioning:

Implementation 1,000 accepted codes
React Native 136.8s
NativePHP / PHP loop 138.7s

That is a 1.9-second difference (about 1.4%) in this run. React Native was built as release while the NativePHP measurement was a debug build, so the result should be read as practical parity from this paired run rather than a broad framework performance claim.

Validation:

  • 6 PHPUnit tests passing, including reset/timer ordering, PHP dedupe, server acceptance, and the exact 1,000-scan stop
  • NativePHP plugin validation passing
  • Android debug build compiled, installed, and launched on the Galaxy A15

shanerbaner82 and others added 3 commits September 16, 2026 12:01
Every decode used to leave the phone through PHP: native event -> WebView
fetch -> a full Laravel request -> Guzzle POST on a fresh connection ->
a bridge call back into native to confirm. That is a PHP boot and a TCP
handshake per code, and it is what the benchmark ends up measuring.

`ZxingScanner::start($target, $reportUrl)` now hands the scanner the
server's /scan URL. Android and iOS POST {"value": code} there themselves
on a single persistent connection (opened before the first code) and count
a scan as soon as the reply says ok. PHP runs once to start the run and
once when RunCompleted arrives. The CodeScanned + confirm() path is still
there for apps that want to decide what counts, so RelayScan is gone from
the app.

Android talks to the socket directly: the NativePHP scaffold's
network-security config blocks cleartext for HttpURLConnection/OkHttp, a
plugin manifest cannot relax it, and the benchmark server is plain http
on the LAN.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Android: pin the AE target fps range to the camera's fastest fixed rate
(60 where advertised, else 30) so a new code on the screen is captured
sooner and delivered more often, and decode a 640x480 stream instead of
720p. That is plenty of pixels per module for a phone-screen QR at arm's
length, zxing-cpp decodes it in a few ms, and a budget SoC can deliver it
at the full camera rate (a 1440x1080 stream ran at 11 fps on a Galaxy A15).

iOS: the .hd1280x720 preset settles on a 30 fps format on most iPhones.
Pick the 720p format that can do 60, cap exposure at 1/250 s so a frame is
not integrating for a whole 1/30 s, and restrict autofocus to near.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The elapsed time began when the camera opened, so warm-up and aiming at
the screen were part of every run. It now starts when the first scan is
accepted, which is the loop the benchmark is about.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mrousavy

Copy link
Copy Markdown
Member

Your PR one-sidedly improves NativePHP to "cheat" being better than React Native in the following ways;

  • It literally doesn't go through PHP anymore but instead keeps the entire scanning and network send in native Kotlin. This literally removes NativePHP out of the mix, lmao.
  • You changed NativePHP's camera config from 720p to 480p (smaller Frames are faster to scan) and 30 FPS to 60 FPS (double the frame rate), while leaving the React Native implementation at 720p and 30 FPS. Completely one-sided to cheat the benchmark. We can also configure React Native to 480p 60 FPS with two lines of code which would make it fair, but yours just made it unfair.
  • You start the timer later (only on first scan) rather than earlier, while keeping React Native's timer to start earlier, giving NativePHP a head-start. Why?

I created a follow up (#4) to also apply the same optimizations to React Native, which is super funny because now we both built native Android apps doing the same thing (ZXing).

@shanerbaner82 shanerbaner82 changed the title Keep the scan loop native: report from Kotlin/Swift over a keep-alive connection, tune the camera, clock from first scan Match the React Native throughput path with PHP Sep 16, 2026
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