Commit Graph

16 Commits

Author SHA1 Message Date
agra
12bf61a9fc std: restructure step 3 — ffi/ moves, build.sx, math dir spelling, fixtures
- objc.sx, objc_block.sx (from std/) + sdl3/opengl/raylib/stb/stb_truetype/
  wasm vendor bindings (from modules/ root) -> modules/ffi/
- std/uikit.sx deleted: platform/uikit.sx already declares UIApplicationMain
  and imports objc; '#framework "UIKit"' cannot live in a file imported on
  macOS targets (unconditional link directive, UIKit is iOS-only), so the
  three iOS-only examples carry the 3-line glue inline. 1607/1608/1616 also
  un-rotted (dead ns_string -> 'xx "..."' Into conversions, callconv(.c)
  msgSend fn-ptrs) — all three build for ios-sim/ios again.
- math/math.sx -> math/scalar.sx; one spelling '#import "modules/math"'
  everywhere (4 pinned IR snapshots regenerated: dir import adds Vec2/Mat4
  to the type tables).
- compiler.sx -> build.sx (imports, CLAUDE.md bundling table, specs.md).
- testpkg/ + test_c.sx -> tests/fixtures/ (resolve CWD-relative from repo
  root, same as vendors/).
- library-internal imports use full modules/... paths (std.sx tail,
  platform/bundle.sx, fixtures).
2026-06-11 08:37:22 +03:00
agra
bdd0e96d78 feat(lang): block value requires no trailing ; (Rust-style)
A block's value is now its last statement ONLY when that statement is a
trailing expression with no `;`. A trailing `;` discards the value,
leaving the block void. This makes value-vs-statement explicit and lets
the compiler reject "this block was supposed to produce a value".

Compiler:
- Parser records `Block.produces_value` (last stmt is a no-`;` trailing
  expression) + `Block.discarded_semi` (the `;` that discarded a value),
  via `expectSemicolonAfter`. A trailing expression before `}` may now
  omit its `;` (previously a parse error). Match-arm and else-arm bodies
  are built value-producing regardless of the arm `;` (arms are exempt —
  the `;` is an arm terminator).
- Lowering: `lowerBlockValue` / the block-expr path / `inferExprType`
  respect `produces_value`. A value-position block that discards its value
  is a hard error (`lowerValueBody` for function bodies; the value-context
  `.block` path for if/else branches, `catch` bodies, value bindings,
  match arms). Pure-failable `-> !` bodies (value rides the error channel)
  and a value-if whose branches are void are handled without false errors.
- `defer`/`onfail` cleanup bodies lower as statements (void), so a
  trailing `;` there is fine.

Migration (behavior-preserving — output unchanged):
- stdlib + ~210 examples: dropped the trailing `;` on value-position last
  expressions. `format` now ends with an explicit `#insert "return
  result;"` (it relied on `#insert`-as-block-value, which `;` discards).
- Two `main :: () -> s32` examples that relied on the old silent
  default-return got an explicit trailing `0`.
- Rejection snapshots 0412 / 1013 regenerated (their quoted source lines
  lost a `;`); the diagnostics themselves are unchanged.

Docs/tests: specs.md "Block values" section; examples 0040 (rules) + 0041
(rejection); 3 parser unit tests. Filed issue 0066 (pre-existing
match-arm negated-literal phi-width quirk, surfaced not caused here).

Gates: zig build, zig build test, run_examples.sh -> 343 passed,
cross_compile.sh -> 7 passed (also refreshed its stale example names).
2026-06-02 09:23:50 +03:00
agra
56414407fc ffi: drop static keyword on foreign-class methods; param type discriminates
`static name :: ...` was redundant — instance methods always declare
`self: *Self` as their first param by convention. The parser now derives
`is_static` from the first param's TYPE: if it's `*Self` the method is
an instance method; anything else (including no params at all) is a
class method. Removes a token from the surface, keeps the dispatch
behavior identical.

