Game Development Community

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.

#1
09/26/2007 (1:36 pm)
The preferred method to bind input is using input maps. You can bind input to a specific player, or bind generic things like menu shortcuts and stuff like that using the global input map.

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.
#2
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
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
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.