Decoupled input

Started by
7 comments, last by MGB 19 years, 9 months ago
OK, now I've decoupled my physics from my rendering (at last), this also had the unfortunate side-effect of decoupling my input processing from working correctly. Various forum searches have touched on the problem but not really a cure (e.g. the famous flipcode loop post says input processing may be a problem ;)). One method springs to mind quickly; split the input processing into two parts: - Time-dependant input (vehicle movement, firing etc). Process with physics. May be unresponsive with larger time steps. - Time-independant input (GUI/mouse/cursor etc.) Process outside of physics loop. Anyone with experiences with this problem / other approaches?
Advertisement
I'm designing my engine with decoupling in mind. To store input for processing, I'm using message queues and device wrappers for the mouse and keyboard. I poll the devices once their timers expire. If the device state has changed, eg: buttons pressed, etc. then I create a new message detailing the state change and insert the message into the message queue. The message is picked up by an object that can handle it, then it is deleted.
The programming was reletively easy. :)
Best of Luck.
Ah - I have a DirectInput abstraction wrapper, but was hoping to give message queues a miss for input!
I suppose if I want to e.g. detect the mouse being pressed and released quickly then I'll need a queue or similar.. :-/
So your objects poke around in the input queue themselves eh?
I modeled two functions roughly off of win32's PeekMessage and Dispatch message. My PeekMessage takes a pointer to the the current TASK class and a reference to a message, and some other option stuff, and returns true if a valid message can be found. My DispatchMessage takes a pointer to the current class and the message that was referenced in PeekMessage. Dispatchmessage simply calls the Procedure method for the TASK class that was passed.
void DispatchMessage(TASK_SERVICE* task, MESSAGE& msg){    /*Call EngineProc for the task*/    task->EngineProc(msg);}

It works well, or well enough. I do have to use some forward class declarations. The objects dont necessarily poke around in the input queue because they have to go through PeekMessage to get a message. Just like any win32 app would have to do. :)
An incredibly powerful way to model input is by using a Publisher\Subscriber design. If you've read Design Patterns, it's called Observer.

Pretty much what happens is this: The input system retrieves the new input for the frame and then for each input event the input system broadcasts a message which describes it. This almost requires that the underlying input API supports buffered input.

---
Following is a brief (but detailed!) discussion of how I implemented this.

In my engine the input is divided into three groups, keyboard, mouse, and joystick. For each of those device types I declared an interface which has virtual functions for each of the types of events that the devices can trigger. For instance, IKeyboardSubscriber has HandleKeyUp() and HandleKeyDown(); IMouseSubscriber has HandleMouseMove(), HandleMouseButtonUp(), HandleMouseButtonDown(). Joysticks would have many more, like movement, rotation, buttons, etc.

Now, each of those functions takes a single structure which contains the information about the event. The structure for HandleMouseMove() would have the axis (X, Y, Z) and then the amount. (This is called the "Push" version of the Observer pattern, as opposed to the "Pull" version.)

If an object wanted to do something in response to input, it inherits from the appropriate interface class and then implements the desired callbacks. Then on construction, it would add itself to the input system and specify the type of event it wants to receive. On destruction, it would notify the input system and it would be removed (this should be automated in the interface).

Something worth noting is that it is important to have the subscriber interfaces be the "Access point" for the input system. This way the global penetration of the input system singleton is significantly reduced.

Another level to this, which would be necessary for a mature "engine," would be to provide an abstraction layer on top of input in the form of actions. But, rather than tripling the length of the post, I'll leave that discussion for another time.
---

*edit
To respond directly to your initial question, though, this input processing should be executed entirely before physics resolution. If the physics code is done correctly, meaning it accounts for the motion paths of the objects over the last frame, this will work perfectly! I believe Real Time Rendering describes a technique for doing this.

Hope that helps :D
- Jason Citron- Programmer, Stormfront Studios- www.stormfront.com
aaron: sound nice and efficient.

clash: Good points :) I do use push and pull observers in my GUI and other places - I actually thought about using them for input before but decided it would be too big a refactoring!! ;).
I currently do buffered key/mouse events and also keyboard polling. I think I'll have to go totally event-driven by the looks of things. Spose it'll make serialising input easier too.
I have a very thin layer on top of that: the input is converted into messages e.g. EMsgControlLeft and sent to the current object HandleMessage(), but that worked as part of the physics loop and updated the object state immediately (tut tut?).

Would doing the input separate to the physics require 'amortisation' over the timestep? Extreme example: 2 mouse clicks occur before the physics loop is even entered..?!

Cheers,
Aph

*I've been looking for an excuse to get Realtime Rendering for a while now... thought there may be a new edition coming soon though :)
If you consider that a game is running between 30fps to 60fps, any input that occurs during one frame is essentially insantaneous. However, considering that you are using buffered input, you will still react to ALL input events, so long as they don't overflow your input buffer. Pressing the mouse four times before the physics but after the input will just cause those input events to be handled in the next frame, which is expected anyway.

The trick is to exclusively use buffered input. That way you don't "miss" input events. Polling should be supported for those moments when you need to do a quick 'n dirty test, but not production code. Interestingly enough, I've found that using the Observer pattern in this situation simplifies testing and prototyping as well!

:D
- Jason Citron- Programmer, Stormfront Studios- www.stormfront.com
I have an input class that has different variables to control sampling frequency, repeat rates, and the like. This leaves it unbound from frame rates or whatever.

There are no actions hard-coded to the various keyboard keys. Every action that can be performed is configured in an XML file, with the exception of mouse input which is "mostly" hard coded, because, well, everyone is going to want the mouse cursor to move when they move the mouse. For the mouse, you simply configure what actually moves when you move it, what happens when you click a mouse button, what happens when you release said button, and what happens when you scroll. This is all done via a simple scripting engine that gives you control over various engine components.

For example, you can set "F1" to enable wireframe mode, "Shift + F1" to disable wireframe mode, "Left click" to call "Interface.ClickObjectAt(x,y)" and so on and so forth.

---------------------------Hello, and Welcome to some arbitrary temporal location in the space-time continuum.

Etnu: good plan making it independent.

How do you all handle mouse double-clicks?
I'm thinking the only way is to process before queueing, and not queue a click until it is definitely known to not be a double-click (wait a certain time)...?

[ed] Hmm no, that aint how e.g. windows works. I suppose it'll be down to the receiver of the clicks to determine if it was double-clicked, which would involve time-stamps and everything! Argh!

This topic is closed to new replies.

Advertisement