So, I’ve been tacking input in my latest coding endeavors and I’ve run into… I guess a snag? But not in the sense that I don’t know ‘how’ to accomplish my goal, but rather which method will yield good long-term, extensible results. Here’s some context on how my system currently works(skip reading this if you want):
I’ve implemented a system where raw inputs(from keyboard, mouse and gamepad) are translated into a ‘sequence’, which is just a string of characters (LCtrl+A, RTrigger+X, Shift+LeftClick, etc). These simple strings are then matched against a lookup table to determine what ‘Command’ they are mapped to. A command is also just a simple string which denotes some input device-agnostic action, like ‘MoveLeft’ or ‘Jump’ or ‘Copy/Paste’ or whatever. This way any combination of keyboard, mouse or gamepad inputs can be used to create a hotkey for any command, including modifier keys like shift, or gamepad triggers and the like. They can also be remapped by a user, if needed.
So, this lets me do something like, say, in my GUI, I have a menu of buttons, and I want to move the focus from button1 to button2. I have a command for this, “MenuUp”, which could be accomplished by pressing any number of sequences… Shift+Tab, UpArrow, DPadUp, whatever. The point is that all those sequences are mapped to ‘MenuUp’, so I now have some kind of input device independence in my system.
So, great… But here’s the part I’m less sure about…
There are two ways (I can personally) think of to handle these input commands. Through an event system, or through some sort of polling. Events seem like a sure bet, as they’re commonly used for handling input-related issues… but here’s my hang up with them…
They are fired regardless of the current ‘state’ of the subscribing object. A trivial example of where this might be undesirable is if my ‘PlayerCharacter’ object is subscribed to an array of player commands like Jump, Attack, etc… But then I bring up a menu. I’m now navigating the menu, but, of course, my command events don’t care that the PlayerCharacter object is no longer receiving Updates through my state manager, and that it shouldn’t be processing input events of any kind. Yet it will continue to receive events for Attack, Jump and the like if the menu commands are mapped to those same sequences of inputs. So, okay, just ensure that all sequences must be uniquely mapped to a single command, right? But OTOH, it’s perfectly reasonable that WASD could control my character AND also be mapped to a camera-pan in my editor, or that right arrow navigates a menu but also moves a character, etc. So some hotkey sequences can’t be unique across the entire app, only domains of the app(menus, game screen, editor, etc).
One solution would be unsubscribing and resubscribing to events every time an object goes idle… But that feels REALLY clunky. Also, I don’t like that an object has to even be aware that the state manager isn’t updating to it. Not great.
I see really only one good way around this… and that’s to have some kind of ‘IsActive’ bool on any object that’s subscribed to events. It would be publicly settable. A simple bool check to make sure the object is active before processing any events.
But that also means that I’d have to track every object which receives events, and make sure it’s only set IsActive when it should be, and turned off when it shouldn’t. This can be ameliorated somewhat through recursive calls if my class structure is tree-like, I guess. This isn’t the end of the world, but it feels a bit clunky (maybe that’s just the way it goes?)
The second option was polling. Have some kind of a ‘pool’ of all commands events fired on a given frame, and every interested object, during its Update(), goes through the pool and finds any commands it’s interested in reacting to.
This does of course mean that every currently active and updating object is now doing an O(n) operation every frame on that pool. Also not the end of the world unless there are many hundreds of objects being updated…
Anyways, that’s the situation. I’m just curious if there’s some obvious answer to this issue that I’m missing, or if it’s just a case of nutting up and dealing with it.
Anyways sorry for the ramble. Any input is appreciated.