Bug: TorqueEventManager.SilenceEvents
by Frederic · in Torque X 2D · 09/26/2007 (10:15 am) · 4 replies
I get an exception using the following code:
First, register for keyboard events using
TorqueEventManager.ListenEvents(TorqueInputDevice.KeyboardEvent, KeybEventHandler);
On receiving a quit key via this keyboard event call unregister using
TorqueEventManager.SilenceEvents(TorqueInputDevice.KeyboardEvent, KeybEventHandler);
The call returns without an exception, but immediately after that I get a NullReferenceException.
The stack trace contains the following:
GarageGames.Torque.Core.TorqueEvent'1._Trigger(Delegate d)
GarageGames.Torque.Core.TorqueEventManager._TriggerEvent(TorqueEventBase ev)
GarageGames.Torque.Core.TorqueEventManager.MgrProcessEvents()
GarageGames.Torque.Core.TorqueEventManager.ProcessEvents()
GarageGames.Torque.XNA.TorqueEngineComponent.Update(GameTime gameTime)
Microsoft.Xna.Framework.Game.Update(GameTime gameTime)
Microsoft.Xna.Framework.Game.Tick()
Doing the same with the mouse works fine, so my first guess was that silencing the events might change some data structure that is currently being used in the event call since I am setting some keyboard input mappings at other places in the code.
First, register for keyboard events using
TorqueEventManager.ListenEvents(TorqueInputDevice.KeyboardEvent, KeybEventHandler);
On receiving a quit key via this keyboard event call unregister using
TorqueEventManager.SilenceEvents(TorqueInputDevice.KeyboardEvent, KeybEventHandler);
The call returns without an exception, but immediately after that I get a NullReferenceException.
The stack trace contains the following:
GarageGames.Torque.Core.TorqueEvent'1._Trigger(Delegate d)
GarageGames.Torque.Core.TorqueEventManager._TriggerEvent(TorqueEventBase ev)
GarageGames.Torque.Core.TorqueEventManager.MgrProcessEvents()
GarageGames.Torque.Core.TorqueEventManager.ProcessEvents()
GarageGames.Torque.XNA.TorqueEngineComponent.Update(GameTime gameTime)
Microsoft.Xna.Framework.Game.Update(GameTime gameTime)
Microsoft.Xna.Framework.Game.Tick()
Doing the same with the mouse works fine, so my first guess was that silencing the events might change some data structure that is currently being used in the event call since I am setting some keyboard input mappings at other places in the code.
#2
The reason I am not using input maps is that I am listening to a lot of keys in a chat function. Using input maps to bind these keys to a method would be nice, but the called method only gets to know whether the key triggering the event is pressed or released (via the val parameter of the ActionDelegate), but not which key triggers the event. So I would have to create a dedicated method for every key on the keyboard when using input maps.
I believe that I can avoid the exception I posted by silencing the events in the *Tick method, so my code should be working again. I just wanted to post the problem since it looked like a bug and might affect somebody else, too.
09/27/2007 (2:54 am)
Thank you very much for your detailed answer.The reason I am not using input maps is that I am listening to a lot of keys in a chat function. Using input maps to bind these keys to a method would be nice, but the called method only gets to know whether the key triggering the event is pressed or released (via the val parameter of the ActionDelegate), but not which key triggers the event. So I would have to create a dedicated method for every key on the keyboard when using input maps.
I believe that I can avoid the exception I posted by silencing the events in the *Tick method, so my code should be working again. I just wanted to post the problem since it looked like a bug and might affect somebody else, too.
#3
Out of curiosity, are you saying that calling silence in a Tick method fixes the issue? If so, where were you calling silence from before? Any extra clues to repro and track this down would be helfpul.
09/27/2007 (2:57 pm)
Yes, this does sound like the delegate list is not getting cleared out properly and that (or a flaw in the logic) might be what's causing the null reference exception (the delegate is the only possible culprit, it's a short method). I'll definitely look into this. Thanks for the detailed info. Out of curiosity, are you saying that calling silence in a Tick method fixes the issue? If so, where were you calling silence from before? Any extra clues to repro and track this down would be helfpul.
#4
Yes, the issue is fixed by silencing everywhere except in the method registered to listen to keyboard events. Silencing in a tick method or even in a method registered via InputMap.BindCommand works fine.
Interestingly, silencing even works fine when done in the mouse event listening method.
Finally, the exception only occurs when listening for both mouse and keyboard events.
Now the code to reproduce the exception:
Using the StarterGame starter kit:
in Game.cs:
in BeginRun: add
TorqueEventManager.ListenEvents(TorqueInputDevice.MouseEvent, mouseEventHandler);
TorqueEventManager.ListenEvents(TorqueInputDevice.KeyboardEvent, keybEventHandler);
before SceneLoader.Load.
Additionally, add the following methods to Game.cs:
public void stopInputEvents()
{
TorqueEventManager.SilenceEvents(TorqueInputDevice.MouseEvent, mouseEventHandler);
TorqueEventManager.SilenceEvents(TorqueInputDevice.KeyboardEvent, keybEventHandler);
}
private void keybEventHandler(string eventname, TorqueInputDevice.InputEventData ie)
{
if (((Microsoft.Xna.Framework.Input.Keys)ie.ObjectId ==
Microsoft.Xna.Framework.Input.Keys.Escape)
&& (ie.Value == 0))
{
this.stopInputEvents();
}
}
private void mouseEventHandler(string eventname, TorqueInputDevice.InputEventData ie)
{
if ((ie.ObjectId == (int)XMouseDevice.MouseObjects.RightButton)
&& (ie.EventAction == TorqueInputDevice.Action.Break))
{
this.stopInputEvents();
}
}
The exception occurs when pressing the Escape key.
As described above, pressing the right mouse button silences the events without any problems.
10/05/2007 (9:42 am)
I finally managed to get a minimal set of code to reproduce the exception.Yes, the issue is fixed by silencing everywhere except in the method registered to listen to keyboard events. Silencing in a tick method or even in a method registered via InputMap.BindCommand works fine.
Interestingly, silencing even works fine when done in the mouse event listening method.
Finally, the exception only occurs when listening for both mouse and keyboard events.
Now the code to reproduce the exception:
Using the StarterGame starter kit:
in Game.cs:
in BeginRun: add
TorqueEventManager.ListenEvents(TorqueInputDevice.MouseEvent, mouseEventHandler);
TorqueEventManager.ListenEvents(TorqueInputDevice.KeyboardEvent, keybEventHandler);
before SceneLoader.Load.
Additionally, add the following methods to Game.cs:
public void stopInputEvents()
{
TorqueEventManager.SilenceEvents(TorqueInputDevice.MouseEvent, mouseEventHandler);
TorqueEventManager.SilenceEvents(TorqueInputDevice.KeyboardEvent, keybEventHandler);
}
private void keybEventHandler(string eventname, TorqueInputDevice.InputEventData ie)
{
if (((Microsoft.Xna.Framework.Input.Keys)ie.ObjectId ==
Microsoft.Xna.Framework.Input.Keys.Escape)
&& (ie.Value == 0))
{
this.stopInputEvents();
}
}
private void mouseEventHandler(string eventname, TorqueInputDevice.InputEventData ie)
{
if ((ie.ObjectId == (int)XMouseDevice.MouseObjects.RightButton)
&& (ie.EventAction == TorqueInputDevice.Action.Break))
{
this.stopInputEvents();
}
}
The exception occurs when pressing the Escape key.
As described above, pressing the right mouse button silences the events without any problems.
Torque Owner Thomas Buscaglia
Here's an example of binding input to a player taken from MovementComponent in the StarterGame project:
// player is the owner of the MovementComponent // playerIndex 0 in the default level (set by the PlayerIndex on MovementComponent) 0-3 are supported // gamePad is "gamepad" // keyboard is "keyboard" private void _SetupInputMap(TorqueObject player, int playerIndex, String gamePad, String keyboard) { // Set player as the controllable object PlayerManager.Instance.GetPlayer(playerIndex).ControlObject = player; // Get input map for this player and configure it InputMap inputMap = PlayerManager.Instance.GetPlayer(playerIndex).InputMap; int gamepadId = InputManager.Instance.FindDevice(gamePad); if (gamepadId >= 0) { inputMap.BindMove(gamepadId, (int)XGamePadDevice.GamePadObjects.LeftThumbX, MoveMapTypes.StickAnalogHorizontal, 0); inputMap.BindMove(gamepadId, (int)XGamePadDevice.GamePadObjects.LeftThumbY, MoveMapTypes.StickAnalogVertical, 0); inputMap.BindAction(gamepadId, (int)XGamePadDevice.GamePadObjects.Back, _OnBackButton); } // keyboard controls int keyboardId = InputManager.Instance.FindDevice(keyboard); if (keyboardId >= 0) { inputMap.BindMove(keyboardId, (int)Keys.Right, MoveMapTypes.StickDigitalRight, 0); inputMap.BindMove(keyboardId, (int)Keys.Left, MoveMapTypes.StickDigitalLeft, 0); inputMap.BindMove(keyboardId, (int)Keys.Up, MoveMapTypes.StickDigitalUp, 0); inputMap.BindMove(keyboardId, (int)Keys.Down, MoveMapTypes.StickDigitalDown, 0); // WASD inputMap.BindMove(keyboardId, (int)Keys.D, MoveMapTypes.StickDigitalRight, 0); inputMap.BindMove(keyboardId, (int)Keys.A, MoveMapTypes.StickDigitalLeft, 0); inputMap.BindMove(keyboardId, (int)Keys.W, MoveMapTypes.StickDigitalUp, 0); inputMap.BindMove(keyboardId, (int)Keys.S, MoveMapTypes.StickDigitalDown, 0); } }Whatever the current control object is get's all the input states assigned via BindMove passed to it's ProcessTick and InterpolateTick methods in the Move structure. BindAction and BindCommand work similar, but they just call whatever delegate is specified when the input event happens.
If you want to bind global input to certain events, you would use the global input map. Here's an example of that:
int keyboardId = InputManager.Instance.FindDevice("keyboard"); InputMap.Global.BindCommand(keyboardId, (int)Microsoft.Xna.Framework.Input.Keys.Escape, ToggleMenu, null);And here's an example of what a delegate for the above sample would look like:
public void ToggleMenu() { Console.WriteLine("TOGGLE MENU"); }Let me know if this doesn't answer your question.