Concept for an input library - thoughts?

Started by
30 comments, last by haegarr 9 years, 1 month ago

I'm not sure; can you define custom operators for strongly-typed enums?

Advertisement
Spiro, your points are valid, but I believe you're quite wrong about this not being something that can be solved for most use cases. A well-designed game, as I understand it, is generally implemented as a complex hierarchical state machine, and the state of the machine is affected by signals it receives. I'm not sure how familiar you are with reactive programming, but input is just one stage of the combinatorial process.

Now, you are quite right that the input received is not universally applicable in the simplistic way that most new game designers are envisioning. Physics gets in the way (such as with your example whereby the space bar means jump, but not if they're already in the air). I think the reason you're having a conceptual problem with this is that you're not decoupling these concerns well enough.

With my library, the input sequencer is itself a hierarchical state machine, with each input sequence itself being a simple state machine. You still need to treat a sequencer as a child state machine in the larger state machine of your game! Each individual sequencer that you instantiate would be related to its own domain of concern, with its own group of mapped input sequences, and can itself be set to an "off" state, so that it doesn't trigger sequence-matched events when it's not relevant to do so.

Conceptually, think of it like this. Raw input maps to unified input, which is directed into my [proposed] library. You then use my library to build groups (my sequencer class) of domain-specific intents, where a domain is something like "main menu input", "inventory screen input", etc. and the intent is something like "Player Jumped" or "Menu Item Selected". The library maps the unified source input events to whatever intent group is active at the time. Those groups then fire events when any of their mapped input sequences are matched, and you then project that resultant event to a game-specific intent, which you can then filter using your own preferred model, whether directly, or via additional events, streams, queues or whatever.

In a nutshell, if you're having problems with universality of input because you're thinking that state matters, then your concerns don't have enough separation. Validating intent is not a concern of the input library.

You’re focusing too much on what I would have considered the smallest point I was trying to make.

Translating actions into commands such as “jump” was a child point of the fact that looking for key combinations (auto observer = input::observe("ctrl+a,ctrl+b,ctrl+c", 0, 1000);) at the level where you library would sit.

You still have the exact same problem I mentioned in the link I already posted (or a variation of it based on how you handle what I discussed about “jump” commands).

First, as I have explained in many posts, and lightly touched here, input is to be buffered and then handled later (on a second thread even).

So if you perform key-combination checking at the wrong time, you could recognize an action whose context is no longer contextually contextual that will be out of context by the time the game processes it.

Your suggestion to swap out several combinations of key handlers (an “intent group” as you put it) depending on the game context (main menu, game, etc.) doesn’t fix it.

I posted the link twice in order to reduce how much I have to repeat myself. It should be enough now to simply point out that you still have the exact same problem and then let you read the post already-written.

L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

I posted the link twice in order to reduce how much I have to repeat myself. It should be enough now to simply point out that you still have the exact same problem and then let you read the post already-written.


You seem to be irritated by the (I believe false) perception that I haven't understood the point you're trying to make. The last sentence of what I wrote was the most pertinent. A combination of keys still maps to an intent, irrespective of whether that intent becomes invalid before it has a chance to be processed. The point is that your game should process intents, not keys, at the point where the intent is to be translated to a game state change. The conditions under which validation is asserted are, as you clearly keep pointing out, unique to a given game engine, but the original intent itself doesn't change. It still up to you to validate the intent, and if you're applying a log processing concept as per your other examples, then what is logged is the chronological set of intents, not the set of keys. The area of code dealing with the outcome of the intent should not be dealing with the keys related to that intent. If it is, your code is too tightly coupled.

In your example, you gave the example of remapping 'K' to 'A' and that needing to be immediately reflected, even if it happens right in the middle of a subsecond succession of bot-initiated keypresses. In my system, it is immediately reflected, because you feed the raw input event in at the top end, and it flows out the bottom as an intent, and that intent *was* perfectly valid at the precise moment that the input sequence produced that intent. If you're asserting that perhaps the key will be remapped mid-sequence, then that's splitting hairs. The process leading to the remapping would cause the key-sequence state machine to be reset anyway.

Second, you talk about timing, which is again a valid concern, but it still doesn't invalidate my solution! Irrespective of timing, input sequences and combinations still translate to an intent, otherwise the player wouldn't have pressed that combination of keys to begin with. If it turns out that they were 100ms too late, then your processing of the intent at the point where the intent is to be processed is where you would validate that intent. Even if you completely took away the obvious feature of being able to disable and re-enable an input sequencer (to avoid redundant processing of inputs), your validation on the end of your intent queue should still be correctly validating the next intent in the queue in the context of the game state as it is during that exact slice of time.

Perhaps you're pointing out a subtlety that I have missed, but consider this. The library is intended to be inserted into your main game loop, and nothing happens in your game without your game going through that game loop in sequential cycles. I know you've been around the block here, so forgive any blindness surrounding this issue on my part, but basic input processing doesn't strike me as something so resource intensive that it can't be included as part of your primary game loop. Therefore, even at the very start of your game loop, the game will be in a precise state, which is still reflected when the intent is emitted by an input sequencer, as it will be part of the same cycle, before the input library call completes. What you do at that point is up to you. If you are dealing with a parallel system, then again, it is up to you to validate that intent in the context of your game's state at the precise moment that the intent is processed, even if you relegated input processing to a secondary thread. However my point is this:

Device inputs map to intents. That's why we have input, because the player is trying to effect a certain outcome using their knowledge of the input device's (current) mapping to your game's systems. The point of the library is to translate intents from input signals to a language that your game understands. If an input sequence is invalidated midway through reaching a "completion" state, no problem, it's reset. If the intent that is added to the queue turns out to be invalid at the point it is to be processed due to 100ms of latency, again, no problem - you validate that intent before you process it. The fact that it wasn't valid at the precise moment it was to be applied doesn't change the fact that the player intended a given effect as a result of that input sequence. It's up to you, the game designer, to validate the intent before it is applied.

If you still disagree with me, the present me with a use case you think I can't handle, and I'll do my best to explain why I don't think it's a problem, and if I can't, then I will happily yield the argument to you.

You seem to be irritated

I’m not. That’s just the result the Internet has on things said as matter-of-fact.

I will edit the rest of my reply into this post later, as I need to go in a few minutes.


In the meantime, why don’t you explain where exactly your library will live within an engine?
Does it catch WM_KEYDOWN messages directly (does it live in the message loop?) or where?


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

In the meantime, why don’t you explain where exactly your library will live within an engine?
Does it catch WM_KEYDOWN messages directly (does it live in the message loop?) or where?


This has already been covered. (To summarise, no, the core of my library would not process platform-specific messages directly.) Have a quick read back through my responses to other people's replies, as I explain this in more detail. Also, please do read through my last post and try to understand what I'm trying to convey, as it would be easy for both of us to go back and forth and not be quite on the same wavelength, and waste a bunch of time.

I'm not sure; can you define custom operators for strongly-typed enums?


Absolutely. Not the kinds that must be member functions, of course, but you can overload all the rest. A useful trick is to use a macro or a operator templates and a type trait to allow for a bitfield-like enum to support the |, &, ~, ^, and corresponding assignment operators.

Sean Middleditch – Game Systems Engineer – Join my team!

Why strings? The keys themselves have nothing to do with strings.

Cross-platform'd enums would make more sense to me.
std::vector = {Key::Ctrl|Key::A, Key::Ctrl|Key::B, Key::Ctrl|Key::C};
It doesn't look as nice in code, but most of the time, it wouldn't be hardcoded anyway.


I'm a big fan of code that is easy to read and scan at a glance. The overhead of parsing a string would be minimal, and a one-time cost anyway. As a result, you get nice, succinct code that is immediately obvious without having its readability compromised by syntactic pollution. You get to represent complex combinations in a short amount of code, e.g. "ctrl+k, ctrl+shift+alt+q" or "a,a,b,c,space".


This is an overly simplistic response to this, but while your idea here is clever, it can result is unexpected errors. If I type "Key::Crtl" instead of "Key::Ctrl", using Servant of the Lord's method, the compiler will immediately catch it. However, if it is buried in data (a string) rather that defined in code, it can be missed and possibly only after it is too late. At best, it will make tracking down bugs more difficult. I can only speak for myself, but I frequently transpose letters and numbers and can easily see me typing "crtl" instead of "ctrl" and have difficulty finding it.

Another issue is with player-definable key combinations. I would have to use a string concatenation function in place of something simple like "player_controls = left_input | right_input | fire_input | whatever;".

Other than that, you are going to have to have your parser parse the string every time the function is called. Personally, I'd choose a switch statement over a parser any day. These functions will be called inside the game loop. (misread the initial post) Timing is critical and a parser can be slow, especially if you do necessary error checks.

Overall, it is an over-complication to a simple problem.

Thanks for your input (pun not intended).

This is an overly simplistic response to this, but while your idea here is clever, it can result is unexpected errors. If I type "Key::Crtl" instead of "Key::Ctrl", using Servant of the Lord's method, the compiler will immediately catch it. However, if it is buried in data (a string) rather that defined in code, it can be missed and possibly only after it is too late. At best, it will make tracking down bugs more difficult. I can only speak for myself, but I frequently transpose letters and numbers and can easily see me typing "crtl" instead of "ctrl" and have difficulty finding it.


Each identifier would be registered. Use of an invalid identifier would yield an error immediately, so this is not going to be an issue.

Another issue is with player-definable key combinations. I would have to use a string concatenation function in place of something simple like "player_controls = left_input | right_input | fire_input | whatever;".


There will be strongly-typed overloads. Use of strings to define input sequences will be optional, so if it's not your cup of tea, you don't have to drink it.

Other than that, you are going to have to have your parser parse the string every time the function is called. Personally, I'd choose a switch statement over a parser any day. These functions will be called inside the game loop. Timing is critical and a parser can be slow, especially if you do necessary error checks.


That would be a terrible way to implement it! String parsing will only occur on first registration.

Overall, it is an over-complication to a simple problem.


As with L.Spiro's comments, I'm not sure you fully understood what I'm proposing. The whole point of this is that it's lightweight and very much not an overcomplication. A number of the commenters' suggestions lead very quickly towards overcomplication, but that doesn't mean I'll be following suit.

I'm going to go ahead and implement this. A picture [of code] speaks a thousand words. This is neither complicated, a performance problem, or something which is not applicable to many scenarios.

Thanks for your input (pun not intended).

This is an overly simplistic response to this, but while your idea here is clever, it can result is unexpected errors. If I type "Key::Crtl" instead of "Key::Ctrl", using Servant of the Lord's method, the compiler will immediately catch it. However, if it is buried in data (a string) rather that defined in code, it can be missed and possibly only after it is too late. At best, it will make tracking down bugs more difficult. I can only speak for myself, but I frequently transpose letters and numbers and can easily see me typing "crtl" instead of "ctrl" and have difficulty finding it.


Each identifier would be registered. Use of an invalid identifier would yield an error immediately, so this is not going to be an issue.


That would work.

Thanks for your input (pun not intended).

Another issue is with player-definable key combinations. I would have to use a string concatenation function in place of something simple like "player_controls = left_input | right_input | fire_input | whatever;".


There will be strongly-typed overloads. Use of strings to define input sequences will be optional, so if it's not your cup of tea, you don't have to drink it.

Overall, it is an over-complication to a simple problem.


As with L.Spiro's comments, I'm not sure you fully understood what I'm proposing. The whole point of this is that it's lightweight and very much not an overcomplication. A number of the commenters' suggestions lead very quickly towards overcomplication, but that doesn't mean I'll be following suit.


I think these two replies go together. You are correct. I possibly do not understand what you're proposing.

Other than that, you are going to have to have your parser parse the string every time the function is called. Personally, I'd choose a switch statement over a parser any day. These functions will be called inside the game loop. Timing is critical and a parser can be slow, especially if you do necessary error checks.


That would be a terrible way to implement it! String parsing will only occur on first registration.


That's why I edited the comment and struck it out. I realized after posting that I was mentioning a set up function. Still, I wouldn't use a parser, but that's just me.

This topic is closed to new replies.

Advertisement