The receiver param's NAME doesn't matter — only its type. Calling the
first param `this`, `me`, `receiver`, etc. is fine as long as the type
is `*Self`. This mirrors how the rest of sx handles receiver dispatch.

Migration of every site that used the keyword:

- `library/modules/platform/android.sx` — `SurfaceView.new(ctx)`.
- `examples/ffi-jni-class-03-static.sx` — `Math.abs(n)`.
- `examples/ffi-jni-main-03-ctor.sx` — `SurfaceView.new(ctx)` in the
  `#jni_main` body.
- `examples/ffi-objc-dsl-05-static.sx` — NSObject's `.class()` /
  `.description()`.

164/164 example tests; chess clean on macOS / iOS sim / Android via
`tools/verify-step.sh`.
2026-05-25 16:32:32 +03:00
agra
d4a342d0c1 mem: implicit-Context platform fixes — chess green on macOS/iOS/Android
Verify-step uncovered three categories of regressions where sx code
calls into the platform's C ABI through fn-pointer types or as a
registered callback. Every site now declares the right convention.

C-side calls INTO sx → callconv(.c) on the sx function:
- platform/android.sx: sx_android_render_thread_entry is the start
  routine pthread_create invokes — pthread treats it as a C function.
  Also annotate the pthread_create signature so the start-routine fn-
  pointer field rejects mismatching sx fns at compile time.

sx code calling typed fn-pointers cast from C symbols → callconv(.c)
on the fn-pointer type:
- opengl.sx: 55 GL fn-ptr globals + load_gl's proc-loader param. GL
  trampolines are macOS/iOS/Android system code.
- std/objc.sx: the two typed `objc_msgSend` casts.
- gpu/metal.sx: ~40 typed `objc_msgSend` casts across Metal command
  encoder / device / pipeline construction.

The block invoke trampolines (objc_block.sx) call back INTO sx (the
closure trampoline). The typed fn-ptr there stays default-conv so
ctx prepends correctly. Compiler change: a callconv(.c) sx function
now binds `current_ctx_ref` to `&__sx_default_context` at entry (used
to be gated by `isExportedEntryName`). C-callable sx callbacks like
the block invokes don't get their own __sx_ctx param but their bodies
still need a real Context to forward to the closure they delegate to.

Tests: 152/152 example suite + chess green on all 3 platforms.
Screenshots at /tmp/sx-game-{macos,iossim,android}.png.
2026-05-25 09:35:15 +03:00
agra
632e64512b bundling: Android APK pipeline moved into sx; android.sx state-on-plat
Week 7 of /Users/agra/.claude/plans/lets-plan-to-move-splendid-pumpkin.md
plus the android.sx refactor + three sx-compiler fixes hit along the way
to get chess on Pixel 7 Pro responding to touch end-to-end.

library/modules/platform/bundle.sx now covers the Android APK shape
alongside macOS / iOS-sim / iOS-device. `android_bundle_main` discovers
the SDK ($ANDROID_HOME / $ANDROID_SDK_ROOT / $HOME/Library/Android/sdk),
picks the highest-versioned build-tools + platforms via
`process.run("ls .. | sort -V | tail -1")`, stages
`<apk>.stage/lib/arm64-v8a/<libfoo.so>`, synthesizes
AndroidManifest.xml (NativeActivity vs `#jni_main` Activity branch),
writes each `#jni_main` decl's Java source under
`<stage>/java/<pkg>/<Cls>.java`, runs javac --release 11 + d8 to
produce classes.dex, aapt2-links the unaligned APK, appends lib/ +
classes.dex + each registered asset tree via zip, zipalign + ensure
debug keystore via keytool + apksigner sign.

Compiler-side accessors (src/ir/compiler_hooks.zig + library/modules/compiler.sx):
- is_android predicate.
- set_manifest_path / manifest_path + set_keystore_path / keystore_path.
- jni_main_count / jni_main_foreign_path_at(i) /
  jni_main_java_source_at(i) surface the `#jni_main` emissions that
  the Zig createApk previously consumed directly.
