Skip to content

feat: ordered startup + Services autoload extraction#2159

Draft
kuruk-mm wants to merge 6 commits into
mainfrom
feat/ordered-startup-instrumentation
Draft

feat: ordered startup + Services autoload extraction#2159
kuruk-mm wants to merge 6 commits into
mainfrom
feat/ordered-startup-instrumentation

Conversation

@kuruk-mm

Copy link
Copy Markdown
Member

Summary

Diagnoses + reshapes the boot path for the iOS WatchdogTermination Sentry issue GODOT-EXPLORER-3B (392 events / 97 users in prod, every release since 0.44; same crash as #1532 / tracked in #1980). Three changes land together:

  1. BootInstrumentation autoload — boot clock + Sentry breadcrumb chain + last_boot_step tag on every event. On non-prod, renders an on-screen overlay with ephemeral step lines + a circular RAM-usage indicator pinned outside the safe area (triple-finger / F8 toggle). When a watchdog kill happens, the Sentry event now arrives tagged with the last step that fired.
  2. Services autoload — single registry for every long-lived dependency. Owns 12 GDScript service objects (realm, scene_fetcher, modal_manager, music_player, …), the 5 previously-autoloaded scripts (ui_sounds, notifications_manager, connection_quality_monitor, impostor_capturer, avatar_lod_coordinator), and forwarding getters (typed where possible) for 19 Rust-inherited singletons on DclGlobal. Services.bootstrap() is awaited from main.gd under the visible splash; it yields process_frame between heavy blocks so iOS sees a responsive main loop. Rust singletons attach as children of /root/Services instead of /root.
  3. Global shrunk — keeps signals, config constants, runtime state (camera mode, height tracking, session_id), CLI / orientation / Sentry-tag setup, and the async_route_to_target_scene branching that used to live in main.gd. Six autoload entries removed from project.godot (down from 8 to 3: BootInstrumentation, Services, Global).

Scope

  • 135 files changed, +1946 / -1432.
  • ~1000 callsites swept mechanically (UiSounds.*Services.ui_sounds.*, Global.realmServices.realm, Global.get_config()Services.config, etc.).
  • 1 Rust patch: lib/src/scene_runner/global_get_node_helper.rsget_realm_node now resolves /root/Services/realm (was /root/realm). No other absolute path lookups exist in Rust per audit.
  • class_name UISounds stripped from ui_sounds.gd; entry removed from .godot/global_script_class_cache.cfg.

Why this fixes the watchdog

The previous boot ran _async_clear_cache_if_needed() plus ~20 add_child.call_deferred Rust singleton attaches inside Global._ready, under the engine's opaque splash — iOS sees the main loop go silent and kills the process. Now those run inside Services.bootstrap() awaited from main.tscn's visible splash, with await get_tree().process_frame yields between heavy blocks. The instrumentation also tells us exactly which step is the last one before a kill, so if a watchdog still fires we can target the culprit.

Test plan

  • cargo run -- run — desktop smoke; should reach lobby; step prints monotonic.
  • cargo run -- run -- --emulate-ios and … --emulate-ios --landscape — visual smoke + BootInstrumentation overlay sanity (RAM indicator in the notch channel in both orientations).
  • cargo run -- run --target ios — original repro from [Bug]: iOS sometimes loading too long (shaders?) #1532: open app, wait on splash, lock the phone 30s, unlock. App should survive.
  • cargo run -- run --itest — alternate-mode boot (cli.test_runner) routes through Services.bootstrap() then async_route_to_target_scene().
  • cargo run -- run --stest — same for scene tests.
  • Post-merge ~3–7 days: query Sentry last_boot_step tag values on GODOT-EXPLORER-3B (filtered to environment:production). Either the event count drops sharply, or remaining events cluster on one step we can target next.

Validated

  • gdformat godot/ — clean
  • gdlint godot/ — no problems
  • cargo run -- check-gdscript — 303 scripts, 0 errors
  • Rust clippy on patched helper — clean

Risk

  • Rust singletons now live at /root/Services/<Name>. Phase-1 audit confirmed only global_get_node_helper.rs uses /root/realm; no other absolute-path lookups exist. Patched in this PR.
  • Initialization order is now deterministic instead of "all on the next frame". Anything implicitly depending on deferred attachment timing could regress — integration tests above are the gate.
  • All ~1000 renames are mechanical (sed + check-gdscript clean after each batch). No silent class-name shadowing risk (UISounds was the only class_name on the demoted scripts; stripped).

Related: #1532 (user repro), #1980 (tracking), GODOT-EXPLORER-3B (Sentry).

kuruk-mm added 2 commits May 21, 2026 15:28
Addresses Sentry GODOT-EXPLORER-3B (iOS WatchdogTermination, #1532,
#1980) by (1) introducing a BootInstrumentation autoload that records
every named boot step as a Sentry breadcrumb + last_boot_step tag,
(2) extracting all service-like dependencies out of Global into a new
Services autoload whose bootstrap() runs under main.tscn's visible
splash, and (3) collapsing six autoload entries into children of
Services.

- BootInstrumentation (new): boot clock, Sentry breadcrumb chain, and
  on non-prod a CanvasLayer overlay with ephemeral step lines plus a
  circular RAM-usage indicator pinned outside the safe area
  (triple-finger / F8 toggle).

- Services (new): single registry for every long-lived dependency.
  Owns 12 GDScript service objects (realm, scene_fetcher,
  modal_manager, etc.), the 5 previously-autoloaded scripts
  (ui_sounds, notifications_manager, connection_quality_monitor,
  impostor_capturer, avatar_lod_coordinator), and forwarding getters
  (typed where possible) for 19 Rust-inherited singletons on
  DclGlobal. bootstrap() is awaited from main.gd; heavy work yields
  process_frame between steps so the iOS watchdog stays responsive.
  Rust singletons attach under /root/Services rather than /root.

- Global: shrunk by ~250 lines. Retains signals, config constants,
  runtime state (camera mode, height tracking, session_id),
  CLI / orientation / Sentry-tag setup, and the
  async_route_to_target_scene branching that used to live in main.gd.

- project.godot autoload block: 8 entries -> 3
  (BootInstrumentation, Services, Global).

- Callsite migration: ~1000 sites swept across 120+ files.
  UiSounds.* / NotificationsManager.* / ConnectionQualityMonitor.* /
  ImpostorCapturer.* / AvatarLODCoordinator.* -> Services.<name>.*;
  Global.realm / Global.player_identity / Global.metrics /
  Global.config / etc. -> Services.<name>;
  Global.get_config() -> Services.config.
  class_name UISounds removed from ui_sounds.gd and from
  .godot/global_script_class_cache.cfg.

- lib/src/scene_runner/global_get_node_helper.rs: get_realm_node now
  reads /root/Services/realm (was /root/realm) to match the new tree.

Validates clean: gdformat, gdlint, check-gdscript (303 scripts, 0
errors). Pending live smoke tests: cargo run -- run, --emulate-ios
(portrait + landscape), --itest, --stest, on-device iOS watchdog
repro.
…instrumentation

# Conflicts:
#	godot/src/decentraland_components/avatar_attach.gd
#	godot/src/global.gd
#	godot/src/logic/scene_fetcher.gd
#	godot/src/mobile/joystick/virtual_joystick.gd
#	godot/src/ui/explorer.gd
#	godot/src/ui/pages/auth/lobby.gd
#	godot/src/ui/pages/loading_screen/loading_screen.gd
#	godot/src/ui/pages/loading_screen/loading_screen_progress_logic.gd
@kuruk-mm kuruk-mm changed the title feat: ordered startup + Services autoload extraction (iOS watchdog fix) feat: ordered startup + Services autoload extraction May 29, 2026
@github-actions

github-actions Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

📦 Build Report

🤖 Android

Artifact Status
APK 📱 Download APK
AAB main/release builds only
Debug Symbols main/release builds only

Build Status: ✅ Success

🍏 iOS

The signed APK is uploaded to R2 on every build (link above). Add the build label (alias: build-ios) to build & ship iOS to TestFlight and post a Slack "Android build ready" notification.


🔗 Workflow Run: View logs

🔄 Updated: 2026-06-24 22:06:34 UTC

…instrumentation

# Conflicts:
#	godot/project.godot
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

🍏 iOS Build Complete

Status: Success

🔗 iOS Workflow: View build

📦 Artifacts: godot-mobile-deploy-pipeline

📍 Branch: PR-2159-feat/ordered-startup-instrumentation

🔄 Completed: 2026-06-01 19:11:39 UTC

kuruk-mm added 3 commits June 22, 2026 22:18
…instrumentation

# Conflicts:
#	godot/project.godot
#	godot/src/connection_quality_monitor.gd
#	godot/src/decentraland_components/avatar/avatar.gd
#	godot/src/decentraland_components/avatar/impostor/impostor_capturer.gd
#	godot/src/global.gd
#	godot/src/logic/player/player_gamepad_input.gd
#	godot/src/test/avatar/spawn_and_move.gd
#	godot/src/ui/components/organisms/profile_settings/profile_settings.gd
#	godot/src/ui/explorer.gd
#	godot/src/ui/pages/auth/lobby.gd
#	godot/src/ui/pages/backpack/backpack.gd
#	godot/src/ui/pages/discover/discover.gd
#	godot/src/ui/pages/settings/settings.gd
…tion

Main's iOS StoreKit env-report (iap_manager.gd) timed boot-relative
telemetry via Global._startup_time, which this branch removed in favor of
the BootInstrumentation boot clock. Add BootInstrumentation.boot_elapsed_ms()
and use it, fixing the merge fallout (undefined Global._startup_time -> parse
error in iap_manager.gd).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant