When do event handlers get registered after loading a save game?

Ask about ACS, DECORATE, ZScript, or any other scripting questions here!

Moderator: GZDoom Developers

Forum rules
Before asking on how to use a ZDoom feature, read the ZDoom wiki first. If you still don't understand how to use a feature, then ask here.

Please bear in mind that the people helping you do not automatically know how much you know. You may be asked to upload your project file to look at. Don't be afraid to ask questions about what things mean, but also please be patient with the people trying to help you. (And helpers, please be patient with the person you're trying to help!)
Magdiel012
Posts: 10
Joined: Wed Dec 21, 2022 10:59 am
Operating System Version (Optional): Windows 11
Graphics Processor: nVidia with Vulkan support

When do event handlers get registered after loading a save game?

Post by Magdiel012 »

So I'm working on a system to dynamically add and remove HUD elements from the screen, where these elements are managed by an event handler. The setup is a bit too intricate to detail here, but for the most part it works fine: I can have weapons add their HUDs to the handler on select and remove them on deselect. However, I'm having some issues with one very specific edge case. When reloading a save, calling EventHandler.Find() from the first frame of a weapon's Select state returns null, but it doesn't happen if at least one of the player's held weapons doesn't call the method in this first frame. I'm thinking this has something to do with the order that the engine initializes all these things in, but manually making sure this gets called after at least one tic has passed isn't quite as intuitive as I'd like it to be (this is part of a ZScript library I'm working on, I'd like for it to be as user-friendly as possible). I thought about maybe making the handler query the objects that would like to add extensions instead, but I'd like for any object to be able to add extensions on demand, and doing this from the handler doesn't seem feasible.

So is there any way to defer the logic that adds the extension until after the handler is registered, or am I out of luck?

Also yeah, do handlers get registered after weapons enter their Select state? If so, why doesn't this fail when starting a new game or entering a new map?
User avatar
Player701
 
 
Posts: 1551
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support

Re: When do event handlers get registered after loading a save game?

Post by Player701 »

Magdiel012 wrote: Mon Dec 26, 2022 2:10 pmWhen reloading a save, calling EventHandler.Find() from the first frame of a weapon's Select state returns null, but it doesn't happen if at least one of the player's held weapons doesn't call the method in this first frame.
I could not reproduce this, could you please provide a minimal example?
Magdiel012 wrote: Mon Dec 26, 2022 2:10 pm I can have weapons add their HUDs to the handler on select and remove them on deselect.
Note that by default, UI cannot be directly affected by code in play scope (which includes weapons). As far as I understand, you're using an event handler to work around this. I'd generally advise against that because the limitation is there for a reason. For example, in multiplayer co-op it is possible to press F12 (default binding) to watch the game through another player's eyes. In this case, the HUD will reflect the state of the chosen player instead of the console player, which is not going to work properly with your architecture if you expect weapons to always be switched via select/deselect states. And what about functions like TakeInventory/ClearInventory, which also bypass the deselect state? All these problems magically go away if you do it the other way around - that is, make your UI code read from the game state. Doing it in reverse is almost always bad design, please don't do that.
Magdiel012
Posts: 10
Joined: Wed Dec 21, 2022 10:59 am
Operating System Version (Optional): Windows 11
Graphics Processor: nVidia with Vulkan support

Re: When do event handlers get registered after loading a save game?

Post by Magdiel012 »

Player701 wrote: Wed Dec 28, 2022 12:54 am
Magdiel012 wrote: Mon Dec 26, 2022 2:10 pmWhen reloading a save, calling EventHandler.Find() from the first frame of a weapon's Select state returns null, but it doesn't happen if at least one of the player's held weapons doesn't call the method in this first frame.
I could not reproduce this, could you please provide a minimal example?
My current codebase has gotten a tad complex. It could take a while to isolate the issue, but I'll see what I can do.

EDIT: I've managed to reproduce it in a test project, but it turns out it only happens when you reload your save from dying, not from the menu or through the hotkeys. I've also attached the repro project.
Player701 wrote: Wed Dec 28, 2022 12:54 am
Magdiel012 wrote: Mon Dec 26, 2022 2:10 pm I can have weapons add their HUDs to the handler on select and remove them on deselect.

Note that by default, UI cannot be directly affected by code in play scope (which includes weapons). As far as I understand, you're using an event handler to work around this. I'd generally advise against that because the limitation is there for a reason. For example, in multiplayer co-op it is possible to press F12 (default binding) to watch the game through another player's eyes. In this case, the HUD will reflect the state of the chosen player instead of the console player, which is not going to work properly with your architecture if you expect weapons to always be switched via select/deselect states. And what about functions like TakeInventory/ClearInventory, which also bypass the deselect state? All these problems magically go away if you do it the other way around - that is, make your UI code read from the game state. Doing it in reverse is almost always bad design, please don't do that.
I've thought about this as well, but haven't found another workaround that suits my current design goals for the UI, which are primarily:
  1. For it to be able to respond to any arbitrary changes in context, whether it's each weapon needing a distinct custom ammo indicator, other inventory items displaying their own custom indicators on demand, enemies and bosses displaying unique health bars or other mechanics-related indicators, etc.
  2. For it to respond to specific events. That is to say, when something happens in game that the UI is meant to respond to, it should respond to it once and only once.