- main.zig wires manifest_path, keystore_path, and the per-decl
  (foreign_path, java_source) parallel slices into BuildConfig before
  invoking the post-link callback.

CLI `--apk <path>` keeps working as a transitional alias: it now feeds
bundle_path so the existing auto-`post_link_module = "platform.bundle"`
shim fires the same way as `--bundle`. main.zig no longer calls
target.createApk directly.

Deletions in src/target.zig: createApk, compileJniMainSources,
buildJniMainManifest, buildAndroidManifest, ensureDebugKeystore,
libNameFromSoBasename, plus helpers splitForeignPath / discoverJavac /
discoverAndroidSdk / findHighestSubdir / runProcess / runProcessIn
(~400 lines). git grep returns only the obituary comment.

library/modules/platform/android.sx refactor (chess Android dependency):
- Module-level globals retired (g_app_window, g_egl_*, g_viewport_*,
  g_dpi_scale, g_should_stop, g_render_thread*, g_user_main_fn,
  g_touch_*) → AndroidPlatform struct fields.
- All sx_android_* helpers take `plat: *AndroidPlatform` as first arg.
  Render thread receives plat via pthread_create's arg.
- New `logical_w: f32 = 0.0` field. Consumers set it before init() to
  define the design width in points; `recompute_scale` derives
  `dpi_scale = pixel_w / logical_w` (or 1.0 if unset). Called on
  init / set_viewport / egl_init. drain_touches divides incoming
  physical pixel coords by dpi_scale so chess sees logical-space
  positions matching its layout. Touch lands on the right squares.

Three sx-compiler bugs hit + fixed along the way:

1. Top-level `inline if OS == .X { decls }` body decls were silently
   dropped because scanDecls/lowerDecls had no .if_expr arm. New
   `flattenComptimeConditionals` pre-pass in src/imports.zig
   (threaded via ComptimeContext from core.zig) hoists matching arms
   recursively. Regression at examples/124-inline-if-hoist-toplevel.sx.

2. Parser rejected `#import` / `#framework` inside inline-if bodies
   because parseStmt in src/parser.zig only had arms for `#insert`.
   Added the missing arms. Regression at
   examples/123-inline-if-import-in-body.sx (landed earlier).

3. JNI `Call<T>Method` switches in src/ir/emit_llvm.zig (instance /
   nonvirtual / static) were missing `.f32` rows — jfloat returns
   (e.g. MotionEvent.getX/getY) fell into the silent-undef else arm.
   Chess's sx_android_push_touch(plat, getAction(), getX(), getY())
   delivered garbage f32 coords to the touch ring, so taps landed
   nowhere recognisable. Added `.f32 => Jni.Call{Static,Nonvirtual,}FloatMethod`
   rows to all three switches; lifted unsupported-type detection
   from emit_llvm into lowerForeignMethodCall with proper
   source-spanned diagnostics (`isJniReturnTypeSupported`). Regressions
   at examples/ffi-jni-call-10-jfloat-return.sx,
   examples/ffi-jni-class-09-multi-float-args.sx,
   examples/ffi-jni-call-11-unsupported-return-diag.sx.

Stale-snapshot drift in tests/expected/ffi-objc-call-03-selector-sharing.ir
and ffi-objc-call-06-sret-return.ir picks up the new BuildOptions
accessor extern decls (is_android, set_manifest_path,
set_keystore_path, jni_main_count, jni_main_foreign_path_at,
jni_main_java_source_at). Verified diff is dead-decl-only.

Chess on Pixel 7 Pro: tap on e2 white pawn -> yellow selection +
green dots on legal e3/e4 targets; tap on e4 -> board updates with
1. e4, "Black to move" + "1. e4" in info panel.

