Discombobulated input callbacks

Started by
3 comments, last by Zipster 8 years, 5 months ago

I have made an input system based kind of on this:

http://www.gamedev.net/blog/355/entry-2250186-designing-a-robust-input-handling-system-for-games/

I have my raw input -> context map -> actions.

I have a dispatcher / subject / observerable (whatever) that will do the notifying when actions are triggered.

Implemented very roughly as:


vector<*listeners>

I have a listener which registers to the dispatcher to handle certain actions.

It is implemented roughly as


vector<*dispatchers> /// to automatically unregister on destruction
map<actions, std::functions>  /// function to call given an action, bound to *this with std::bind

I would like to place this listener as a member of any game object which should do something with input.


class Camera
{
Camera();
InputListener m_listener;
}

Camera::Camera()
{
dispatcher.register(m_listener, event::CAM_MOVE_LEFT);  /// probably occurs someplace else, really.
m_listener.addCallback(event::CAM_MOVE_LEFT, std::function<void()>(std::bind(Camera::moveLeft, this)));
}

Problem #1

I had to use std::bind to bring along the *this so that the callback can call the member function. This isn't a problem unto itself until...

...The parent object is copied.

The callbacks which are stored in the listener are now pointing to the pre-copied-object.

This means that any object which decides to have an inputListener as a member must implement a non-trivial copy constructor to rebind all the callbacks with the proper *this.

Kinda stinks of poor design.

I could make the member static, but then wouldn't *all* cameras move left when this callback occurs?

I'm overcomplicating this somehow.

Advertisement

Is there a reason you can't register the camera function as a listener directly? I.e.


dispatcher.register(std::function<void()>(std::bind(Camera::moveLeft, this)), event::CAM_MOVE_LEFT);

The parent object is copied.
The callbacks which are stored in the listener are now pointing to the pre-copied-object


Don't allow the object to be copied or moved. These kinds of delegate-like callback systems kind of rely on having immovable objects.

You _could_ solve this with an additional layer of indirection, but you shouldn't.

The other option is to just not use the delegate-like callback model. I find they work especially poorly for input anyway once you get to non-trivial games and game UIs (e.g., layering of menus, interrupted inputs, loss of window focus, etc.) as gameplay often has to deal with interaction models that are very very different from the traditional desktop models where delegates work well.

Sean Middleditch – Game Systems Engineer – Join my team!

Is there a reason you can't register the camera function as a listener directly? I.e.


dispatcher.register(std::function<void()>(std::bind(Camera::moveLeft, this)), event::CAM_MOVE_LEFT);

I don't know. What happens when the camera dies? How does the dispatcher know to unregister that dangly pointer? Seems like this just moves that responsibility to the camera object, which then needs to be reimplemented for every game object that registers functions to the dispatcher. Unless I am misunderstanding.

Don't allow the object to be copied or moved. These kinds of delegate-like callback systems kind of rely on having immovable objects.

How do you handle storing the objects (like the camera) in a container (std::vector or similar). Doesn't that require a copy?

The other option is to just not use the delegate-like callback model. I find they work especially poorly for input anyway once you get to non-trivial games and game UIs (e.g., layering of menus, interrupted inputs, loss of window focus, etc.) as gameplay often has to deal with interaction models that are very very different from the traditional desktop models where delegates work well.

What is the alternative?

Are you saying that the entire Input->MapToActionsWithAContext->ActOnThoseActions model doesn't work well?
Or are you saying that the final step of resolving the action via callback does not work well?

How else does the game object act upon the action event?

Thanks to both of you.

I don't know. What happens when the camera dies? How does the dispatcher know to unregister that dangly pointer? Seems like this just moves that responsibility to the camera object, which then needs to be reimplemented for every game object that registers functions to the dispatcher. Unless I am misunderstanding.

My example was a little hasty, you can't actually unregister the function if you use that exact code. The approach would look more like:


class Camera
{
   Camera() : m_callback(std::bind(Camera::moveLeft, this))
   {
      dispatcher.register(&m_callback, event::CAM_MOVE_LEFT);
   }

   ~Camera()
   {
      dispatcher.unregister(&m_callback, event::CAM_MOVE_LEFT);
   }

   void moveLeft() { ... }

   std::function<void()> m_callback;
};

You'll get a C4355 warning, which is safe to ignore in this particular instance (and there are ways around it)... but none of it is very pretty. Note the aforementioned level of indirection.

I personally use fast delegates whenever I'm dealing with delegates in C++. They just make this kind of code a lot easier to work with than std::function.

How do you handle storing the objects (like the camera) in a container (std::vector or similar). Doesn't that require a copy?

std::vector<std::unique_ptr<Camera>>

What is the alternative?
Are you saying that the entire Input->MapToActionsWithAContext->ActOnThoseActions model doesn't work well?
Or are you saying that the final step of resolving the action via callback does not work well?
How else does the game object act upon the action event?

I've never had any issue using delegates to handle input actions. They're just the final step where you associate the triggering of an action to some code you want to execute. The input contexts are what provide the layer of indirection needed to implement your interaction model.

This topic is closed to new replies.

Advertisement