feat(apple): iOS host — pocket-apple core crate, PocketSurfaceView, external-guest mode - #255
feat(apple): iOS host — pocket-apple core crate, PocketSurfaceView, external-guest mode#255NathanWalker wants to merge 2 commits into
Conversation
…xternal-guest mode
A new engine/apple workspace member packaging modern iOS as a PocketJS
host. The composition mirrors hosts/pocketbook: one pocket_mod::Guest
realm, one pocket_ui_surface::UiSurface, and pocketjs_core::raster driven
incrementally through a DamageTracker, exposed behind a small C ABI.
- engine/apple/src/lib.rs — guest-owning mode: create/load_pak/eval_bundle/
frame/render/hit_test_bounds/destroy, plus an effect channel over the
ui.svc* ops (set_effect_callback drains guest svcSend lines during frame;
post_event queues lines for the guest's next svcPoll).
- engine/apple/src/core_host.rs — external-guest mode: pocket_apple_core_*
owns only the core, pak feed, raster pipeline, and svc queues, for hosts
whose JS engine lives elsewhere (demonstrated with the NativeScript
runtime evaluating the guest bundle in its own context).
- engine/apple/apple/PocketSurfaceView.{h,m} — CADisplayLink capped at
60 Hz, latched touch contacts (a down+up between two ticks still reaches
the guest as one present frame then a release), aspect-fit inverse touch
mapping, damage-gated compositing of the ARGB32 framebuffer.
- engine/apple/build-xcframework.sh — clang-linked dynamic framework per
slice (device arm64 + simulator arm64), no Xcode project.
- pocket-ui-surface additionally mounts hitTestBounds (spec op 42), the
touch-path hit authority the gesture layer prefers over the ink-claiming
hitTest.
- apps/nsengine — reference guest: an effect driver over svcSend, a
per-frame poll pump, a focusable pressable button, and a platform-reach
probe that distinguishes a sidecar realm from an embedding-runtime host.
rquickjs uses its bindgen feature: no pregenerated bindings exist for
aarch64-apple-ios targets. Validation: the render_hero example drives the
ABI end to end — 180 frames of apps/hero/main.tsx at 480x272 density 2
render non-blank and byte-identical across two independent instances.
Build guests with --density matching the surface density (glyphs bake at
build time; density 4 supersamples cleanly on 3x screens).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2dcab2e to
535d43a
Compare
…at the mounted entry Review fixes for pocket-stack#255: - rquickjs's bindgen feature was enabled unconditionally on a workspace member, so cargo feature unification rebuilt rquickjs-sys with bindgen (libclang required) for every desktop build of the engine workspace — including `cargo test --workspace` in the release workflow, which installs no clang. Scope it to cfg(target_os = "ios") following the hosts/pocketbook precedent; desktop builds keep the pre-generated bindings. pocket-apple never uses rquickjs directly (the dep exists only to flip the feature), and anyhow was declared but unused — drop it. - render_hero's usage line and defaults pointed at ../dist/hero.js, but `bun tools/build.ts hero` builds the component-only bundle, which installs no frame() and fails eval with "bundle installed no frame()". Point both at the hero-main mounted entry and document the build step. Verified: `cargo build -p pocket-apple` (desktop, no bindgen in the resolved graph per cargo tree), `cargo build -p pocket-apple --target aarch64-apple-ios` (bindgen active), and zero-arg `cargo run -p pocket-apple --example render_hero` renders 180 frames, non-blank, byte-identical across two instances. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review — verified locally, two fixes pushed, remaining findings belowVerdict: the architecture direction is right and the core claims check out. The composition genuinely reuses the shared crates (no copy of pak feeding, op mounting, or guest plumbing in What I ran
Pushed to this branch (4fe7bd8)
Remaining findings, ranked (none pushed — they need your call)
Verified clean, for the record: op-42 registry consistency; touch encoding; damage-plan arithmetic (full redraw can't be skipped, 🤖 Generated with Claude Code |
What
A new
engine/appleworkspace member (pocket-apple) plus a UIKit view class, packaging modern iOS as a PocketJS host. The composition mirrorshosts/pocketbook: onepocket_mod::Guestrealm, onepocket_ui_surface::UiSurface, andpocketjs_core::rasterdriven incrementally through aDamageTracker, exposed behind a small C ABI.engine/apple/src/lib.rs— guest-owning mode:pocket_apple_create/load_pak/eval_bundle/frame/render/hit_test_bounds/destroy, plus an effect channel over theui.svc*ops (set_effect_callbackdrains guestsvcSendlines during frame;post_eventqueues lines for the guest's nextsvcPoll).engine/apple/src/core_host.rs— external-guest mode:pocket_apple_core_*owns only the core, pak feed, raster pipeline, and svc queues, for hosts whose JS engine lives elsewhere (demonstrated with the NativeScript runtime: the guest bundle evaluates in the embedding runtime's context andglobalThis.uidelegates each op over the bridge).engine/apple/apple/PocketSurfaceView.{h,m}— CADisplayLink capped at 60 Hz, packed touch words with aspect-fit inverse mapping, damage-gated compositing of the ARGB32 framebuffer intolayer.contents.engine/apple/build-xcframework.sh— clang-linked dynamic framework per slice (device arm64 + simulator arm64), no Xcode project.apps/nsengine— reference guest for the effect channel: an effect driver oversvcSend+ a per-frame poll pump, and a platform-reach probe (typeof UIDevice) that distinguishes a sidecar realm from an embedding-runtime host.rquickjsuses itsbindgenfeature here: no pregenerated bindings exist foraarch64-apple-iostargets.Validation
cargo run -p pocket-apple --example render_hero— 180 frames ofapps/hero/main.tsxat 480×272 density 2: non-blank, byte-identical across two independent instances, incremental damage plans active. Release mode renders 360 frames plus two guest boots in 0.34 s wall.Not included (follow-ups)
tools/iphone2g-profile.tspattern) is the likely next step beforePOCKET_TARGETSregistration.pocket-ui-wgpurendering into aCAMetalLayersurface as a second path behind the same view class.