Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a project setting and Engine property to toggle fixed FPS at runtime #60284

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Calinou
Copy link
Member

@Calinou Calinou commented Apr 15, 2022

Non-realtime simulation can now be enabled at runtime and enabled without having to use a command line argument.

The "realtime" value was changed from -1 to 0 as a fixed FPS of 0 is not valid. This is more consistent with Engine.target_fps.

This PR can be remade for 3.x if desired.

Testing project: test_fixed_fps.zip

April 2022 patch
From f9c924b24e7a762e76f90e982b00bddcc78c5ace Mon Sep 17 00:00:00 2001
From: Hugo Locurcio <hugo.locurcio@hugo.pro>
Date: Fri, 15 Apr 2022 23:36:40 +0200
Subject: [PATCH] Add a project setting and Engine property to toggle fixed FPS
 at runtime

Non-realtime simulation can now be enabled at runtime and enabled
without having to use a command line argument.
---
 core/config/engine.cpp          |  9 +++++++++
 core/config/engine.h            |  4 ++++
 core/core_bind.cpp              | 11 +++++++++++
 core/core_bind.h                |  3 +++
 doc/classes/Engine.xml          |  6 +++++-
 doc/classes/ProjectSettings.xml | 13 +++++++++----
 main/main.cpp                   | 15 ++++++++++-----
 main/main_timer_sync.cpp        |  2 +-
 8 files changed, 52 insertions(+), 11 deletions(-)

diff --git a/core/config/engine.cpp b/core/config/engine.cpp
index ff8a8d283fa2..778e95dfe1f4 100644
--- a/core/config/engine.cpp
+++ b/core/config/engine.cpp
@@ -64,6 +64,15 @@ int Engine::get_target_fps() const {
 	return _target_fps;
 }
 
+void Engine::set_fixed_fps(int p_fps) {
+	ERR_FAIL_COND_MSG(p_fps <= -1, "Fixed FPS must be a positive value (or 0 to disable fixed FPS).");
+	fixed_fps = p_fps;
+}
+
+int Engine::get_fixed_fps() const {
+	return fixed_fps;
+}
+
 uint64_t Engine::get_frames_drawn() {
 	return frames_drawn;
 }
diff --git a/core/config/engine.h b/core/config/engine.h
index eac96852b30a..a061dd084fba 100644
--- a/core/config/engine.h
+++ b/core/config/engine.h
@@ -57,6 +57,7 @@ class Engine {
 	int ips = 60;
 	double physics_jitter_fix = 0.5;
 	double _fps = 1;
+	int fixed_fps = 0;
 	int _target_fps = 0;
 	double _time_scale = 1.0;
 	uint64_t _physics_frames = 0;
@@ -90,6 +91,9 @@ class Engine {
 	virtual void set_target_fps(int p_fps);
 	virtual int get_target_fps() const;
 
+	virtual void set_fixed_fps(int p_fps);
+	virtual int get_fixed_fps() const;
+
 	virtual double get_frames_per_second() const { return _fps; }
 
 	uint64_t get_frames_drawn();
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 892b74c26a71..e02e6d992258 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -2179,6 +2179,14 @@ int Engine::get_target_fps() const {
 	return ::Engine::get_singleton()->get_target_fps();
 }
 
+void Engine::set_fixed_fps(int p_fps) {
+	::Engine::get_singleton()->set_fixed_fps(p_fps);
+}
+
+int Engine::get_fixed_fps() const {
+	return ::Engine::get_singleton()->get_fixed_fps();
+}
+
 double Engine::get_frames_per_second() const {
 	return ::Engine::get_singleton()->get_frames_per_second();
 }
@@ -2307,6 +2315,8 @@ void Engine::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_physics_interpolation_fraction"), &Engine::get_physics_interpolation_fraction);
 	ClassDB::bind_method(D_METHOD("set_target_fps", "target_fps"), &Engine::set_target_fps);
 	ClassDB::bind_method(D_METHOD("get_target_fps"), &Engine::get_target_fps);
+	ClassDB::bind_method(D_METHOD("set_fixed_fps", "fixed_fps"), &Engine::set_fixed_fps);
+	ClassDB::bind_method(D_METHOD("get_fixed_fps"), &Engine::get_fixed_fps);
 
 	ClassDB::bind_method(D_METHOD("set_time_scale", "time_scale"), &Engine::set_time_scale);
 	ClassDB::bind_method(D_METHOD("get_time_scale"), &Engine::get_time_scale);
@@ -2346,6 +2356,7 @@ void Engine::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "print_error_messages"), "set_print_error_messages", "is_printing_error_messages");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_ticks_per_second"), "set_physics_ticks_per_second", "get_physics_ticks_per_second");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "target_fps"), "set_target_fps", "get_target_fps");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps"), "set_fixed_fps", "get_fixed_fps");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_scale"), "set_time_scale", "get_time_scale");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "physics_jitter_fix"), "set_physics_jitter_fix", "get_physics_jitter_fix");
 }