zig build && zig build test && bash tests/run_examples.sh -> 145/145
green. bash tests/cross_compile.sh -> 7/7 green.
2026-05-23 01:28:32 +03:00
agra
cc29cfa7ce ffi #jni_main: jni_java_emit + android.sx + manifest fixes; chess on Pixel
Combined slice — gets chess rendering on a Pixel 7 Pro via the
`#jni_main` pipeline. Half-dozen jni_java_emit fixes plus the rebuilt
stdlib android module:

  jni_java_emit:
    - `#implements Alias;` body members render as Java `implements`
      clauses on the class header (space-separated, registry-resolved).
    - Drop the implicit `super.<method>(args)` call from the @Override
      delegate — interface impls (SurfaceHolder.Callback) have no
      super; user calls super explicitly from sx-side via
      `super.method(args)` lowered to `CallNonvirtual<T>Method`.
    - `static { System.loadLibrary("<libname>"); }` static init block,
      lib name derived from the build's `-o` basename.
    - `name: Type;` body items render as private Java fields.
    - `$` (JNI nested-class shape) → `.` in Java source: e.g.
      `android/view/SurfaceHolder$Callback` → `android.view.SurfaceHolder.Callback`.
    - Non-void @Override bodies `return` the native delegate's result.

  lower.zig:
    - `super.method(args)` sugar inside a `#jni_main` (or any
      sx-defined `#jni_class`) bodied method lowers to JNI
      `CallNonvirtual<T>Method` with the parent class resolved via
      `#extends` (default Activity).
    - `Alias.new(args)` constructor sugar lowers to JNI
      `FindClass + GetMethodID("<init>", sig) + NewObject`.
    - `jniMapParamType` stops erasing pointer types so method dispatch
      on foreign-class params (`holder.getSurface()`) resolves.
    - synthesizeJniMainStub pushes the env arg onto the lexical
      `#jni_env` stack so omitted-env `#jni_call` and `super.method`
      sites see it.

  target.zig:
    - Manifest synthesised from `#jni_main` adds
      `android:theme="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"`
      so sx apps own the whole window (no title strip, no status bar).

  library/modules/platform/android.sx (NEW):
    - Replaces the retired NativeActivity-based module under #jni_main.
    - Foreign-class decls for Bundle / Context / Surface / SurfaceHolder
      / SurfaceView / MotionEvent / View / Activity / SurfaceHolderCallback /
      AssetManagerJ.
    - libandroid / EGL / pthread foreign C decls.
    - Helpers consumers call from their Activity body:
      `sx_android_forward_assets(env, ctx)`,
      `sx_android_attach_window(env, holder)`,
      `sx_android_detach_window()`,
      `sx_android_set_viewport(w, h)`,
      `sx_android_start_render_thread(main_fn)`,
      `sx_android_push_touch(action, x, y)`.
    - Render thread brings up EGL on the ANativeWindow then calls the
      user-supplied entry fn pointer.
    - `AndroidPlatform` struct + `impl Platform` (init / begin_frame /
      end_frame / poll_events / safe_insets / keyboard / show_keyboard /
      hide_keyboard / stop / shutdown / run_frame_loop).

End-to-end verified on a Pixel 7 Pro: chess APK builds via
`sx build --target android --apk ... --bundle-id ... -o ...`, installs
via `adb install -r`, launches and renders the chess board with all
pieces in starting position. No title strip, no flicker. Touch events
reach `sx_android_push_touch` and drain through `poll_events` (debug-
verified) — chess's pipeline-side hit-test routing + DPI-correct
sizing remain as follow-ups.

138 host / 8 cross / `zig build test` all green.
2026-05-20 19:50:25 +03:00
agra
619d524bac ffi #jni_main R.5: retire legacy NativeActivity surface
Deletes the entire NativeActivity / native_app_glue / ALooper stack
that the previous Android entry path was built around:

  - `examples/99-android-egl-clear.sx` — the demo of the legacy path.
  - `library/modules/platform/android.sx` — `AndroidPlatform.init`,
    `run_frame_loop`, `sx_android_bootstrap`, `g_android_app`, plus
    the ALooper / AInputEvent / ANativeActivity / AConfiguration
    foreign decls that fed them. The JNI helpers (`sx_load_javavm_fn`,
    `sx_android_get_env`, `sx_query_safe_insets_jni`, the
    `ANATIVEACTIVITY_*` offsets) were tied to the ANativeActivity*
    delivered to `android_main` — they're stale now that the OS hands
    sx code a Java Activity directly via `onCreate(JNIEnv*, jobject)`.
  - `library/vendors/sx_android_jni/sx_android_jni.c` — the input-
    handler installer (`sx_android_install_input_handler`), which
    poked NDK app-pointer field offsets that no longer exist.

