A video demo of my Windows Message Loop processing the WM_KEYDOWN events. I start by showing off single button presses, then do button presses "at the same time," mash the keyboard, and then finally try to press 8 buttons at once multiple times in a row. As many times as I've tested it, I haven't had any reason to believe it's missing any inputs. Beyond that: it feels absolutely mercurial.
I've heard this phrase a few times, and while I mostly agree with it, you have to at least know what you're cutting first.
I started this week by doing research on how I actually wanted to implement my Input System. I have prior experience using GetAsyncKeyState for Windows, but I wanted to know: are there other ways, what way is faster, what are the pros and cons of each approach?
I found a few alternatives, with varying levels of depth: DirectInput, RawInput, and the Windows Message Loop (though, to my understanding, RawInput is a particular HID-device registration whose data is yoinked from the Windows Message Loop?)
While I couldn't immediately see downsides to GetAsyncKeyState, I easily found that DirectInput has been considered deprecated for a long time. Further, I either have no idea what I'm looking at, or it seems to be in my best interest to use WM_KEYDOWN rather than parsing RAWKEYBOARD from WM_INPUT (the RawInput approach). So, at least for Keyboard and basic button inputs, it was either GetAsyncKeyState or WM_KEYDOWN.
A common argument against GetKeyState or GetAsyncKeyState is that a keypress could be missed due to your update loop occasionally (for us, probably quite rarely) missing inputs due to the update loop not querying at the right moment. My issue originally was that polling the keys every frame would be excessive, even if the difference is nearly meaningless on a larger scale.
However, because I want to do everything in my power to not drop any inputs, it made perfect sense for me to settle with the Windows Message Loop.
I would like to avoid inputs being lost when the process stutters, but I'll soon find out whether or not that is possible.
Bonus note: Because the Console apparently doesn't receive these Events normally, I needed to create a Blank Application Window to catch them. It's been quite the learning experience.
Now that I had a good idea of how keypresses would actually be handled under the hood, I had to refer back to my proposal and begin deciding how I wanted to build my Interface.
This will become relevant shortly, but - even with my best technique, I can't get past 12 keypresses per second.
In researching this, I learned about strange semi-exploitative behaviors such as "drag-clicking" - not only am I not at all concerned about Mouse Input right now, but I also have no interest in supporting keypress speeds to that degree.
I want to store as little data as possible, but I want the data I store to represent everything my Input System needs.
I started out with two booleans: isPressed and wasPressed. Using this data on a per-frame basis might give me everything I need. I drew it out, though, and realized that there's a similar issue to GetAsyncKeyState if my buffer of keys is updating with an unlucky offset relative to the user of the Interface.
While I would have needed logic for the KeyUp event anyway, storing the number of KeyUps per-frame allows me to infer things that would have been ambiguous:
Was the key pressed between frames
How many frames is the key being held down for
How many times is the key pressed per-frame (this might help with detecting autoclicker tools!)
This design made me realize I might be able to get rid of isPressed entirely, so I shifted my approach.
Yes, I seriously entertained the idea of "16 presses per-frame" for a while. This is where the key-mashing came in and I realized that was... a bit much.
I decided that storing per-frame KeyUp and KeyDown counts should allow me to get all of the information I require.
So that cut the core data structure down to 1/4th of its original size!
I decided that, aside from this rate of button presses already being absurd, I'm not building my system with the intention that it be able to withstand the utmost edge cases of rapid-button-mashing at sub-24 FPS averages. Sorry guys. (It'd be trivial to change this later, however.)
Also, storing button press data for 600 frames is probably overkill as well. But that was the edge case I chose, and it's a nice number. 10 seconds at 60 FPS.
At this point, I have a pretty good idea of how I want to store this data, how I want to manipulate this data, and how I want users to Interface with my system.
But, I also considered: What if users would like an easily-interfaced-with Wrapper for the WM_KEY events? (Including other features I'd like my System to support, of course) They might want to store and manipulate this information in a way I haven't considered.
I think I would like to support two separate Input approaches: one with memory, and one without. I will allow the User to choose which fits their needs best. I'll start with one and go from there.
I forgot that racing games exist, where I have been known to hold down the Forward button for as long as my finger muscles will allow. Every key may very well just require the isDown boolean, just in case. My prior idea can only work for as many frames as are stored in memory, and unfortunately that could create disruptive behavior.
This probably makes my Key Data struct look like 3 bits, now. Perhaps a fourth bit for padding, to make cache access a bit smoother. Maybe I'll come across a reason for the fourth bit down the line.
But now I'm quite confident that I can extend my Keypress Demo into a solid system.
Found out that I won't be doing anything fancy beneat 1-byte alignment. So I either can store two keypress frames in one struct, or find more information to store.