diff --git a/core/core_bind.h b/core/core_bind.h
index 591cacdabb82..a5bc056b47ca 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -636,6 +636,9 @@ class Engine : public Object {
 	void set_target_fps(int p_fps);
 	int get_target_fps() const;
 
+	void set_fixed_fps(int p_fps);
+	int get_fixed_fps() const;
+
 	double get_frames_per_second() const;
 	uint64_t get_physics_frames() const;
 	uint64_t get_process_frames() const;
diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml
index 506992e3af4e..49943ec630bb 100644
--- a/doc/classes/Engine.xml
+++ b/doc/classes/Engine.xml
@@ -199,6 +199,10 @@
 		</method>
 	</methods>
 	<members>
+		<member name="fixed_fps" type="int" setter="set_fixed_fps" getter="get_fixed_fps" default="0">
+			If set to a value greater than [code]0[/code], this forces the engine to simulate as fast as possible while respecting the given FPS as a [code]delta[/code] value passed to [method Node._process] functions. This can be used for non-realtime simulation such as offline video recording, benchmarking or AI training. See also [member target_fps] and [member ProjectSettings.application/run/fixed_fps].
+			Non-realtime simulation can also be enabled using the [code]--fixed-fps &lt;fps&gt;[/code] command line argument.
+		</member>
 		<member name="physics_jitter_fix" type="float" setter="set_physics_jitter_fix" getter="get_physics_jitter_fix" default="0.5">
 			Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of the in-game clock and real clock but smooth out framerate jitters. The default value of 0.5 should be fine for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended.
 			[b]Note:[/b] For best results, when using a custom physics interpolation solution, the physics jitter fix should be disabled by setting [member physics_jitter_fix] to [code]0[/code].
@@ -213,7 +217,7 @@
 			[b]Note:[/b] This property does not impact the editor's Errors tab when running a project from the editor.
 		</member>
 		<member name="target_fps" type="int" setter="set_target_fps" getter="get_target_fps" default="0">
-			The desired frames per second. If the hardware cannot keep up, this setting may not be respected. A value of 0 means no limit. See also [member physics_ticks_per_second] and [member ProjectSettings.debug/settings/fps/force_fps].
+			The desired frames per second. If the hardware cannot keep up, this setting may not be respected. A value of 0 means no limit. See also [member physics_ticks_per_second], [member fixed_fps] and [member ProjectSettings.debug/settings/fps/force_fps].
 		</member>
 		<member name="time_scale" type="float" setter="set_time_scale" getter="get_time_scale" default="1.0">
 			Controls how fast or slow the in-game clock ticks versus the real life one. It defaults to 1.0. A value of 2.0 means the game moves twice as fast as real life, whilst a value of 0.5 means the game moves at half the regular speed.
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index ee32677b3a6a..10172663841c 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -253,6 +253,11 @@
 			If [code]true[/code], disables printing to standard output. This is equivalent to starting the editor or project with the [code]--quiet[/code] command line argument. See also [member application/run/disable_stderr].
 			Changes to this setting will only be applied upon restarting the application.
 		</member>
+		<member name="application/run/fixed_fps" type="int" setter="" getter="" default="0">
+			If set to a value greater than [code]0[/code], this forces the engine to simulate as fast as possible while respecting the given FPS as a [code]delta[/code] value passed to [method Node._process] functions. This can be used for non-realtime simulation such as offline video recording, benchmarking or AI training. See also [member ProjectSettings.debug/settings/fps/force_fps] and [member physics/common/physics_ticks_per_second].
+			Non-realtime simulation can also be enabled using the [code]--fixed-fps &lt;fps&gt;[/code] command line argument.
+			[b]Note:[/b] This property is only read when the project starts. To toggle non-realtime simulation at runtime, set [member Engine.fixed_fps] instead.
+		</member>
 		<member name="application/run/flush_stdout_on_print" type="bool" setter="" getter="" default="false">
 			If [code]true[/code], flushes the standard output stream every time a line is printed. This affects both terminal logging and file logging.
 			When running a project, this setting must be enabled if you want logs to be collected by service managers such as systemd/journalctl. This setting is disabled by default on release builds, since flushing on every printed line will negatively affect performance if lots of lines are printed in a rapid succession. Also, if this setting is enabled, logged files will still be written successfully if the application crashes or is otherwise killed by the user (without being closed "normally").
@@ -434,9 +439,9 @@
 			Message to be displayed before the backtrace when the engine crashes.
 		</member>
 		<member name="debug/settings/fps/force_fps" type="int" setter="" getter="" default="0">
-			Maximum number of frames per second allowed. The actual number of frames per second may still be below this value if the game is lagging.
-			If [member display/window/vsync/vsync_mode] is set to [code]Enabled[/code] or [code]Adaptive[/code], it takes precedence and the forced FPS number cannot exceed the monitor's refresh rate. See also [member physics/common/physics_ticks_per_second].
-			This setting is therefore mostly relevant for lowering the maximum FPS below VSync, e.g. to perform non-real-time rendering of static frames, or test the project under lag conditions.
+			Maximum number of frames per second allowed. The actual number of frames per second may still be below this value if the game is lagging. A value of 0 means no limit.
+			If [member display/window/vsync/vsync_mode] is set to [code]Enabled[/code] or [code]Adaptive[/code], it takes precedence and the forced FPS number cannot exceed the monitor's refresh rate. See also [member physics/common/physics_ticks_per_second] and [member application/run/fixed_fps].
+			This setting is therefore mostly relevant for lowering the maximum FPS below VSync, or test the project under lag conditions.
 			[b]Note:[/b] This property is only read when the project starts. To change the rendering FPS cap at runtime, set [member Engine.target_fps] instead.
 		</member>
 		<member name="debug/settings/gdscript/max_call_stack" type="int" setter="" getter="" default="1024">
@@ -1543,7 +1548,7 @@
 			[b]Note:[/b] This property is only read when the project starts. To change the physics FPS at runtime, set [member Engine.physics_jitter_fix] instead.
 		</member>
 		<member name="physics/common/physics_ticks_per_second" type="int" setter="" getter="" default="60">
-			The number of fixed iterations per second. This controls how often physics simulation and [method Node._physics_process] methods are run. See also [member debug/settings/fps/force_fps].
+			The number of fixed iterations per second. This controls how often physics simulation and [method Node._physics_process] methods are run. See also [member debug/settings/fps/force_fps] and [member application/run/fixed_fps].
 			[b]Note:[/b] This property is only read when the project starts. To change the physics FPS at runtime, set [member Engine.physics_ticks_per_second] instead.
 			[b]Note:[/b] Only 8 physics ticks may be simulated per rendered frame at most. If more than 8 physics ticks have to be simulated per rendered frame to keep up with rendering, the game will appear to slow down (even if [code]delta[/code] is used consistently in physics calculations). Therefore, it is recommended not to increase [member physics/common/physics_ticks_per_second] above 240. Otherwise, the game will slow down when the rendering framerate goes below 30 FPS.
 		</member>
diff --git a/main/main.cpp b/main/main.cpp
index f20ec94fa558..12fcd2d6f0ac 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -175,7 +175,6 @@ static bool debug_navigation = false;
 #endif
 static int frame_delay = 0;
 static bool disable_render_loop = false;
-static int fixed_fps = -1;
 static bool print_fps = false;
 #ifdef TOOLS_ENABLED
 static bool dump_extension_api = false;
@@ -1096,7 +1095,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 			disable_render_loop = true;
 		} else if (I->get() == "--fixed-fps") {
 			if (I->next()) {
-				fixed_fps = I->next()->get().to_int();
+				Engine::get_singleton()->set_fixed_fps(I->next()->get().to_int());
 				N = I->next()->next();
 			} else {
 				OS::get_singleton()->print("Missing fixed-fps argument, aborting.\n");
@@ -1457,6 +1456,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 					"debug/settings/fps/force_fps",
 					PROPERTY_HINT_RANGE, "0,1000,1"));
 