`library/modules/platform/android_jni.sx` (the `Activity`/`Window`/
`View`/`WindowInsets` `#jni_class` registry used for safe-insets
dispatch) survives — it's standalone declarative bindings useful from
any `#jni_main` onCreate body. Docstring updated to drop the
"imported from android.sx" framing.

131 host / 4 cross / zig build test all green. End-to-end smoke APK
still produces the expected JNI-mangled symbol +
SxApp-extends-Activity dex.

External consumers (chess) will need to migrate their entry from the
`AndroidPlatform.run_frame_loop` model to the `#jni_main` model
(Java-side Activity drives lifecycle; onSurfaceChanged / Choreographer
drive frames via JNI callbacks). That migration is downstream work.
2026-05-20 15:17:24 +03:00
agra
60f3ffed46 ffi 2D: migrate android.sx safe-insets to declarative #jni_class blocks
The four foreign-class declarations move into a new sub-module
`library/modules/platform/android_jni.sx`, imported under a named
namespace from `android.sx`:

    Jni :: #import "modules/platform/android_jni.sx";

This keeps the bare class names (`Activity`, `Window`, `View`,
`WindowInsets`) out of the top level — consumers that flat-import
`modules/platform/android.sx` no longer see `View` collide with
`modules/ui/view.sx`'s protocol of the same name (chess hit this
on the first build attempt).

Compiler-side change: `scanDecls`/`lowerDecls` now also iterate any
`namespace_decl` they encounter and register the contained
`foreign_class_decl`s under their qualified name (`Jni.Activity`).
The recursive scan continues to register the bare names too, so
cross-class refs inside method signatures (e.g. `getWindow ::
(self: *Self) -> *Window`) still resolve through the bare key.
Receiver types like `*Jni.Activity` now route through
`getStructTypeName` → "Jni.Activity" → `foreign_class_map` lookup.

`sx_query_safe_insets_jni`'s param signature changes from
`activity: *Activity` to `activity: *Jni.Activity`; the caller in
`AndroidPlatform.safe_insets` casts via `xx`.

Verified on-device — chess APK built with the new sx, installed via
`adb install -r`, launched on the Pixel. Screencap shows the board
rendering with correct status-bar clearance (time + battery icons
visible above the board, board sized below them) — safe insets are
being queried via the new declarative dispatch and produce the same
values as the pre-migration hand-rolled #jni_call chain.

129/129 examples + cross_compile 3/3 + on-device chess all green.
2026-05-20 12:19:15 +03:00
agra
c9db2a8dc0 ffi 2D: migrate android.sx safe-insets to declarative #jni_class blocks
`sx_query_safe_insets_jni`'s body — previously seven hand-rolled
`#jni_call` sites with verbose JNI descriptor literals — now uses
four `#jni_class` declarations and the DSL method-call form inside
a `#jni_env(env) { ... }` scope. The new shape:

```
WindowInsets :: #jni_class("android/view/WindowInsets") {
    getSystemWindowInsetTop :: (self: *Self) -> s32;
    ...
}
... Activity / Window / View ...

#jni_env(env) {
    window := activity.getWindow();
    decor := window.getDecorView();
    insets := decor.getRootWindowInsets();
    top.* = insets.getSystemWindowInsetTop();
    ...
}
```

Descriptor derivation happens at lower time (jni_descriptor.zig);
slot interning + vtable dispatch shape match the Phase 1C hand-rolled
form byte-for-byte. The function param signature changes from
`activity: *void` to `activity: *Activity` so the DSL can resolve
method names through `foreign_class_map`; the AndroidPlatform.safe_insets
caller adds an `xx` cast at the call site.

