Event handler called twice per frame [solved]

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
Post Reply
denton
Posts: 3
Joined: Thu Aug 30, 2007 2:02 am

Event handler called twice per frame [solved]

Post by denton »

Hello All,

Whenever I add non-interactive GUIElements to my scene it ends up calling my EventReceiver twice per frame. I'm wondering what The Right Way to handle this situation is.

Here's a full report:

My event handler returns "false" on Key/Mouse events. That's necessary because if I have a GUI up, I want the events to pass to it. Below we see that UserReceiver must return false before GUIEnvironment is polled (and there are numerous threads about this etc.) :

Code: Select all

//! send the event to the right receiver
void CIrrDeviceStub::postEventFromUser(SEvent event)
{
	bool absorbed = false;

	if (UserReceiver)
		absorbed = UserReceiver->OnEvent(event);

	if (!absorbed && GUIEnvironment)
		absorbed = GUIEnvironment->postEventFromUser(event);

	scene::ISceneManager* inputReceiver = InputReceivingSceneManager;
	if (!inputReceiver)
		inputReceiver = SceneManager;

	if (!absorbed && inputReceiver)
		absorbed = inputReceiver->postEventFromUser(event);
}
However, at the moment, the only GUI element in my app is an IGUIStaticText*. So the following happens on a mouse click:

0. My handler processes the Event and returns false. As seen above, execution moves to CGUIEnvironment::postEventFromUser().

1. CGUIEnvironment::postEventFromUser(), seeing the mouse event, searches for a "Hovered" object, finds the IGUIStaticText as a "Hovered" object, and calls

2. IGUIStaticText->onEvent() with the event my handler just processed. IGUIStaticText, having no virtual override for OnEvent(), calls IGUIElement::onEvent():

Code: Select all

//! Called if an event happened.
virtual bool IGUIElement::OnEvent(SEvent event)
{
	if (Parent)
		Parent->OnEvent(event);
	return true;
}
Now this is misleading because _every_ IGUIElement is issued a parent: if you pass NULL as the parent to the ctor, your parent is set to the IGUIEnvironment instance.

3. Therefore, this calls IGUIEnvironment::onEvent():

Code: Select all

//! called by ui if an event happened.
bool CGUIEnvironment::OnEvent(SEvent event)
{
	if (UserReceiver && event.GUIEvent.Caller != this)
		return UserReceiver->OnEvent(event);

	return false;
}
Wait- that just called my handler on the same event again. So the event was processed twice for this frame. And what led to the event being repeated? Just adding a StaticText object to the display. That doesn't seem like correct behavior to me. Of course, I can get around this by adding hacks to my EventReceiver, but I'm curious as to what the The Right Way to solve the problem is. A workaround was proposed 2 years ago but never discussed: http://irrlicht.sourceforge.net/phpBB2/ ... hp?t=10042

Thanks for helping,
denton
Last edited by denton on Tue Sep 25, 2007 4:32 pm, edited 1 time in total.
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

I think that the post you link to is discussing a different problem than you are. The bug is that CGUIEnvironment::OnEvent() checks event.GUIEvent.Caller without first checking to see if the event is actually a gui event. This is bad because it could result in false positives and messages would not get sent to the user event receiver when they should. Unfortunately, the fix proposed is not good...

Code: Select all

bool CGUIEnvironment::OnEvent(SEvent event) 
{ 
   if (event.EventType == EET_GUI_EVENT && UserReceiver && event.GUIEvent.Caller != this) 
      return UserReceiver->OnEvent(event); 

   return true; 
} 
The problem is that only gui events from non-root gui elements would get sent to the user event receiver. Other message types would be lost forever [i.e. you couldn't send a user event from a custom gui element to the user event receiver by calling Parent->OnEvent()]. I don't necessarily think that is a bad thing, but it is an issue. I think they should need to call device->postEventFromUser() instead, but that is just me.

I think this might be better to fix the problem mentioned above, but it doesn't solve the problem that you are having.

Code: Select all

bool CGUIEnvironment::OnEvent(SEvent event) 
{ 
   // let event through if it is not a gui event, or it is a gui event that
   // isn't from ourselves.
   if (UserReceiver && (event.EventType != EET_GUI_EVENT || event.GUIEvent.Caller != this))
      return UserReceiver->OnEvent(event); 

   return true; 
} 
That said, there is a workaround that you can apply to your event receiver that should allow you to avoid duplicate handling of the same event message. It does require that you use more recent versions of Irrlicht. Specifically, the event handling functions need to take the event parameter by reference. This won't work otherwise.

Code: Select all

bool MyEventReceiver::OnEvent(const SEvent& event)
{
  // could be made a regular member variable
  // note: don't ever call a function on this, you can only compare the
  // pointed to event against the address of the current event. any other
  // use is likely to cause stack corruption and a crash.
  static const SEvent* prev = 0;

  if (&event == prev)
    return false; // we're already handling this event

  const SEvent* save = prev;
  prev = &event;

  bool result = _OnEvent(event); // handle the event for real

  prev = save;

  return result;
}
This code should prevent the same message from being processed twice. I moved the OnEvent() logic to another function to simplify things and to avoid accidentally forgetting to restore the cached previous event pointer.

Travis
denton
Posts: 3
Joined: Thu Aug 30, 2007 2:02 am

Post by denton »

vitek, thanks for looking into this. You're right about the problems with the old workaround. As for the EventReceiver, I don't think pointer comparisons are the way to go here because in the linux device, all SEvents are stored as local variables on the stack, so there could be cases where two different events held the same memory location and were thrown out inadvertently. But I think I figured it out this morning (stumbled on the function when a seach went wrong in emacs :<), so sorry for wasting your time...

The UserReceiver for GUIEnvironment() is separate from the standard IrrlichtDevice UserReciever. Thus, you can build two EventReceivers, a "GUI EventReceiver" and a "Non-GUI EventReciever" and register the "GUI EventReciever" with

Code: Select all

getGUIEnvironment()->setUserEventReceiver(er);
Then, any repeated messages end up in the "GUI EventReceiver", where they're ignored (or at least you have the context to know what they mean). I think that's what the original designers had in mind when they came up with this system, because, from that point of view, having a repeated message that corresponds to the "Root GUI Element" actually makes sense.

If you only have one EventReceiver, used for both, as I naively assumed you did, then the system doesn't make sense because messages from both systems end up at the same location without any context to distinguish between them.

I put this in my application and everything worked as expected. Thanks for helping!

denton
Post Reply