Skip to content

feat(platform): add mobile touch input and app lifecycle for iOS/Android - #659

Merged
aram-devdocs merged 6 commits into
mainfrom
codex/issue-278-mobile-touch-lifecycle
Apr 3, 2026
Merged

feat(platform): add mobile touch input and app lifecycle for iOS/Android#659
aram-devdocs merged 6 commits into
mainfrom
codex/issue-278-mobile-touch-lifecycle

Conversation

@aram-devdocs

Copy link
Copy Markdown
Owner

Overview

Type: feature

Summary:
Cross-platform touch input handling and app lifecycle management for iOS and Android using winit's platform-agnostic Touch events and suspended/resumed callbacks.

Related Issues: Closes #278, Closes #279, Closes #286, Closes #303


Changes Made

Engine Core (goud_engine/src/)

  • Added TouchPhase enum, TouchId type alias, max_touch_points to InputCapabilities in core/providers/input_types.rs
  • Added AppSuspended and AppResumed lifecycle events in core/events/app.rs
  • Added TouchState struct in core/input_manager/types.rs
  • Added touch.rs module to InputManager with multi-touch tracking, touch-to-pointer emulation, and frame-based state cycling
  • 14 unit tests in core/input_manager/tests/touch.rs

FFI Layer (goud_engine/src/ffi/)

  • New ffi/input/touch.rs with 6 FFI functions: goud_input_touch_count, goud_input_touch_active, goud_input_touch_position, goud_input_touch_just_pressed, goud_input_touch_just_released, goud_input_touch_delta
  • Lifecycle transition detection and GPU surface management in ffi/window/state.rs

C# SDK (sdks/csharp/)

  • Generated touch input methods via codegen

Python SDK (sdks/python/)

  • Generated touch FFI declarations via codegen

TypeScript SDK (sdks/typescript/)

  • Node napi binding changes
  • Type definition changes

Codegen Pipeline (codegen/)

  • Schema changes (touch methods added to goud_sdk.schema.json)
  • ffi_mapping changes (touch functions mapped)

Proc Macros (goud_engine_macros/)

No changes

Tools (tools/)

No changes

WASM (goud_engine/src/wasm/)

No changes

Examples (examples/)

No changes

Documentation

No changes


Architectural Compliance

  • Rust-first: All logic lives in Rust; SDKs are thin wrappers
  • FFI boundary: New exports use #[no_mangle] extern "C" and #[repr(C)] where needed
  • Dependency flow: Imports follow layer hierarchy (down only)
  • SDK parity: Changes exposed via FFI are wrapped in C#, Python, AND TypeScript SDKs
  • Unsafe discipline: No unsafe block without a // SAFETY: comment
  • File size: No file exceeds 500 lines

Testing

  • cargo test passes (4736 lib tests, 0 failures)
  • cargo clippy -- -D warnings is clean
  • cargo fmt --all -- --check passes
  • Codegen produces consistent output (python3 codegen/validate.py && python3 codegen/validate_coverage.py)
  • Pre-commit hooks pass

Code Quality

  • No todo!() or unimplemented!() in production code
  • No #[allow(unused)] without justification comment
  • Error handling uses Result, not unwrap()/expect() in library code
  • Public items have doc comments

Documentation

  • Added doc comments on new public APIs

Breaking Changes

None

  • API changes: None (additive only)
  • FFI signature changes: 6 new functions, no changes to existing
  • SDK interface changes: 6 new methods on GoudGame, no changes to existing

Version Bump

Bump type: minor
Justification: New touch input and lifecycle APIs (additive feature)


Security

  • New unsafe blocks each have // SAFETY: comments (touch_position and touch_delta FFI functions)
  • New FFI pointer parameters have null checks
  • No new dependencies with known advisories
  • No secrets or credentials in committed files

Performance

N/A — Touch input tracking uses HashMap<u64, TouchState> which is negligible overhead. Surface lifecycle (drop/recreate) only occurs on mobile suspend/resume transitions.


Deployment

  • NuGet package version updated (if SDK changed)
  • Python package version updated (if SDK changed)
  • npm package version updated (if TS SDK changed)
  • Native library builds on all targets (macOS, Linux, Windows)