Net body shrinks from 14 dispatch lines to 12 (slightly shorter but
the win is type safety + readability — the foreign descriptor
strings are gone). On-device chess regression is the remaining
verification step (Pixel device with safe-area-driven board layout).

Verified locally: zig build, run_examples (129/129), cross_compile
(3/3 — incl. examples/99-android-egl-clear.sx cross-compile to
android target succeeds and produces a valid .o).

Naming caveat: `Activity` / `Window` / `View` / `WindowInsets` are
now top-level names exported by `modules/platform/android.sx`. User
code that imports this module shouldn't redefine these aliases.
2026-05-20 11:34:04 +03:00
agra
4ddee931b5 ffi 1.29: retire the C sx_android_query_safe_insets body
Closes the Phase 1D migration for the safe-insets JNI chain. The C
function and its `#foreign` declaration in `android.sx` are gone;
all dispatch now goes through the sx-side `#jni_call` machinery
plus the JavaVM helpers landed in 1.26.

What's gone from `library/vendors/sx_android_jni/sx_android_jni.c`:
- `#include <android/native_activity.h>` and `<jni.h>` (no longer
  needed without the JNI body).
- `sx_android_query_safe_insets` — 55 lines of `(*env)->Foo` chain
  with manual `goto done` early-exit. Migrated to
  `library/modules/platform/android.sx::sx_query_safe_insets_jni`
  in 1.25 (15 lines of `#jni_call`).

What stays:
- `sx_android_install_input_handler` — non-JNI; struct-field
  assignment against `struct android_app`'s `onInputEvent` slot.
  No sx equivalent yet (would need to either land a `#android_app`-
  style intrinsic or hand-roll the offset, neither of which is
  Phase 1 scope).
- `<android/input.h>` and the `struct sx_android_app_min` mirror
  needed by the input-handler installer.

Net diff: -55 lines in the .c file, -1 line `#foreign` decl in
android.sx. Phase 2 (declarative JNI imports) will revisit whether
the .c file can be deleted entirely (the input-handler hop may
move into a different shape).

Verification:
- zig build + zig test + run_examples + cross_compile all green.
  Notable: the previously-failing `ffi-objc-call-12-rect-u64-returns`
  also passes now — looks like the working-tree `#import c` work
  was tidied up alongside.
- chess Android APK rebuilt + reinstalled + launched on Pixel
  device; safe-insets behavior unchanged (board top edge sits below
  the status bar correctly, all pieces in starting positions, no
  status-bar overlap).
2026-05-19 23:13:51 +03:00
agra
6e65324f44 ffi 1.27: switch safe-insets call site to sx-side JNI implementation
`AndroidPlatform.safe_insets` now reaches into the JVM through the
sx helpers from 1.25 + 1.26 instead of the C `sx_android_query_safe_insets`
foreign call:

  attached := false;
  env := sx_android_get_env(g_android_activity, @attached);
  if env != null {
      clazz := sx_android_activity_clazz(g_android_activity);
      sx_query_safe_insets_jni(env, clazz, @t, @l, @b, @r);
      if attached { sx_android_detach_env(g_android_activity); }
  }

Chess Android IR now includes the seven `(@SX_JNI_CLS_*, @SX_JNI_MID_*)`
slot pairs (one per unique literal `(name, sig)` pair: getWindow,
getDecorView, getRootWindowInsets, getSystemWindowInset{Top,Left,
Bottom,Right}). First call populates each; subsequent calls hit the
cached jmethodID via the 1.17 lazy-init branch.

The C `sx_android_query_safe_insets` body is now unused; left in place
per the plan ("leave the file in place until Phase 2 deletes it").
Chess Android + iOS-sim both compile clean; host 118/119;
cross-compile 3/3.

