feat(ios): AR moves to Zig — ratchet 18 -> 13 - #126
Merged
Conversation
The last group whose deferral reason was an effort judgement rather than a wall — 'SceneKit node-graph glue; miserable and low-value through objc_msgSend'. The node graph turned out not to be the hard part. What is hard is that this is the first module to pass and return structs by value in floating-point registers, and it does it three ways: CGRect for the view frame, SCNVector3 for a node's position, and simd_float3 for a plane anchor's centre and extent. The last one uses a different rule from the other two — sixteen bytes in one vector register, not a float aggregate in four — and getting it wrong yields plausible coordinates rather than a crash. So all three are exercised against real objects rather than read off the headers. SceneKit ships on macOS even though ARKit does not, which is what makes SCNNode, SCNBox and simdPosition reachable from a host test. The simd_float3 guard was mutation-tested: decoding it as four floats leaves y and z at zero, and the test says so. SCNVector3 is three CGFloats on macOS and three floats on iOS, so the alias follows the target and the host test verifies the mechanism at the wrong width. That is a real limit and it is written down in the module. Ports two details that are easy to miss: extent contributes x and z as width and height, so a plane's y extent never reaches the page; and position is a [String: Double] cast of the whole dictionary, so one non-number discards all three components rather than just its own.
alloc/initWithFrame: already gives this frame the reference ar_view owns and stopAR releases, and addSubview: gives the window a second one that removeFromSuperview drops. The extra retain outlived both, leaking the view and its whole SceneKit graph on every start/stop cycle.
✅ Binary load timeWhat this measures
Both binaries are measured interleaved on this runner and compared by |
✅ Binary Size Report
Size limits
|
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.
Moves
startAR,stopAR,placeARObject,removeARObjectandgetARPlanesto Zig. Ratchet 18 → 13.
This was the last group whose deferral reason was an effort judgement rather
than a wall — "SceneKit node-graph glue; miserable and low-value through
objc_msgSend". The node graph turned out not to be the hard part.The actual risk: three struct returns, not the node graph
Every previously migrated module passes and returns pointers and integers. This
one passes and returns structs by value in floating-point registers, three
different ways:
CGRectUIScreen.main.bounds,-initWithFrame:v0–v3SCNVector3SCNNode.position,eulerAnglesv0–v2simd_float3ARPlaneAnchor.center/.extentv0aloneThe third uses a different rule from the other two. Getting it wrong does not
crash — it yields plausible coordinates that ARKit never measured, which is a
wrong answer delivered as a right one.
So none of the three is asserted from the headers and hoped for. SceneKit
ships on macOS even though ARKit does not, which makes
SCNNode,SCNBoxand— crucially —
simdPositionreachable from a host test. All three round-tripagainst real objects in
zig build test:ios.The
simd_float3guard was mutation-tested: swapping@Vector(4, f32)for afour-field extern struct makes it read
y = 0andz = 0whilexstillagrees, and the test fails exactly there. It bites.
What this cannot verify, and won't pretend to
ARWorldTrackingConfiguration.isSupportedis false in the simulator, sostartARrefuses there and every line past that guard needs hardware I don'thave. The tests cover argument parsing, the reply shapes, the plane JSON, the
refusal paths and the three ABI mechanisms. They do not cover a running AR
session, and no test in this repository can.
One further limit, written into the module rather than glossed:
SceneKitTypes.hdeclares
SCNVector3as threeCGFloats on macOS and threefloats on iOS(checked in both SDKs). The alias follows the target, so the host test verifies
the mechanism at double width, not the exact iOS layout.
Two details that are easy to miss, and are ported
extentcontributes x and z, mapped towidthandheight. A plane'syextent never reaches the page at all.positionis a[String: Double]cast of the whole dictionary. It failsas a unit, so
{"x":1,"y":"up"}is not "x with a bad y" — it is no position,and the node lands at the default
(0, 0, -0.5). A reader that keptxwouldplace the object somewhere Swift never would. Pinned by its own test.
Divergences
INVALID_ARGUMENT→INVALID_PARAMETER, and "AR not supported" →PLATFORM_NOT_SUPPORTED.BridgeErrorcarries an enum, not free text — thesame trade
bridge_mobile_misc.zigandbridge_mobile_watch.zigdocument.getARPlanesis the only one ofthe five Swift does not wrap in
DispatchQueue.main.async, so astartARimmediately followed by a
getARPlaneshas the planes reply first. Both aresynchronous here, so replies keep the order the page sent them in.
config.enableARin the spec — unlike the recorder, where onlythe start is gated — so all five are added to
ios_config.gateFor. Theconformance test that asserted
gateFor("startAR") == nullis inverted ratherthan deleted: it now asserts the gate is there.
Verification
zig build test:ios— 7/7 steps, 1083/1095 passed, 12 skipped, 0 failed.zig build test— 1454/1454 passed. The one failing step isinjected_js_test.zigcompiling against a localzig-jscheckout that isincompatible with the pinned toolchain; it fails identically on
main,checked before claiming it.
casearms stay as the spec, and Ziganswers first — the same arrangement
readLocationRecordinguses.