Reviewer Notes

  • Touch-to-pointer emulation is enabled by default: touch 0 maps to MouseButton::Left + mouse position. Existing game code works on touch devices without changes.
  • AppSuspended/AppResumed events are defined but not yet emitted to the ECS event bus — the WinitPlatform doesn't have access to the World. Event emission will be integrated when the mobile event loop inversion (run_app()) is implemented.
  • The wgpu surface lifecycle (drop on suspend, recreate on resume) is wired through WindowState::poll_events() transition detection.
  • pump_app_events is desktop-only in winit — the cfg gates are widened for mobile but the actual mobile event loop integration is a separate architectural task.

🤖 Generated with Claude Code

Implement cross-platform touch input handling and app lifecycle management
using winit's platform-agnostic Touch events and suspended/resumed callbacks.

Touch input:
- TouchPhase enum, TouchId type, and TouchState tracking in InputManager
- Multi-touch support with per-finger position and delta tracking
- Touch-to-pointer emulation: touch 0 maps to MouseButton::Left + mouse position
- 6 new FFI functions (touch count, active, position, delta, just_pressed/released)
- 14 unit tests covering all touch scenarios

App lifecycle:
- AppSuspended/AppResumed event types in core events
- PlatformBackend trait gains on_suspended/on_resumed/is_suspended with defaults
- WinitPlatform implements suspended/resumed callbacks and Touch event handling
- wgpu surface becomes Option<Surface> with drop_surface/recreate_surface methods
- WindowState detects suspend/resume transitions and manages GPU surface lifecycle

Platform cfg gates widened to include target_os = "android" and "ios" for
winit_platform and native_runtime modules.

Closes #278, closes #279, closes #286, closes #303

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Claude finished @aram-devdocs's task in 3m 7s —— View job


PR Review: feat(platform): add mobile touch input and app lifecycle for iOS/Android

Tasks:

  • Gather context and understand PR scope
  • Phase 1: Context gathering — layers touched, PR type
  • Phase 2: Architecture validation — dependency flow, Rust-first
  • Phase 3: Anti-pattern scan — FFI safety, unsafe blocks, SDK parity
  • Phase 4: Quality assessment — clarity, tests, docs
  • Phase 5: Security review — FFI null checks, memory ownership
  • Phase 6: Scope verification
  • Phase 7: Final review output

Phase 1 — Context

