Overflow exception when having key combos overlap regular key bindings

Issue: Getting this:

12:58:22,542 ERROR [LegacyApplication] Uncaught exception thrown in Thread[jME3 Main,5,main]
java.lang.StackOverflowError
	at java.util.HashMap.hash(HashMap.java:338) ~[?:1.8.0_111]
	at java.util.HashMap.remove(HashMap.java:798) ~[?:1.8.0_111]
	at java.util.HashSet.remove(HashSet.java:235) ~[?:1.8.0_111]
	at com.simsilica.lemur.input.InputMapper.deactivate(InputMapper.java:423) ~[lemur-1.9.1.jar:?]
	at com.simsilica.lemur.input.InputMapper$StateGroupIndex.refresh(InputMapper.java:757) ~[lemur-1.9.1.jar:?]
	at com.simsilica.lemur.input.InputMapper$StateGroupIndex.refresh(InputMapper.java:764) ~[lemur-1.9.1.jar:?]
	at com.simsilica.lemur.input.InputMapper$StateGroupIndex.refresh(InputMapper.java:764) ~[lemur-1.9.1.jar:?]
	at com.simsilica.lemur.input.InputMapper$StateGroupIndex.refresh(InputMapper.java:764) ~[lemur-1.9.1.jar:?]
	at com.simsilica.lemur.input.InputMapper$StateGroupIndex.refresh(InputMapper.java:764) ~[lemur-1.9.1.jar:?]
	at com.simsilica.lemur.input.InputMapper$StateGroupIndex.refresh(InputMapper.java:764) ~[lemur-1.9.1.jar:?]
	at com.simsilica.lemur.input.InputMapper$StateGroupIndex.refresh(InputMapper.java:764) ~[lemur-1.9.1.jar:?]

When I press the TAB key, to shoot my bombs. The stacktrace continues like that for 100s of lines.

Suspected cause: Having both a binding for TAB, and one for SHIFT+TAB, like so:

public class PlayerMovementFunctions {

    public static final String G_MOVEMENT = "Movement";

    public static final FunctionId F_BOMB = new FunctionId(G_MOVEMENT, "Bomb");
    public static final FunctionId F_MINE = new FunctionId(G_MOVEMENT, "Mine");

    public static void initializeDefaultMappings(InputMapper inputMapper) {

        if (!inputMapper.hasMappings(F_BOMB)) {
            inputMapper.map(F_BOMB, KeyInput.KEY_TAB);
        }
        
        if (!inputMapper.hasMappings(F_MINE)) {
            inputMapper.map(F_MINE, KeyInput.KEY_LSHIFT, KeyInput.KEY_TAB);
        }
    }
}

Tests: I’ve tried removing the binding from the addStateListener and removeStateListener:

inputMapper.addStateListener(this,
                PlayerMovementFunctions.F_BOMB,
                PlayerMovementFunctions.F_SHOOT,
                PlayerMovementFunctions.F_MOUSELEFTCLICK,
                PlayerMovementFunctions.F_MOUSERIGHTCLICK,
                PlayerMovementFunctions.F_GRAVBOMB,
                PlayerMovementFunctions.F_REPEL);
                //PlayerMovementFunctions.F_MINE);

And it works again. Am I doing something wrong with the key combo approach?

1 Like

Try flipping those around so that it’s TAB + SHIFT. Order is important in how it resolves combos against single keys.

It shouldn’t error like it does, though.

1 Like

Thanks. It doesn’t throw the error now, but I think my StateFunctionListener may be wrong - since I check on value == InputState.Positive it triggers an action as soon as one of the buttons is pressed down - which doesn’t work for combos, since one key is perhaps pressed before the other.

@Override
public void valueChanged(FunctionId func, InputState value, double tpf) {
    if (value == InputState.Positive) {
        if (func == PlayerMovementFunctions.F_SHOOT) {
            session.attack(ProjectileTypes.BULLET);
        } else if (func == PlayerMovementFunctions.F_BOMB) {
            session.attack(ProjectileTypes.BOMB);
        } else if (func == PlayerMovementFunctions.F_GRAVBOMB) {
            session.attack(ProjectileTypes.GRAVITYBOMB);
        } else if (func == PlayerMovementFunctions.F_REPEL) {
            session.attack(ProjectileTypes.REPEL);
        } else if (func == PlayerMovementFunctions.F_MINE) {
            session.attack(ProjectileTypes.MINE);
        }
    }
}

I’ll work on this and see if I can come up with a sensible solution

1 Like

Yeah, I think that’s true. If you activate something on the down then you’ll end up with both activated. At least, I don’t think I automatically deactivate the other one.

1 Like

I dont really understand the order of the mappings/bindings, because I have other bindings depending on the other key (3+ bindings that overlap so to speak with both key combos and single keys).

1 Like

It depends on the order the keys are passed to the mapping call for sure. That much I know 100%. I just can’t remember off the top of my head which is the proper order. I thought it was shared keys must go first… but that could be backwards.

It’s definitely consistent, whichever way it is. And when I get it wrong, I just never see the events. I don’t get that weird error you got.

1 Like

Yea, but I could have scenarios where both keys in a key combo is shared with other key combos. But I guess that doesn’t really matter as long as I put the shared keys first.

Thanks.

1 Like

I tried with InputState.Off, to make it fire when I release the keys. Now it seems to map to one binding alone, but the wrong one. I try with SHIFT+TAB, but it maps to just SHIFT.

These are my mappings:

    // Default key mappings
    if (!inputMapper.hasMappings(F_TURN)) {
        inputMapper.map(F_TURN, KeyInput.KEY_A);
        inputMapper.map(F_TURN, InputState.Negative, KeyInput.KEY_D);

    }

    if (!inputMapper.hasMappings(F_THRUST)) {
        inputMapper.map(F_THRUST, KeyInput.KEY_W);
        inputMapper.map(F_THRUST, InputState.Negative, KeyInput.KEY_S);
    }

    if (!inputMapper.hasMappings(F_SHOOT)) {
        inputMapper.map(F_SHOOT, KeyInput.KEY_LCONTROL);
    }

    if (!inputMapper.hasMappings(F_BOMB)) {
        inputMapper.map(F_BOMB, KeyInput.KEY_TAB);
    }

    if (!inputMapper.hasMappings(F_STOP)) {
        inputMapper.map(F_STOP, KeyInput.KEY_SPACE);
    }
    
    if (!inputMapper.hasMappings(F_MOUSELEFTCLICK)) {
        inputMapper.map(F_MOUSELEFTCLICK, Button.MOUSE_BUTTON1);
    }
    
    if (!inputMapper.hasMappings(F_MOUSERIGHTCLICK)) {
        inputMapper.map(F_MOUSERIGHTCLICK, Button.MOUSE_BUTTON2);
    }
    
    if (!inputMapper.hasMappings(F_GRAVBOMB)) {
        inputMapper.map(F_GRAVBOMB, KeyInput.KEY_LSHIFT);
    }
    
    if (!inputMapper.hasMappings(F_REPEL)) {
        inputMapper.map(F_REPEL, KeyInput.KEY_LSHIFT, KeyInput.KEY_LCONTROL);
    }
    
    if (!inputMapper.hasMappings(F_MINE)) {
        inputMapper.map(F_MINE, KeyInput.KEY_TAB, KeyInput.KEY_LSHIFT);
    }
1 Like

As I said, I could have it backwards and it’s supposed to be shared key last. Unfortunately, I don’t have time to look at my own mappings at the moment… but I do this in a few places in my own code.

1 Like