-
Notifications
You must be signed in to change notification settings - Fork 27
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
Drastically optimize pause menu elements #300
base: master
Are you sure you want to change the base?
Conversation
Always the first step.
_process runs nothing but this function, and it's not used elsewhere. No need to separate it.
WHOA. Just by doing this, I drop the total process time for this script from ~11 ms to ~4.5 ms. I gotta keep this going!
For use later in refactoring button name fetches.
This drops processing time from ~4.5 ms down to ~0.25 ms. This puts it level with Entity._physics_process in T1R1. Frankly a bit shocking that an options menu should even be that intensive, though. The script already has machinery to mostly operate without _process(). Looking into how to take that to 100% so we can skip that function entirely.
...instead of proactively running update_list() for every rebind option, every frame. This allows us to completely remove the _process() function from rebind_options. Script functions now profile as sub-1 ms on my machine (~0.8).
If we're already caching other input translations, might as well finish the job. Should be zero redundant translation calls now, apart from when translations changed (and I know how to fix that).
Accomplished by statically caching the name of the current locale, only updating the strings if the cache is out of date, and updating the cached locale as soon as the first rebind option finishes (so the others don't see it).
Perhaps it's not taking 11 milliseconds, but this script's process function was up there with water and entities last I checked--which, again, is pretty outrageous for a static pause menu element! Now it only updates when the language changes, not once every frame. While I'm here, refactor the script to potentially support the fancy font in other languages someday. That's really just using a match statement instead of an if, though.... (and commenting it)
Bit of "why use a separate function when a comment will do" here. Also, updating for-loop variable names to accurately describe what's actually contained in them (as far as we care). And commenting, of course.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally all good, just some crashes that need resolving.
output = output.trim_suffix(", ") | ||
return output | ||
|
||
key_list.text = output |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The game crashes here for me. "Invalid assignment of property or key 'text' with value of type 'String' on a base object of type 'Nil'."
I believe this happens because update_list()
is called before @onready
can trigger, when the translation change notification is received. I'm not sure why this didn't occur while you were working on it, perhaps Godot updates, or perhaps something to do with my own language settings? (Apologies for the belated response to your PR.)
refresh_caps() | ||
func _notification(what): | ||
if what == NOTIFICATION_TRANSLATION_CHANGED: | ||
match TranslationServer.get_locale().substr(0, 2): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A similar issue occurs here, with a similar solution. (See my other comments.)
match TranslationServer.get_locale().substr(0, 2): | |
if level_name == null: | |
await ready | |
match TranslationServer.get_locale().substr(0, 2): |
# already done that). | ||
if locale_current != TranslationServer.get_locale().substr(0, 2): | ||
_update_translations() | ||
# Update the list. | ||
update_list() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The aforementioned crash can be resolved like so:
update_list() | |
if key_list == null: | |
await ready | |
update_list() |
Description of changes
On profiling the normal gameplay loop, it was discovered that several pause menu elements had startlingly high performance costs. This PR optimizes them down to reasonable levels.
The bigger offender was the function
_get_joypad_buttons
inrebind_option.gd
, which was profiled as using 11(!) ms of CPU time in each frame. This function returns an array of arrays of button names, indexed by logical button index and by controller brand. On inspection, it was found that this function was called once for every binding of every action, every frame, and doing the same 25 string translations every time (which suggests 25 disk reads). Assuming one binding for every action--which I know is lowballing it--that's25 * 25 = 625
total translations per frame, literally 98% of which are repeated work.Optimizing the script thus becomes a matter of reusing work. This can be done:
_get_joypad_buttons
. Because some buttons share names between brands, some translated strings can be cached before constructing the return array, which skips 15 extra translations per function call. This step alone reduced CPU time from 11ms to 4ms._get_joypad_buttons
doesn't depend on which action or mapping it's used for--only the selected locale. By storing the result in a static variable, it can be reused between instances, and the disk reads(?) involved in translation lookup can be skipped for all but the first user._get_joypad_buttons
returns the same result as last frame.rebind_option
already has machinery to receive the engine's locale-change notification, so it's fairly trivial to only set_get_joypad_buttons
when the game starts or the locale changes. Combined with the previous step, this reduced CPU time from 4ms to ~0.3ms, about level withEntity._physics_process
andDejitterGroup._physics_process
.rebind_option
's state doesn't change between frames. By adding (and connecting to) a signal to the reset-mappings button, it becomes possible to completely remove_process
and instead update rebind options reactively. Naturally, this meansrebind_option
is no longer charting at all on most profiler frames.Some similar string-caching-via-statics work was also done to
get_joypad_motion_name
. This is mostly for methodological consistency, but if somebody were to make very strange mapping choices ("wassup YouTube, today I'm gonna be playing SM63R using just the left and right analog stick!"), I could see it saving actual performance.I also chose to optimize
level_info.gd
. This was much less of a problem script, but it still seemed appropriate to switch it from detecting locale via_process
to detecting via engine notification, especially sincerebind_option
had shown me how to do that. This saves about 0.15ms--not much, but still seemed excessive for a static menu element.Some mild refactors have also been sprinkled in, including obligatory reordering functions to standard and commenting everything I touch.