Layers touched: Core (input_manager, events, providers), Libs (platform/winit, graphics/wgpu_backend, native_backend), FFI (input/touch, window/state), SDKs (C#, Python, TypeScript, Go, Kotlin, Swift, Lua, JNI)

PR type: Additive feature — new touch input API + mobile app lifecycle


Phase 2 — Architecture

Check Result
Dependency flow downward only ✓ Core → Libs → FFI → SDKs
No logic in SDKs ✓ All wrappers call FFI, no game logic
GPU calls in backend/ only drop_surface/recreate_surface live in wgpu_backend/init.rs and native_backend/mod.rs
No file exceeds 500 lines ✓ Largest new file is touch.rs (115 lines)

Phase 3 — Anti-Pattern Scan

Check Result
#[no_mangle] extern "C" on all FFI exports ✓ All 6 functions in ffi/input/touch.rs
unsafe blocks have // SAFETY: touch_position and touch_delta
FFI changes propagated to all SDKs ✓ C#, Python, TS, Go, Kotlin, Swift, Lua, JNI
No todo!()/unimplemented!()
No unwrap()/expect() in library code ✓ All use unwrap_or/?

Phase 4 — Quality Assessment

Positives:

  • 14 unit tests covering edge cases including same-frame tap behavior, multi-touch independence, pointer emulation, and touch_canceltouch_end parity. The documented behavior in same_frame_tap_not_detected_as_released is exemplary.
  • touch_just_released logic correctly avoids a common off-by-one mistake by checking both touches_current and touches_previous states.
  • Touch-to-pointer emulation is opt-out (enabled by default), preserving backward compatibility for existing desktop games.
  • begin_frame in frame.rs:13-15 silently skips rendering when surface is None, cleanly handling the mobile suspended state without crashing.

Warnings:

W1 — Misleading comment in InputManager::update()

goud_engine/src/core/input_manager/manager.rs:122:

// Reset moved touches back to started for next frame's delta detection
for state in self.touches_current.values_mut() {
    state.previous_position = state.position;
}

The comment says "reset moved touches back to started" but the phase is not reset — only previous_position is updated. A more accurate comment would be: "Snapshot current position as previous so delta is zero at frame start."
Fix this →

W2 — Missing test: touch_move with pointer emulation

goud_engine/src/core/input_manager/tests/touch.rs has pointer_emulation_maps_touch_to_mouse which tests start/end emulation but skips the move case. touch_move with emulation enabled should update the mouse position. This is a test gap.
Fix this →

W3 — FFI doc for goud_input_touch_delta says "if the touch is active" but it also returns true for ended touches

goud_engine/src/ffi/input/touch.rs:65-66:

/// Returns `true` if the touch is active and the delta was written.

The guard used is input.touch_position(touch_id).is_none(), and touch_position returns Some for any entry in touches_current — including Ended/Cancelled phases within the same frame. So this function returns true for a touch that ended this frame. The delta is valid in that case (last movement), but the doc is misleading. Consider changing the guard to if !input.touch_active(touch_id) or clarifying the doc.
Fix this →

W4 — AppSuspended/AppResumed exported as public API but never emitted

goud_engine/src/core/events/mod.rs:49 re-exports these types. They are part of the public API but are never inserted into the ECS event bus (acknowledged in reviewer notes). This could surprise users who subscribe to Events<AppSuspended> expecting to receive events. Consider either: (a) adding a #[doc(hidden)] or a prominent # Note in the doc comment saying emission is not yet wired, or (b) deferring the pub export until the event loop inversion task is complete.


Phase 5 — Security

Check Result
Null pointer checks on all FFI pointer params out_x, out_y, out_dx, out_dy all checked
Memory ownership documented ✓ "Caller guarantees pointers are valid and aligned"
Buffer bounds ✓ No raw buffer indexing
Use-after-free potential ✓ None — all data is owned by InputManager

The double-unsafe pattern in touch_position (outer function + inner block) is idiomatic and correct.


Phase 6 — Scope

  • 46 files changed: 8 new Rust files + expected codegen outputs across 8 SDK targets. Bulk is mechanical SDK regeneration — no concerns.
  • Cargo.lock churn from crate version bumps is unrelated but minor.
  • Version bump not yet reflected in NuGet/PyPI/npm packages — flagged in the PR checklist.

Verdict: APPROVED

This is a well-structured additive PR. The Rust-first architecture is followed, FFI patterns are correct, all 6+ SDK targets are updated, and the 14 unit tests cover the expected edge cases. The four warnings above are non-blocking but W1 and W2 are easy fixes worth landing before or shortly after merge.

BLOCKERS None
WARNINGS W1 misleading comment, W2 missing move-emulation test, W3 delta FFI doc accuracy, W4 dead event API surface

… stubs

pump_app_events is only available on desktop targets (via winit's
pump_events platform extension). Gate the import and call sites behind
cfg(not(target_os = "android/ios")), and provide a stub new() on mobile.

Add touch method stubs to the TypeScript web wrapper so the GoudGame
class correctly implements the IGoudGame interface.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added size/XL and removed size/XL labels Apr 2, 2026
aram-devdocs and others added 3 commits April 2, 2026 17:43
…ck repos

Kotlin 1.9.22 Gradle plugin failed to resolve from the Gradle Plugin
Portal (transient POM parse error). Upgrade to Kotlin 2.0.21 and add
pluginManagement with both gradlePluginPortal() and mavenCentral() as
fallback repositories to improve reliability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update drop_surface/recreate_surface cfg gates on NativeRenderBackend
to match the new any(wgpu-backend, xbox-gdk) pattern from the merged
xbox-gdk POC.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added size/XL and removed size/XL labels Apr 2, 2026
The xbox_init.rs path creates WgpuBackend without a winit window.
Make the window field Option<Arc<Window>> so Xbox can pass None while
desktop/mobile paths pass Some(window) for surface recreation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added size/XL and removed size/XL labels Apr 2, 2026
@aram-devdocs
aram-devdocs merged commit 81f844e into main Apr 3, 2026
45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

F19-07: Android app lifecycle F19-06: Android touch input handling F19-03: iOS app lifecycle F19-02: iOS touch input handling

1 participant