Skip to content

Physics: Add manual space stepping to PhysicsServer2D/3D - #7

Open
dsarno wants to merge 3 commits into
masterfrom
claude/physics-manual-space-step
Open

Physics: Add manual space stepping to PhysicsServer2D/3D#7
dsarno wants to merge 3 commits into
masterfrom
claude/physics-manual-space-step

Conversation

@dsarno

@dsarno dsarno commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

Adds three methods to PhysicsServer2D and PhysicsServer3D for driving a physics space by hand instead of letting the engine advance it once per frame:

  • space_step(space, delta) — advance one space
  • space_flush_queries(space) — run that space's contact and area monitoring callbacks
  • space_get_last_process_info(space, info) — per-space counters, since a manual step does not update the server-global ones

The motivating use cases are rollback-style networking (fast-forwarding a simulation after a correction) and prediction (previewing where a ball will end up before committing to a shot). The API is forward-only: snapshotting and restoring body state remains the caller's job.

The methods are plumbed through the abstract server, the dummy server, the GDExtension binding, the threaded wrapper, and all three backends (godot_physics_2d, godot_physics_3d, jolt_physics), and are marked experimental in the class reference.

Provenance

The first commit is third-party work, rebased onto current master and preserved with its original authorship. It did not apply cleanly — 14 files conflicted, because the physics enums were since moved out of the server classes into the PhysicsServer2DEnums/PhysicsServer3DEnums namespaces. Conflicts were resolved onto the current enum API.

The remaining two commits are mine: defects found while reviewing, and the tests that cover them.

This branch was produced by an AI agent. Everything below was verified by building and running it, but it warrants a human read before it goes anywhere.

Defects found and fixed

1. Flushing flag leaked on early return (all three backends). flushing_queries = true was set before the arguments were validated, so an invalid RID returned early with the flag still raised. The server then stayed permanently in the "flushing" state, silently disabling every guard that reads is_flushing_queries()Area2D::set_monitorable among them. The flag is now raised only after validation, and saved/restored rather than forced to false, so a nested flush from inside a physics callback cannot clear the outer one. This is the defect the last test case covers.

2. Jolt exhausted its job allocator. JoltPhysicsServer3D::step() brackets each space with job_system->pre_step() / post_step(), but the new space_step() did not. Repeated manual steps therefore ran the job system out of slots. Now bracketed identically.

3. Backends disagreed about active spaces. The GodotPhysics backends rejected a manual step on an active space; Jolt silently allowed it, double-advancing the simulation, since the engine already steps active spaces every physics frame. Jolt now rejects it too.

4. The documentation contradicted every backend. It described stepping an active space from _physics_process() as the supported workflow, which all three backends reject. Rewritten to state the actual contract: deactivate the space first.

5. Compile and style breakage from the rebase. ProcessInfo needed namespace qualification in the backend definitions and switch cases under the current enum API, and two GDVIRTUAL_BIND lines were missing trailing semicolons (196 of 198 in those files had them).

Testing

New file tests/servers/test_physics_server_manual_step.cpp, 8 cases / 29 assertions, all passing:

[doctest] test cases:  11 |  11 passed | 0 failed
[doctest] assertions:  29 |  29 passed | 0 failed

The test harness installs the dummy physics servers, so the backends are unreachable through PhysicsServer3DManager. The cases construct GodotPhysicsServer2D/3D directly instead. Coverage: a manually stepped inactive space advances; an unstepped space does not; stepping one space leaves another untouched; an active space rejects a manual step; and flushing with an invalid space leaves the flag clear.

The last case was verified to actually catch defect 1 — with the fix reverted it fails:

ERROR: CHECK( !server.is_flushing_queries() ) is NOT correct!
  values: CHECK( false )

Also verified locally: clang-format clean on all touched files, and --doctool regenerates the class reference with zero diff, so the documentation matches the bindings.

Open questions for review

These are real and deliberately not resolved here, because each one is a design decision rather than a defect:

  • Threading. In the wrapper, space_step is queued asynchronously, so under run_on_separate_thread the call returns before the step happens and there is no exposed sync point. space_get_last_process_info bypasses the queue entirely. Making this coherent probably needs a per-space sync primitive.
  • flushing_queries is server-wide. Save/restore makes it safe, but the flag arguably belongs on the space so that flushing one space doesn't affect guards on another.
  • GDExtension compatibility. The three new methods bind as virtual required, which breaks out-of-tree physics servers. A non-required binding or a compat shim may be wanted.
  • Jolt's space_get_last_process_info returns 0. Left as-is; it should either be implemented or explicitly documented as unsupported on that backend.

Generated by Claude Code

Daylily-Zeleen and others added 3 commits July 29, 2026 03:19
Review fixes on top of the manual space-stepping API:

- Fix `flushing_queries` being raised before argument validation. An
  invalid RID returned early with the flag still set, leaving the server
  permanently in the "flushing" state and silently disabling every guard
  that reads `is_flushing_queries()` (such as `Area2D::set_monitorable`).
  The flag is now saved and restored around the call rather than forced
  to `false`, so a nested flush from inside a physics callback cannot
  clear the outer one.

- Bracket the Jolt backend's `space_step()` with `job_system->pre_step()`
  and `post_step()`, matching `step()`. Without this, repeated manual
  steps exhaust Jolt's job allocator.

- Reject active spaces in the Jolt backend, matching the GodotPhysics
  backends. An active space is already advanced once per physics frame,
  so stepping it manually double-advanced the simulation.

- Correct the documentation, which described stepping an active space
  from `_physics_process()` as a supported workflow while every backend
  rejects it.

- Qualify `ProcessInfo` with the enum namespace in the backend
  definitions, and restore the trailing semicolons on two
  `GDVIRTUAL_BIND` lines.
Adds regression coverage for the manual stepping API against the real
GodotPhysics backends. The test harness installs the dummy servers, so
these cases construct `GodotPhysicsServer2D`/`3D` directly instead of
going through the server managers.

Covered: a manually stepped inactive space advances; an unstepped space
does not; stepping one space leaves another untouched; an active space
rejects a manual step; and flushing queries with an invalid space leaves
the server's flushing flag clear.

The last case fails without the accompanying validation-ordering fix.
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.

3 participants