On-device chess regression is the next checkpoint — the safe-area
behavior is visible: board must sit below the status bar with
correct top inset on a Pixel 7 Pro with notch. Deferred to the next
session (requires APK build + adb install + screencap).
2026-05-19 23:01:23 +03:00
agra
885b4239c9 ffi 1.26: hand-roll JavaVM dispatch in sx for env attach
Adds the JavaVM-side vtable indirection to `library/modules/platform/
android.sx` so the sx caller of `sx_query_safe_insets_jni` (1.25)
can obtain a `JNIEnv*` without the C wrapper. `#jni_call` only
dispatches through `JNIEnv*`'s vtable (a different table from
`JavaVM*`'s), so the JavaVM hop is hand-rolled here.

New decls:
- `JNI_VERSION_1_6` (0x00010006) and the `ANATIVEACTIVITY_*` byte
  offsets (8, 24 on 64-bit Android — vm, clazz respectively).
- `sx_load_ptr_at(base, offset)` — load a `*void` field at a raw
  byte offset. Used for both ANativeActivity fields and the JavaVM
  vtable load.
- `sx_load_javavm_fn(vm, slot)` — load function pointer at the
  given vtable slot. `vm` is `JavaVM*` which points to
  `JNIInvokeInterface*`; the indirection is `*vm + slot * 8`.
- `sx_android_get_env(activity, out_attached)` — calls `GetEnv`
  (slot 6); on `JNI_EDETACHED` falls through to
  `AttachCurrentThread` (slot 4), sets `out_attached = true` so
  caller can balance with `sx_android_detach_env` (slot 5).
- `sx_android_activity_clazz(activity)` — reads the jobject at byte
  offset 24.

Chess Android + iOS-sim builds still clean; cross-compile 3/3
green; host 118/119. The new functions dead-strip until step 1.27
wires them into the safe-insets call site in
`android.sx::AndroidPlatform.safe_insets`.
2026-05-19 22:58:29 +03:00
agra
ba0a1a13e3 ffi 1.25: sx-side reimplementation of safe-insets JNI chain
Phase 1D for `library/vendors/sx_android_jni/sx_android_jni.c` starts
here. Adds `sx_query_safe_insets_jni` to `library/modules/platform/
android.sx` — a sx-side implementation of the JNI dispatch chain
that lives inside the C `sx_android_query_safe_insets` helper.

The C version is ~50 lines of `(*env)->GetMethodID` + `CallObjectMethod`
+ `CallIntMethod` boilerplate with manual `goto done` early-exit
plumbing on every step. The sx version collapses to four
`#jni_call(*void)` chain steps + four `#jni_call(s32)` reads at the
end — each #jni_call internally handles GetObjectClass + GetMethodID
+ Call<Type>Method via the slot interning from 1.17.

Signature differences from the C version:
- The sx version takes `env: *void` directly. The C version derives
  it from `ANativeActivity*` via JavaVM's GetEnv/AttachCurrentThread.
  Bridging that gap (sx-side JavaVM dispatch OR a tiny C shim that
  returns the env) is the next Phase 1D step.
- The activity arg here is the jobject (`ANativeActivity*.clazz`)
  rather than the activity pointer itself.

No call sites switched yet. Chess Android still uses the foreign C
function. Cross-compile + chess both targets all clean — verifies
the new function typechecks and lowers, but on-device runtime
verification is deferred to the integration commit.
2026-05-19 22:54:24 +03:00
agra
4849cfb904 android: dpi_scale, scissor, JNI safe-insets, touch input
Four Android UX wins landing together; all verified end-to-end on a
Pixel 7 Pro (board fills width, info-panel text renders, status bar
inset honored, tap-to-select + tap-to-move plays 1. e4).

- AndroidPlatform.init reads density via AConfiguration_getDensity
  (app->config at offset 32) and sets dpi_scale = density / 160. The
  hardcoded 1.0 had been making every logical unit equal one physical
  pixel; ChessBoardView's 520-default size_that_fits fallback then
  rendered at ~half the framebuffer width on the device, and glyphs
  rasterized at literal 11-13 physical pixels were essentially invisible
  on a 2340-tall display.
