Skip to content

perf(tween): native Godot Tween for Move/Rotate/Scale [prototype, draft]#2170

Draft
manuelmaceira wants to merge 3 commits into
feat/performance-maximizingfrom
perf/transform-batching
Draft

perf(tween): native Godot Tween for Move/Rotate/Scale [prototype, draft]#2170
manuelmaceira wants to merge 3 commits into
feat/performance-maximizingfrom
perf/transform-batching

Conversation

@manuelmaceira
Copy link
Copy Markdown
Collaborator

What

Prototype migrating SDK7 Tween Move/Rotate/Scale modes from the per-frame Rust interpolation loop to Godot's native Tween (RefCounted, no Node overhead). Stacked on #2025 (perf stack).

Draft — perf-neutral on Genesis Plaza; kept as an architectural reference + the diagnostic flag it produced. See findings below before merging.

Changes

  • --native-tween flag (default OFF, deeplink native-tween=true). When on, SDK7 Tween components with Move/Rotate/Scale modes are driven by a Gd<Tween> created via node.create_tween(), configured with the mapped Godot TransitionType + EaseType. Eliminates the double-pass (Rust calc → CRDT put → re-dirty → transform_and_parent apply); Godot processes all active tweens in one C++ loop.
    • setup_native_tween() builds the tween on first frame; the main loop only polls is_running() to emit TweenState on transition.
    • On completion: writes final transform back to CRDT (SDK7 readout parity), drops the Gd<Tween>.
    • On reset/delete/mode-change: kill() the existing tween.
    • Continuous + TextureMove modes stay on the Rust path (need per-frame delta-time / UV callbacks).
  • --kill-animations flag (default OFF, deeplink kill-anims=true) — diagnostic. Gates update_tween + update_animator (Rust early-return) and sweeps the scene tree at warmup setting every AnimationPlayer/AnimationTree active=false. Measures the animation-free frame ceiling.

Bench findings (A54, HIGH, GP ea9cc738)

native-tween A/B

metric rust tween native tween Δ
fps 17.17 17.19 +0.02
render_gpu_ms 55.49 54.88 −0.61
render_cpu_ms 7.99 8.30 +0.31
primitives 2.22M 2.22M ~0

Perf-neutral. GP barely uses SDK7 Move/Rotate/Scale tweens — its animation load is 144 AnimationPlayers + AnimationTrees (glTF/avatar), a different subsystem. The fogata (16 fire particles × Move+Rotate+Scale via createOrReplace) is the heaviest SDK7-tween user and renders identically with native ON (correctness validated visually), but ~48 tweens is too few to move the frame. Native tween would win on a scene with hundreds of long-lived concurrent tweens; GP isn't that scene.

kill-animations diagnostic (counterintuitive)

metric baseline kill-anims Δ
fps 17.17 16.36 −0.81
primitives 2.22M 2.90M +30%
draws 1951 2338 +387

Disabling all 209 AnimationPlayer/Tree nodes made perf worse: GP's animations do implicit culling (scale-to-0 particles, visibility tracks, off-screen motion). Frozen at bind-pose, that geometry strands on-screen → +30% primitives. Animation is not the bottleneck in GP — it's the raw primitive/draw count (geometry: tris, LODs, mesh dup). Confirms the scene-side GLTF audit is where the real lever is.

Recommendation

Don't merge for perf — it's net-neutral on GP. Options:

  • Keep as reference for scenes that ARE tween-heavy.
  • Merge anyway for the architectural cleanup (no double-pass, ~250 LOC less Rust interpolation for those modes) if reviewers value it.
  • The --kill-animations diagnostic flag is worth keeping regardless (cheap, gated, useful for future profiling).

Test plan

  • cargo clippy --release -- -D warnings clean.
  • Fogata (Move/Rotate/Scale + createOrReplace churn) visually identical with native ON.
  • Bench A/B JSONs captured.
  • Stress scene with 200+ concurrent long-lived tweens (not done — would quantify the theoretical upside).

Adds --native-tween CLI flag. When enabled, SDK7 Tween components with
Move / Rotate / Scale modes are migrated from the per-frame Rust
interpolation + CRDT re-dirty loop to Godot's native Tween (RefCounted,
RID-based, no Node overhead per tween).

Mechanics:
- setup_native_tween(): on first frame of an eligible tween, creates a
  Gd<Tween> via node.create_tween() configured with the mapped Godot
  TransitionType + EaseType. For Move with face_direction, sets the
  rotation upfront then tweens position only.
- Main loop: for native-active tweens, only polls is_running() to emit
  TweenState on transition (TsActive -> TsCompleted / TsPaused). Skips
  all per-frame interpolation math and the CRDT.put + dirty injection.
- On completion: writes the final transform back to CRDT (so SDK7
  readout matches the rendered pose) and drops the Gd<Tween>.
- On reset / delete / mode change: kills the existing Gd<Tween>.

Continuous and TextureMove modes remain on the Rust path — they need
per-frame delta-time / UV callbacks that don't map cleanly to property
tweens.

Toggle: --native-tween (default OFF). Bench A/B not yet measured.
…rivers

Gates update_tween + update_animator (Rust early-return) and sweeps the
scene tree at warmup entry setting every AnimationPlayer / AnimationTree
active=false + callback MANUAL. Measures the animation-free frame ceiling.
@github-actions
Copy link
Copy Markdown
Contributor

📦 Build Report

🤖 Android

Artifact Status
APK 📱 Download APK
AAB 📦 Download AAB
Debug Symbols 🔧 Debug Symbols

Build Status: ✅ Success

🍏 iOS

iOS builds are triggered manually. Add the build-ios label to trigger an iOS build.


🔗 Workflow Run: View logs

🔄 Updated: 2026-05-26 17:33:05 UTC

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