Open jitspoe opened 5 months ago
I once used this pattern in my project:
extends Node
var should_emit: bool
var timer: float
signal lazy_process
func _process(delta: float) -> void:
timer += delta
if should_emit:
lazy_process.emit(timer)
should_emit = false
timer = 0
func _physics_process(delta: float) -> void:
should_emit = true
Called it "lazy process". It allows to write logic in process frame, but synchronized with physics frame. Simply connect to the signal to use it. The timer acts as delta
parameter.
I once used this pattern in my project:
I think this is basically doing the opposite of what I want. This syncs up so process stuff only executes every physics frame. I want physics to execute every process frame.
My 2 cents as a beginner:
1-update-per-frame seems to be simpler to reason about when learning the engine, I'm puzzled thinking about jittering, interpolation and smoothing add-ons instead of just thinking about my "game".
Expanding on your idea:
If you have the option for 1-update-per-frame, what about a lower and upper bound, for minimum and maximum physics FPS regardless of your FPS?
The 1:1 frame-to-update ratio isn't the best for every project, some might want 1:2, 1:4, 1:8, or the other way around, 2:1, 4:1 and 8:1. However, desired ratios usually change based on framerate. To achieve such granular control, I suggest mapping ranges of framerates. Here is an example, picture a lightweight FPS game for high refresh rate: 1.0- 30 FPS
maps to30- 30 ups
(fixed minimum). 2.30-120 FPS
maps to30-120 ups
(1:1 growth ratio). 3.120-240 FPS
maps to120-180 ups
(after 120, start skipping updates to save unnecessary processing). 4.240-INF FPS
maps to180-180 ups
(fixed maximum) Those values are a bit arbitrary, the point is: you have a LOT of control here. Why not just use a curve then? Well, I find curves to be a bit unpredictable :v.
I don't understand this issue 100%, if you want physics to happen at every process frame, can't you just use the _process()
function for that? It will execute code at every process frame, or if you want the physics to happen at every process frame, isn't the matter of adapting the physics frames per second to the maximum refresh rate of the user's monitor?
The only problem I see is the user with a varying framerate without Vsync and you wanting to execute physics per process frame, but then again having the physics frame per second capped to the user's max monitor refresh rate would solve this,
Updating 100+ enemies every physics tick has huge performance issues, especially if physics update multiple times per frame
This to me doesn't make sense, only if your frames per second is < physics framerate, but if perfomance is the issue and you want to update enemies at different intervals, we could optmize by allowing objects to skip processing entirely, this allows you to update them at different intervals inside the physics processing function, something like this:
var update_rate:int = randi_range(1,9)
var update_i:int = update_rate
func _physics_process(delta: float) -> void:
if update_i > 0: update_i -= 1; return;
update_i = update_rate
# Execute Physics or related code.
This should already be achievable by setting Physics Ticks per Second to 1000 and Max Physics Steps per Frame to 1 in the Project Settings (or 2 if you want 2 iterations per frame). However, slowdown will occur if the physics tick rate can't be met, so there would need to be a setting to disable this behavior.
You can set Physics Ticks per Second above 1,000 with a script if needed.
I don't understand this issue 100%, if you want physics to happen at every process frame, can't you just use the
_process()
function for that? It will execute code at every process frame, or if you want the physics to happen at every process frame, isn't the matter of adapting the physics frames per second to the maximum refresh rate of the user's monitor?
That's effectively what I want, but the _physics_process() and _process() happen at different times in the engine update. _process() happens after things like animation, which is useful to do things like post-anim bone corrections and such, but it means if you put your gameplay logic in _process() all the feedback is going to be delayed by an extra frame. I want the equivalent of _process() that gets called when the _physics_process() does. I think there are some other things that are bad to do outside of the physics frame as well, like moving physics bodies and such.
This should already be achievable by setting Physics Ticks per Second to 1000 and Max Physics Steps per Frame to 1 in the Project Settings (or 2 if you want 2 iterations per frame). However, slowdown will occur if the physics tick rate can't be met, so there would need to be a setting to disable this behavior.
This just makes the game run in slow motion. If you don't meet the physics framerate, the game slows down to compensate.
Also, I think it might be good to have a set physics tick rate for things like rigid body simulations, so we have consistency, but the per-frame physics update would be called every frame in addition to that, so you could optionally do additional per-frame calculations and stagger them in your own way.
So something like:
_physics_process(physics_delta)
- called every physics update, same as it is now. Could be called 0 times in a frame, or 8+ times in a frame, depending on settings and circumstances.
_physics_process_every_frame(frame_delta)
- Like _process(), it is called every frame, but while in the physics update stage of the engine. Once per frame every frame.
_process(frame_delta)
- Called every frame after most of the other stuff has happened, (unchanged).
This would allow:
One drawback I can see is that certain things, like move_and_slide() and is_input_just_pressed() are tied specifically to if they're updating in the physics or process updates, and this new function would be ambiguous.
I'm assuming you're looking for something like Unity's jobs system? I feel like having some form of deferred staggered callback or multithreading mechanism would be preferable to adding another process function to Node. I think having both _process
and _physics_process
is already potentially confusing to new users, and I don't like that I'm always having to override all my nodes to use physics
instead of idle
process, because the defaults don't respect fixed timestep.
_physics_process_every_frame(frame_delta)
- Like _process(), it is called every frame, but while in the physics update stage of the engine. Once per frame every frame.
This is a very confusing name. First, what do you mean by frames? Display frames (what Godot calls idle)? That should be made explicit upfront. When I think of game logic frames, I usually think physics frames, not display frames. Second, the whole point of _physics_process
is that it's only called on physics frames. Having a function that starts with _physics_process
but is called on idle dilutes its meaning. If you want something idle that's called before/during physics, it should be called _process_early
or similar.
Aside: if you just want basic staggered updates of objects, you could do that using something like this:
const UPDATE_RATE = 0.1;
# Could vary this across all your objects so they don't all update at once
var update_timer := 0.0;
func _physics_process(delta: float) -> void:
update_timer += delta;
if update_timer >= UPDATE_RATE:
update_timer -= UPDATE_RATE;
do_intensive_thing();
Admittedly retro, but it won't cause unexpected behavior between two machines running at different framerates.
if update_timer >= UPDATE_RATE: update_timer - UPDATE_RATE;
You probably meant update_timer -= UPDATE_RATE
🙂
Aside: if you just want basic staggered updates of objects, you could do that using something like this:
That's what I'm currently doing. The issue is that it doesn't stagger things evenly across rendered frames, only physics frames, and given that some physics can update 0-(8?) times per rendered frame, even if things are staggered within the physics frame, they can be heavily clustered on different render frames.
So say your physics tick rate is set to 60 and you're rendering on a 120hz monitor. Every other frame is going to have a physics update, which, if the physics update rate is slow, could mean you have every other frame is like twice as long (or more), making the game feel stuttery even if the average framerate is 120. When you turn the camera every other frame takes longer to process so it's not a consistent turn speed.
Alternatively, you could drop to sub-60 FPS where some frames are doing 2 physics updates per frame. Being able to distribute the physics load so it's not alternating between 2x and 1x would make the framerate a bit more smooth. I'm not sure what the ideal solution is.
I could just put everything in the _process() function with staggering to distribute the stuff across multiple frames, but that would mean things like character movement would be happening after the physics update and also systems like animation, so animations would be delayed 1 frame from when I told them to play.
Alternatively, maybe we could fix the physics performance so this wouldn't be necessary: https://github.com/godotengine/godot/issues/93184
Describe the project you are working on
Retro FPS with lots of enemies active at once.
Describe the problem or limitation you are having in your project
Updating 100+ enemies every physics tick has huge performance issues, especially if physics update multiple times per frame. See: https://github.com/godotengine/godot/issues/93184
It would be good to stagger the physics updates of things across multiple frames. For example, 5 enemy updates per frame, prioritized by distance, velocity, etc.
The problem is, the physics update at a set rate, which means if I have the physics updating at 60 hz and the user is running at 120hz, they'll only update enemies every other frame, and if players are running at 30fps, they'll be doing 10 enemy updates per frame instead of 5, which further hurts performance on a machine that's already struggling.
Right now the _physics_process() stuff is all or nothing. Either everything updates every physics update, or nothing does.
Describe the feature / enhancement and how it helps to overcome the problem or limitation
Having a guaranteed physics update (or an update while the engine is in physics mode, _in_physics = true) will allow me to do staggered updates in a controlled manner such that the workload for large numbers of enemies is spread across multiple frames.
Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
What I want is an (optional) physics update call that happens every frame. This could possibly be in addition to the regular _physics_process() or an option to make _physics_process() run every frame instead of at a fixed rate.
So currently we have something in the main loop like:
What I'm proposing is something like:
This would add a new function that executed every frame prior to animations and such that could optionally be used instead of the _physics_process on scripts where I want to control things at varying rates.
Or:
This would add an option to allow the physics to update every single frame at whatever the framerate is at.
This is very similar to the semi-fixed timestep proposal: https://github.com/godotengine/godot-proposals/issues/236
It's also similar to the late physics process: https://github.com/godotengine/godot-proposals/issues/6795
And relevant to the swarm/mob proposal: https://github.com/godotengine/godot-proposals/issues/2380
And the different physics update rates at the same time: https://github.com/godotengine/godot-proposals/issues/439
Edit: Also found this issue which probably more clearly states the issues but was in the main repo, not the proposals, so it got closed: https://github.com/godotengine/godot/issues/24769
So maybe it's not entirely necessary as a separate proposal, but I do want to have some way to guarantee an update while the engine is in physics mode every frame. The semi-fixed one seems like it would do 2 physics updates per frame, but maybe if the physics tick rate was set to 20hz or something, that would mean only one update per frame. Likely resolving one of these proposals would also address a handful of others.
If this enhancement will not be used often, can it be worked around with a few lines of script?
It's possible an autoload could handle this in _process(), but I'm not sure if that is the appropriate place to do things like move enemies around, and ideally this would happen before the animations play so there's not a delay in feedback. Could be possible, though.
Is there a reason why this should be core and not an add-on in the asset library?
This needs to be in the core loop of the engine in order to do per-frame updates in the physics.