feat(sokol): register AndroidBackendContext with core seam (#310 stage 3) - #312
Conversation
…ore#310, Stage 3) Sokol-Android must register an AndroidBackendContext now that core/engine no longer link sokol's symbols directly. Add backends/sokol/src/android.zig adapter (callconv(.c) get_native_activity wrapper @constCasting sokol's const result + extern labelle_android_gamepad_init/_shutdown), surface it as backend_input.android, wire core onto the sokol input module on Android, and emit engine.core.registerAndroidBackend(...) at the top of the generated sokol-Android sokol_main() — before enableImmersiveMode() and before the gamepad source initializes. Emitted on every sokol-Android build (gamepad detection needs it even when immersive mode is off); desktop/wasm/ios codegen unchanged.
PR SummaryMedium Risk Overview A new Codegen changes Tests add a Reviewed by Cursor Bugbot for commit 3ae1ec2. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Code Review
This pull request implements Stage 3 of the backend-agnostic JNI seam (labelle-core#310) for the Sokol Android backend, introducing a new android.zig adapter to register the AndroidBackendContext at startup. The build configuration, codegen, and tests are updated to support this integration. The review feedback highlights potential compilation failures on non-Android platforms due to unconditional imports of labelle-core and references to Android-specific Sokol symbols, recommending compile-time gating. Additionally, it suggests removing redundant callconv(.c) specifiers on extern "c" declarations.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| /// (`engine.core.registerAndroidBackend(...)`) so core's gamepad source and the | ||
| /// engine's immersive mode can reach the running ANativeActivity / InputManager | ||
| /// without core/engine linking any sokol symbol directly. See `android.zig`. | ||
| pub const android = @import("android.zig"); |
There was a problem hiding this comment.
On non-Android platforms (such as macOS or Windows desktop), labelle-core is not added as an import to input_mod in build.zig. Since android.zig unconditionally imports labelle-core, any semantic analysis or reference to android on those platforms will trigger a compilation error. Gating the import of android.zig using agp.is_android prevents this file from being imported on non-Android platforms.
pub const android = if (agp.is_android) @import("android.zig") else struct {};
| fn getNativeActivity() callconv(.c) ?*anyopaque { | ||
| const act = sapp.androidGetNativeActivity(); | ||
| return @constCast(act); | ||
| } |
There was a problem hiding this comment.
Since android.zig is parsed on all platforms, referencing sapp.androidGetNativeActivity() directly can cause compilation failures on non-Android platforms where the symbol is not present in the sokol package. Pruning the function body at compile-time using comptime when is_android is false prevents the compiler from analyzing the missing symbol.
fn getNativeActivity() callconv(.c) ?*anyopaque {\n if (comptime !is_android) return null;\n const act = sapp.androidGetNativeActivity();\n return @constCast(act);\n}
| extern "c" fn labelle_android_gamepad_init(activity: ?*anyopaque) callconv(.c) void; | ||
| extern "c" fn labelle_android_gamepad_shutdown() callconv(.c) void; |
There was a problem hiding this comment.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5308d61. Configure here.
|
Bot triage — addressed in the latest commit:
Verified: assembler |
…redundant callconv (review) Stage-3 bot review: - Unconditional `pub const android = @import("android.zig")` (Gemini+Cursor, high): android.zig imports labelle-core + references sokol's androidGetNativeActivity, neither wired into the input module on desktop/wasm. It only passed because the unreferenced re-export was lazily skipped — fragile. Gate the re-export so the adapter is analyzed only on Android; on other targets it's an empty namespace. Resolves the companion sapp.androidGetNativeActivity desktop concern too. - Redundant callconv(.c) on extern "c" fn (Gemini, medium): extern "c" already implies the C convention; dropped it from the two gamepad-glue externs.
Backend work since v0.39.1: - wgpu: macOS Metal surface + textured sprite rendering (#290, #291) - bgfx: macOS bring-up to on-device Android (#296 epic, #304/#305/#307/#308/#309) - #310 AndroidBackendContext adapters: sokol (#312) + bgfx (#313) register the core seam; bgfx-Android gamepad via the shared android_gamepad sub-package - bgfx desktop gamepad: GLFW (#315) + SDL HIDAPI / Switch-pad support (#318) - cached bgfx CI job (#295) Android codegen now calls core.registerAndroidBackend → requires labelle-core >= v1.17.0 + labelle-engine >= v1.50.0.

Stage 3 of labelle-core#310 — the sokol backend registers an
AndroidBackendContextwith core's seam (Stages 1 core + 2 engine merged), so sokol-Android keeps immersive + gamepad working now that core/engine no longer link sokol's symbols directly.What
backends/sokol/src/android.zig(new adapter) — builds thecore.AndroidBackendContextfrom sokol's own symbols:get_native_activitywrapssapp.androidGetNativeActivity()(@constCasting sokol's?*const anyopaqueto the seam's?*anyopaque);gamepad_init/gamepad_shutdownareextern "c"decls for the existing JNI glue (android_gamepad_jni.c, already compiled into the input module).pub fn backendContext()returns the vtable.backends/sokol/src/input.zig— re-exportpub const android = @import("android.zig")so the generated main reaches it asbackend_input.android.backends/sokol/build.zig— Android branch wireslabelle-coreonto the input module (mutually exclusive with the Linux-desktop core route).src/codegen/lifecycle/callback.zig— the generated sokolsokol_main()now emits, on every sokol-Android build (gamepad needs the context even with immersive off):engine.core.registerAndroidBackend(@import("backend_input").android.backendContext());— before the optionalengine.android.enableImmersiveMode(). Both run on the UI thread at startup, before core's gamepad source first polls. Desktop/wasm/iOS codegen unchanged (gated onplatform == .android).test/— asokol_mobile_lifecyclefixture + 3 codegen assertions (registration emitted + precedes immersive; emitted with immersive off; neither on non-Android).Verification
zig build+zig build test→ green (incl. the 3 new assertions).-Dtarget=wasm32-emscripten) → green, unaffected.zig build-obj -target aarch64-linux-androidagainst the merged seam core) → clean (the@constCast, externs, and context literal type-check on Android).engine.core.registerAndroidBackend+backend_input.android; no removed sokol externs insrc/.Honest scope
Full sokol-Android device/APK build not run — it needs the sokol-Android native toolchain (NDK sysroot GLESv3/EGL/android/log wired by the generated app build) and a seam-core install the example pins to (the example currently pins pre-seam
local:siblings; only the merged core/enginemaincarry the seam). Verified via codegen assertions + the Android-target adapter compile-check against the seam core.Version-compat
Generated sokol-Android apps now call
engine.core.registerAndroidBackend(...), so they require labelle-core ≥ the Stage-1 seam + labelle-engine ≥ Stage-2. Pre-seam apps won't compile. (Stage 4 wires the bgfx adapter using the same backend-agnostic JNI glue.)