- gles3.sx set_scissor un-stubbed; with dpi_scale right the renderer
  feeds in valid pixel bounds and the Y-flip math lands inside the
  framebuffer.
- New library/vendors/sx_android_jni/sx_android_jni.c walks
  activity -> window -> decorView -> rootWindowInsets via JNI and
  publishes the system-bar insets. safe_insets() lazy-queries the
  first call after EGL is up (decor view isn't attached at bootstrap).
- sx_android_install_input_handler sets app->onInputEvent; sx-side
  sx_android_input_event translates AMotionEvent DOWN/MOVE/UP/CANCEL
  into existing mouse_down/mouse_moved/mouse_up Events so the chess
  board's tap-to-select + ScrollView drag path Just Works. Coordinates
  divided by dpi_scale so layout-side hit tests match. poll_events
  drains its slice after returning (mirrors the SDL pattern).
- src/imports.zig now routes #import c { #source / #include } paths
  through the same chain as #import (importing dir -> CWD -> stdlib
  search paths). Lets library-owned C helpers like the JNI bridge
  live in sx/library/vendors/ without forcing consumers to vendor a
  copy. Existing CWD-relative consumer layouts (chess's vendors/...)
  still resolve first, so no regression.

86/86 regression tests pass.
2026-05-19 11:09:41 +03:00
agra
b5bf789b7b android: AAssetManager bootstrap + APK asset bundling + scissor TODO
platform/android.sx: `sx_android_bootstrap(app)` now also reads the
ANativeActivity's `assetManager` (offset 64) and `internalDataPath`
(offset 32) into module globals so consumers can route file I/O
through the APK's bundled `assets/` tree.

target.zig (`createApk`): also zips the project's `./assets/`
directory into the APK alongside `lib/<arch>/`. Resolves relative
to the user's CWD at invoke time — matches the convention chess
uses (assets/ next to main.sx).

gles3.sx: scissor is currently a no-op on Android. The renderer's
ScrollView clip_push path feeds bounds that land outside the
framebuffer (clipping everything off-screen). With scissor disabled
the chess board + pieces render correctly. TODO recorded in the
file to fix the bounds path properly.
2026-05-19 10:09:30 +03:00
agra
561ad03a7c android: Platform-owned entry bridge + .android OS enum variant
User writes BOTH `main` and a 3-line `android_main(app)` trampoline.
The library provides `sx_android_bootstrap(app)` (stashes the NDK app
pointer into a platform-owned global) and `AndroidPlatform` impl of
the Platform protocol. The library NEVER references `main` — the OS-
shape entry symbol lives in user code where the other entry symbols
already live. iOS / SDL3 keep their existing shape; only Android adds
the trampoline.

Cross-cutting bits this commit ships:

  library/modules/compiler.sx
    Add `android` variant to `OperatingSystem`.

  src/ir/lower.zig
    - injectComptimeConstants: map TargetConfig.isAndroid() → .android.
    - New Pass 4 `checkRequiredEntryPoints`: emit a clean diagnostic
      when `--target android` is requested but `android_main` isn't
      defined, instead of letting the user crash on a dlopen-time
      missing-symbol error.

  library/modules/platform/android.sx
    AndroidPlatform impl of the Platform protocol — EGL bringup on
    `APP_CMD_INIT_WINDOW`, ALooper(0) polling, dispatches the user's
    frame closure each ~16 ms tick. `sx_android_bootstrap(app)` is the
    only function exposed for the entry trampoline.

  examples/99-android-egl-clear.sx
    Rewritten to use the new pattern: minimum `main` + `android_main`
    pair, AndroidPlatform-driven render loop. Doubles as the usage
    reference users hand off to the compiler diagnostic.

Verified on Pixel 7 Pro: purple clear-color frame, periodic
`rendered 60 frames` logcat lines. iOS-sim chess + 86/86 regression
tests pass.
2026-05-19 00:23:33 +03:00