[SOLVED] Changing the key mapping in game leads to an exception

I use Lemur’s InputManager. I have a Lemur Menu to change the controller’s button mapping on my actions like jump, shoot, reload, …

If I do this in game then I get an exception

java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
	at com.simsilica.lemur.input.InputMapper$StateGroupIndex.refresh(InputMapper.java:830)
	at com.simsilica.lemur.input.InputMapper$StateGroupIndex.updateValue(InputMapper.java:866)
	at com.simsilica.lemur.input.InputMapper$InputObserver.onJoyButtonEvent(InputMapper.java:977)
	at com.jme3.input.InputManager.processQueue(InputManager.java:851)
	at com.jme3.input.InputManager.update(InputManager.java:923)
	at com.jme3.app.LegacyApplication.update(LegacyApplication.java:777)
	at com.jme3.app.SimpleApplication.update(SimpleApplication.java:248)
	at ch.artificials.bubble.Main.update(Main.java:191)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:160)
	at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:201)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:242)
	at java.base/java.lang.Thread.run(Thread.java:832)

Is there a secured way to apply new mappings on the Inputs? This

inputMapper.map(F_SHOOT, Button.JOYSTICK_BUTTON1);

without this exception?

Are you trying to change Lemur’s input mapping from a Lemur InputMapper listener?

I’m not even sure how that works.

What is the user experience supposed to be here?

Edit: or is it that the input is already mapped to something else? Basically, I need more information.

That function id is indeed already mapped. I want to make it possible in a settings menu to change the default mapping of the game controller to my function ids like “shoot”, “jump” so the player can choose which button does the jump etc.

But what is the user experience? They wish really hard? Click gmail? What?!?

Perhaps it will be more productive to describe how I planned to implement this myself.

Register a RawInputListener with JME.
Present to the user a list of functions and their current mappings.
They click on a mapping.
The code clears the mapping and begins to listen to the RawInputListener until all “ups” have been received.
It uses that to remap the function to a new input.

Hmmm don’t know if I understood it. So I have to clear the mapping for a function before I remap a new one?

I’m trying to understand what you expect the user to see and do. So far I’ve only seen the destination.

In my approach, I clear the mapping so that it does not interfere with what they are trying to input.

And in your approach, I don’t understand why Lemur is receiving inputs for something that hasn’t been mapped yet because the user hasn’t mapped it yet. But I have 0 clues as to what the user is doing in your scenario.

Edit: and so without the information necessary to help you and so that I can stop hovering… I’ll just give a generic “enqueue your remapping” answer.

Ok. when I start the game I do an initial key mapping in the initializer method of the main SimpleApplication by loading a JSON file that contains the mapping

        controllerKeyMapping.loadMappings(globals.getInputMapper(), mappingfile);

Then everything starts and a menu comes along. The menu has a “Play”, “Load”, “Settings” and an “Exit” button. In the “Settings” I give the player the possibility to provide a different mapping interactively (not in the most user-friendly way as I’m still working on it, more a proof of concept). As soon I leave “Settings” I store the new mapping in a custom.map JSON file and try to load that mapping new mapping file with the same method I used initially (but I do not remove the initial loaded and that might be the problem).

I don’t know if the idea is clearer now. If you just go with “Play” I use the initially loaded keymap. That’s it.

I remove now the mappings for shoot, reload, jump, … I still get the exception but I catch it and the new loaded mappings are working. So not beautiful but functional for the moment. Still don’t know how to do this properly and I also fear we talk not about exactly the same thing… anyway looking forward to better controller support, meanwhile, I try myself as I’m not too happy with the existing one.

Are you doing it in the update queue? If it is just a concurrency issue, the easiest guess is that you are doing it outside the main jME loop and the loop seems to crash when it tries to go through the input queue.

I would love to do it outside the update loop but I don’t see how.

An easy way to get some code to run outside the update loop is to use a lambda app.enqueue surrounding that code in the update loop.

For example:

   public void update(float tpf){
      ...

        app.enqueue(() ->{
            remapThingsHere();
        });
     }

Some may consider the lambda like this a messy/hacky solution but once you know if it works, you can always clean it up. But I frequently use this for convenience when I’m troubleshooting a problem in the update loop that I think might be solved by waiting to do something until the next frame.

I could be wrong because I don’t use Lemur’s input mappings, but this might be what Paul meant by this?

1 Like

Yes, if you are doing the remapping because the user clicked a button… then you are remapping while handling an event. You are trying to change the list of mappings while the list is being used to notify you about your button.

The easiest way around that is to have your listener app->enqueue what it’s about to do instead of doing it directly. It will then happen on the next frame.

2 Likes

Thanks I didn’t know this enqueue trick. That’s cool. That way I can certainly solve my issue.

1 Like

@pspeed @yaRnMcDonuts, solved it with this enqueue thing. Thanks a lot for the quick help!

2 Likes