The first goal can probably be brute-forced by making the UI code itself aware of every possible change in game state it should account for, but I fear the prospect of having to write the massive, omniscient status bar class it's going to require that'll be a nightmare to debug. The second goal, however, is especially tricky because the UI may read from the game state an arbitrary number of times within a single game tic. Simply reading a flag that is set for a single tic wouldn't work because at, say, 70 FPS, the flag will be read twice per tic. I've thought about tracking event occurrence from within the UI using timestamps, but haven't been able to come up with an implementation I'm satisfied with. Also, putting in specific flags into play-scoped code meant only for the UI to read feels like it defeats the purpose of separating the play and UI scopes to begin with. I'm at a bit of a loss here.
You do not have the required permissions to view the files attached to this post.
Last edited by Magdiel012 on Wed Dec 28, 2022 12:42 pm, edited 3 times in total.
User avatar
Player701
 
 
Posts: 1551
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support

Re: When do event handlers get registered after loading a save game?

Post by Player701 »

Magdiel012 wrote: Wed Dec 28, 2022 10:49 amI've managed to reproduce it in a test project, but it turns out it only happens when you reload your save from dying, not from the menu or through the hotkeys. I've also attached the repro project.
Considering that it only happens when you reload after dying and not otherwise, I'd recommend to report this inconsistent behavior as a bug.
Magdiel012 wrote: Wed Dec 28, 2022 10:49 amThe first goal can probably be brute-forced by making the UI code itself aware of every possible change in game state it should account for, but I fear the prospect of having to write the massive, omniscient status bar class it's going to require that'll be a nightmare to debug.
When I said "read from the game state", I didn't mean "write a massive, omniscient status bar class". You can still make use of object-oriented programming practices such as polymorphism, composition and encapsulation to achieve the desired result.

Simple example: suppose you want your HUD's ammo counter to change colors depending on ammo types. Unless you're writing a HUD for vanilla Doom, you don't need to put a string of ifs or a switch statement in your HUD code to enumerate every single ammo type there is. Instead, you can create an abstract base ammo class (let's call it ColoredAmmo) and add a virtual method there (let's call it GetHUDColor). Then, in your HUD code, when accessing the current weapon's ammo, simply check if its type is ColoredAmmo, and if it is, call GetHUDColor to return the color, otherwise use a default color. In this particular case, unless the color of the same ammo type can change (e.g. depending on the amount of ammo left), you can even do away with the method and use a field instead (bound to a property in the Default block for easy assignment in subclasses). But it is entirely possible to use such an approach for something much more complex, e.g. return some sort of object that represents an entire fragment of your UI and encapsulates the logic of how it should be displayed on the screen (to avoid recreating it each time, it can be cached in an UI-only variable). The statur bar code will then have to iterate through the player's inventory to detect everything that can return these objects and call the respective methods to display their contents. It will of course be somewhat trickier than it sounds - e.g. you have to ensure that multiple elements do not occupy the same space on the screen and such, but it is doable.
Magdiel012 wrote: Wed Dec 28, 2022 10:49 amThe second goal, however, is especially tricky because the UI may read from the game state an arbitrary number of times within a single game tic. Simply reading a flag that is set for a single tic wouldn't work because at, say, 70 FPS, the flag will be read twice per tic. I've thought about tracking event occurrence from within the UI using timestamps, but haven't been able to come up with an implementation I'm satisfied with. I'm at a bit of a loss here.
I probably wouldn't use flags for this purpose at all, but it's hard to say without any specific scenario in mind.
Magdiel012 wrote: Wed Dec 28, 2022 10:49 amAlso, putting in specific flags into play-scoped code meant only for the UI to read feels like it defeats the purpose of separating the play and UI scopes to begin with.
It depends on what exactly you intend to use these flags for, but in general, having a read-only method, field or flag in play-scoped code for use in UI scope in perfectly reasonable. See above for examples.
Magdiel012
Posts: 10
Joined: Wed Dec 21, 2022 10:59 am
Operating System Version (Optional): Windows 11
Graphics Processor: nVidia with Vulkan support

Re: When do event handlers get registered after loading a save game?

Post by Magdiel012 »

Player701 wrote: Thu Dec 29, 2022 1:01 amIt depends on what exactly you intend to use these flags for, but in general, having a read-only method, field or flag in play-scoped code for use in UI scope in perfectly reasonable. See above for examples.
I see. I was under the impression that this was considered bad practice, my mistake.
Player701 wrote: Thu Dec 29, 2022 1:01 amit is entirely possible to use such an approach for something much more complex, e.g. return some sort of object that represents an entire fragment of your UI and encapsulates the logic of how it should be displayed on the screen (to avoid recreating it each time, it can be cached in an UI-only variable). The statur bar code will then have to iterate through the player's inventory to detect everything that can return these objects and call the respective methods to display their contents. It will of course be somewhat trickier than it sounds - e.g. you have to ensure that multiple elements do not occupy the same space on the screen and such, but it is doable.
I do actually like this approach (it's actually not too far from what I'm already doing). My only concern is the status bar needing to know about every possible type with custom HUD logic. Ultimately it seems like I have no better choice, but I really wish ZScript had interfaces so any type could implement the required logic and the status bar could recognize it abstractly. Abstract classes come close to this, but even then the status bar will still need to know all of the base types that could implement custom HUD logic.
User avatar
Player701
 
 
Posts: 1551
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support

Re: When do event handlers get registered after loading a save game?

Post by Player701 »

Magdiel012 wrote: Mon Jan 02, 2023 11:54 amAbstract classes come close to this, but even then the status bar will still need to know all of the base types that could implement custom HUD logic.
Yeah, but that is only because GZDoom does not have built-in ZScript APIs to implement such an architecture, so you have to build your own instead - there's no way around this.

Return to “Scripting”