Skip to content

Commit 55ddf46

Browse files
committed
Add semi-fixed timestep and physics time stretching for global timescale
Fixes #24769 Fixes #24334 The main change in this PR is adding the option in project settings->physics to choose between the old fixed timestep and a new path for semi-fixed timestep. With semi-fixed timestep users can either choose a high physics fps and get the benefit of matching between physics and frame times, or low physics fps and have physics effectively driven by frame deltas. There is also a minor refactor to the main::iteration function, notably moving the physics tick into a separate function, as well as a major refactor to main_timer_sync, separating the common components of timing (timescaling, limiting max physics ticks) from the details of the timestep functionality themselves, which are separated into 2 classes, MainTimerSync_JitterFix (the old fixed timestep) and MainTimerSync_SemiFixed. There is also a modification to allow the existing global time_scale to change the speed of the game without affecting the physics tick rate (i.e. giving consistent physics at different timescales).
1 parent adae2b0 commit 55ddf46

File tree

6 files changed

+417
-166
lines changed

6 files changed

+417
-166
lines changed

core/engine.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ float Engine::get_time_scale() const {
8989
return _time_scale;
9090
}
9191

92+
bool Engine::get_physics_stretch_ticks() const {
93+
return _physics_stretch_ticks;
94+
}
95+
9296
Dictionary Engine::get_version_info() const {
9397

9498
Dictionary dict;
@@ -232,6 +236,7 @@ Engine::Engine() {
232236
_fps = 1;
233237
_target_fps = 0;
234238
_time_scale = 1.0;
239+
_physics_stretch_ticks = true;
235240
_pixel_snap = false;
236241
_physics_frames = 0;
237242
_idle_frames = 0;

core/engine.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class Engine {
6161
float _fps;
6262
int _target_fps;
6363
float _time_scale;
64+
bool _physics_stretch_ticks;
6465
bool _pixel_snap;
6566
uint64_t _physics_frames;
6667
float _physics_interpolation_fraction;
@@ -100,6 +101,7 @@ class Engine {
100101

101102
void set_time_scale(float p_scale);
102103
float get_time_scale() const;
104+
bool get_physics_stretch_ticks() const;
103105

104106
void set_frame_delay(uint32_t p_msec);
105107
uint32_t get_frame_delay() const;

main/main.cpp

Lines changed: 61 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@
7878
#include "editor/project_manager.h"
7979
#endif
8080

81+
#include <stdint.h>
82+
8183
/* Static members */
8284

8385
// Singletons
@@ -1026,6 +1028,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
10261028
Engine::get_singleton()->set_iterations_per_second(GLOBAL_DEF("physics/common/physics_fps", 60));
10271029
ProjectSettings::get_singleton()->set_custom_property_info("physics/common/physics_fps", PropertyInfo(Variant::INT, "physics/common/physics_fps", PROPERTY_HINT_RANGE, "1,120,1,or_greater"));
10281030
Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5));
1031+
GLOBAL_DEF("physics/common/timestep/method", "Jitter Fix");
1032+
ProjectSettings::get_singleton()->set_custom_property_info("physics/common/timestep/method", PropertyInfo(Variant::STRING, "physics/common/timestep/method", PROPERTY_HINT_ENUM, "Jitter Fix,Fixed,Semi Fixed"));
1033+
Engine::get_singleton()->_physics_stretch_ticks = GLOBAL_DEF("physics/common/timestep/timescale_stretch_ticks", true);
1034+
10291035
Engine::get_singleton()->set_target_fps(GLOBAL_DEF("debug/settings/fps/force_fps", 0));
10301036
ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/fps/force_fps", PropertyInfo(Variant::INT, "debug/settings/fps/force_fps", PROPERTY_HINT_RANGE, "0,120,1,or_greater"));
10311037

@@ -1856,6 +1862,35 @@ bool Main::is_iterating() {
18561862
static uint64_t physics_process_max = 0;
18571863
static uint64_t idle_process_max = 0;
18581864

1865+
// returns usecs taken by the physics tick
1866+
uint64_t Main::physics_tick(float p_physics_delta) {
1867+
uint64_t physics_begin = OS::get_singleton()->get_ticks_usec();
1868+
1869+
PhysicsServer::get_singleton()->sync();
1870+
PhysicsServer::get_singleton()->flush_queries();
1871+
1872+
Physics2DServer::get_singleton()->sync();
1873+
Physics2DServer::get_singleton()->flush_queries();
1874+
1875+
if (OS::get_singleton()->get_main_loop()->iteration(p_physics_delta)) {
1876+
// UINT64_MAX indicates we want to stop the loop through the physics iterations
1877+
return UINT64_MAX;
1878+
}
1879+
1880+
message_queue->flush();
1881+
1882+
PhysicsServer::get_singleton()->step(p_physics_delta);
1883+
1884+
Physics2DServer::get_singleton()->end_sync();
1885+
Physics2DServer::get_singleton()->step(p_physics_delta);
1886+
1887+
message_queue->flush();
1888+
1889+
Engine::get_singleton()->_physics_frames++;
1890+
1891+
return OS::get_singleton()->get_ticks_usec() - physics_begin;
1892+
}
1893+
18591894
bool Main::iteration() {
18601895

18611896
//for now do not error on this
@@ -1865,21 +1900,21 @@ bool Main::iteration() {
18651900

18661901
uint64_t ticks = OS::get_singleton()->get_ticks_usec();
18671902
Engine::get_singleton()->_frame_ticks = ticks;
1868-
main_timer_sync.set_cpu_ticks_usec(ticks);
1869-
main_timer_sync.set_fixed_fps(fixed_fps);
18701903

18711904
uint64_t ticks_elapsed = ticks - last_ticks;
18721905

18731906
int physics_fps = Engine::get_singleton()->get_iterations_per_second();
1874-
float frame_slice = 1.0 / physics_fps;
18751907

1876-
float time_scale = Engine::get_singleton()->get_time_scale();
1908+
// main_timer_sync will deal with time_scale and limiting the max number of physics ticks
1909+
MainFrameTime advance;
1910+
main_timer_sync.advance(advance, physics_fps, ticks, fixed_fps);
18771911

1878-
MainFrameTime advance = main_timer_sync.advance(frame_slice, physics_fps);
1879-
double step = advance.idle_step;
1880-
double scaled_step = step * time_scale;
1912+
double scaled_step = advance.scaled_frame_delta;
18811913

1882-
Engine::get_singleton()->_frame_step = step;
1914+
// Note Engine::_frame_step was previously the step unadjusted for timescale.
1915+
// It was unused within Godot, although perhaps used in custom Modules, I'm assuming this was a bug
1916+
// as scaled step makes more sense.
1917+
Engine::get_singleton()->_frame_step = scaled_step;
18831918
Engine::get_singleton()->_physics_interpolation_fraction = advance.interpolation_fraction;
18841919

18851920
uint64_t physics_process_ticks = 0;
@@ -1889,50 +1924,40 @@ bool Main::iteration() {
18891924

18901925
last_ticks = ticks;
18911926

1892-
static const int max_physics_steps = 8;
1893-
if (fixed_fps == -1 && advance.physics_steps > max_physics_steps) {
1894-
step -= (advance.physics_steps - max_physics_steps) * frame_slice;
1895-
advance.physics_steps = max_physics_steps;
1896-
}
1897-
18981927
bool exit = false;
18991928

19001929
Engine::get_singleton()->_in_physics = true;
19011930

1902-
for (int iters = 0; iters < advance.physics_steps; ++iters) {
1931+
float physics_delta = advance.physics_fixed_step_delta;
19031932

1904-
uint64_t physics_begin = OS::get_singleton()->get_ticks_usec();
1933+
for (int iters = 0; iters < advance.physics_steps; ++iters) {
19051934

1906-
PhysicsServer::get_singleton()->sync();
1907-
PhysicsServer::get_singleton()->flush_queries();
1935+
// special case, if using variable physics timestep and the last physics step
1936+
if (advance.physics_variable_step && (iters == (advance.physics_steps - 1))) {
1937+
// substitute the variable delta
1938+
physics_delta = advance.physics_variable_step_delta;
1939+
}
19081940

1909-
Physics2DServer::get_singleton()->sync();
1910-
Physics2DServer::get_singleton()->flush_queries();
1941+
// returns the time taken by the physics tick
1942+
uint64_t physics_usecs = physics_tick(physics_delta);
19111943

1912-
if (OS::get_singleton()->get_main_loop()->iteration(frame_slice * time_scale)) {
1944+
// in the special case of wanting to exit the loop we are passing
1945+
// UINT64_MAX which will never occur normally.
1946+
if (physics_usecs == UINT64_MAX) {
19131947
exit = true;
19141948
break;
19151949
}
19161950

1917-
message_queue->flush();
1918-
1919-
PhysicsServer::get_singleton()->step(frame_slice * time_scale);
1920-
1921-
Physics2DServer::get_singleton()->end_sync();
1922-
Physics2DServer::get_singleton()->step(frame_slice * time_scale);
1923-
1924-
message_queue->flush();
1925-
1926-
physics_process_ticks = MAX(physics_process_ticks, OS::get_singleton()->get_ticks_usec() - physics_begin); // keep the largest one for reference
1927-
physics_process_max = MAX(OS::get_singleton()->get_ticks_usec() - physics_begin, physics_process_max);
1928-
Engine::get_singleton()->_physics_frames++;
1951+
// performance stats
1952+
physics_process_ticks = MAX(physics_process_ticks, physics_usecs); // keep the largest one for reference
1953+
physics_process_max = MAX(physics_usecs, physics_process_max);
19291954
}
19301955

19311956
Engine::get_singleton()->_in_physics = false;
19321957

19331958
uint64_t idle_begin = OS::get_singleton()->get_ticks_usec();
19341959

1935-
if (OS::get_singleton()->get_main_loop()->idle(step * time_scale)) {
1960+
if (OS::get_singleton()->get_main_loop()->idle(scaled_step)) {
19361961
exit = true;
19371962
}
19381963
message_queue->flush();
@@ -1965,6 +1990,8 @@ bool Main::iteration() {
19651990

19661991
if (script_debugger) {
19671992
if (script_debugger->is_profiling()) {
1993+
// note that frame_slice is original physics delta, before time_scale applied
1994+
float frame_slice = 1.0 / physics_fps;
19681995
script_debugger->profiling_set_frame_times(USEC_TO_SEC(frame_time), USEC_TO_SEC(idle_process_ticks), USEC_TO_SEC(physics_process_ticks), frame_slice);
19691996
}
19701997
script_debugger->idle_poll();

main/main.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class Main {
5757
static bool start();
5858

5959
static bool iteration();
60+
static uint64_t physics_tick(float p_physics_delta);
6061
static void force_redraw();
6162

6263
static bool is_iterating();

0 commit comments

Comments
 (0)