+	Engine::get_singleton()->set_fixed_fps(GLOBAL_DEF("application/run/fixed_fps", 0));
+	ProjectSettings::get_singleton()->set_custom_property_info("application/run/fixed_fps",
+			PropertyInfo(Variant::INT,
+					"application/run/fixed_fps",
+					PROPERTY_HINT_RANGE, "0,1000,1"));
+
 	GLOBAL_DEF("debug/settings/stdout/print_fps", false);
 	GLOBAL_DEF("debug/settings/stdout/print_gpu_profile", false);
 	GLOBAL_DEF("debug/settings/stdout/verbose_stdout", false);
@@ -2640,7 +2645,7 @@ bool Main::iteration() {
 	const uint64_t ticks = OS::get_singleton()->get_ticks_usec();
 	Engine::get_singleton()->_frame_ticks = ticks;
 	main_timer_sync.set_cpu_ticks_usec(ticks);
-	main_timer_sync.set_fixed_fps(fixed_fps);
+	main_timer_sync.set_fixed_fps(Engine::get_singleton()->get_fixed_fps());
 
 	const uint64_t ticks_elapsed = ticks - last_ticks;
 
@@ -2664,7 +2669,7 @@ bool Main::iteration() {
 	last_ticks = ticks;
 
 	static const int max_physics_steps = 8;
-	if (fixed_fps == -1 && advance.physics_steps > max_physics_steps) {
+	if (Engine::get_singleton()->get_fixed_fps() < 1 && advance.physics_steps > max_physics_steps) {
 		process_step -= (advance.physics_steps - max_physics_steps) * physics_step;
 		advance.physics_steps = max_physics_steps;
 	}
@@ -2783,7 +2788,7 @@ bool Main::iteration() {
 		Input::get_singleton()->flush_buffered_events();
 	}
 
-	if (fixed_fps != -1) {
+	if (Engine::get_singleton()->get_fixed_fps() >= 1) {
 		return exit;
 	}
 
diff --git a/main/main_timer_sync.cpp b/main/main_timer_sync.cpp
index 6c0afcad3a48..99027b0ce9bb 100644
--- a/main/main_timer_sync.cpp
+++ b/main/main_timer_sync.cpp
@@ -157,7 +157,7 @@ MainFrameTime MainTimerSync::advance_core(double p_physics_step, int p_physics_t
 
 // calls advance_core, keeps track of deficit it adds to animaption_step, make sure the deficit sum stays close to zero
 MainFrameTime MainTimerSync::advance_checked(double p_physics_step, int p_physics_ticks_per_second, double p_process_step) {
-	if (fixed_fps != -1) {
+	if (fixed_fps >= 1) {
 		p_process_step = 1.0 / fixed_fps;
 	}
 

@Calinou Calinou requested review from a team as code owners April 15, 2022 21:39
@Calinou Calinou added this to the 4.0 milestone Apr 15, 2022
@Calinou Calinou force-pushed the engine-add-fixed-fps-setting branch 3 times, most recently from 0351200 to f9c924b Compare April 15, 2022 23:49
@YuriSizov YuriSizov modified the milestones: 4.0, 4.x Feb 9, 2023
@Calinou Calinou force-pushed the engine-add-fixed-fps-setting branch from f9c924b to be1d95d Compare May 14, 2023 23:22
@Calinou
Copy link
Member Author

Calinou commented May 14, 2023

Rebased and tested again (with updated MRP), it works as expected. I also documented the application/run/fixed_fps project setting's interactions with the editor/movie_writer/fps project setting in the class reference.

@TCROC
Copy link
Contributor

TCROC commented May 29, 2024

This PR has better docs than my PR over here: #92490

I did not realize @Calinou already had a PR in for this or I would've just merged his branch into my fork 😅. Anywho, I think we should use this one instead once @Calinou makes the CI happy :)

@Calinou Calinou force-pushed the engine-add-fixed-fps-setting branch 2 times, most recently from 41af7bd to b78e654 Compare June 3, 2024 22:15
@Calinou
Copy link
Member Author

Calinou commented Jun 3, 2024

Rebased and tested again, it works as expected.

@Calinou Calinou force-pushed the engine-add-fixed-fps-setting branch from b78e654 to dcbe82c Compare June 3, 2024 22:18
Non-realtime simulation can now be enabled at runtime and enabled
without having to use a command line argument.
@Calinou Calinou force-pushed the engine-add-fixed-fps-setting branch from dcbe82c to 9e710d8 Compare June 3, 2024 23:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants