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
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.
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.
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
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.
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?
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.