
de.gurkenlabs.litiengine.input.GamepadManager Maven / Gradle / Ivy
Show all versions of litiengine Show documentation
package de.gurkenlabs.litiengine.input;
import de.gurkenlabs.litiengine.Game;
import de.gurkenlabs.litiengine.GameListener;
import de.gurkenlabs.litiengine.ILaunchable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.EventListener;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.java.games.input.Controller;
import net.java.games.input.Controller.Type;
import net.java.games.input.ControllerEnvironment;
/**
* The {@code GamepadManager} provides access to all gamepad input devices.
*
*
* Gamepads don't need to be added explicitly, the manager supports hot-plugging at runtime and will auto-detect any
* added/removed gamepads.
*
* @see #current()
* @see #get(int)
*/
public final class GamepadManager extends GamepadEvents implements ILaunchable {
private static final Logger log = Logger.getLogger(GamepadManager.class.getName());
private final Collection gamepadAddedConsumer;
private final Collection gamepadRemovedConsumer;
private final List gamePads;
private final Thread hotPlugThread;
private int defaultgamepadId = -1;
private boolean handleHotPluggedControllers;
GamepadManager() {
this.gamepadRemovedConsumer = ConcurrentHashMap.newKeySet();
this.gamepadAddedConsumer = ConcurrentHashMap.newKeySet();
this.gamePads = new CopyOnWriteArrayList<>();
this.hotPlugThread =
new Thread(
() -> {
while (!Thread.interrupted()) {
this.updateGamepads();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
Game.addGameListener(
new GameListener() {
@Override
public void terminated() {
hotPlugThread.interrupt();
}
});
this.onAdded(
pad -> {
if (this.defaultgamepadId == -1) {
this.defaultgamepadId = pad.getId();
this.hookupToGamepad(pad);
}
});
this.onRemoved(
pad -> {
if (this.defaultgamepadId == pad.getId()) {
this.defaultgamepadId = -1;
final Gamepad newGamePad = current();
if (newGamePad != null) {
this.defaultgamepadId = newGamePad.getId();
this.hookupToGamepad(newGamePad);
}
}
});
updateGamepads();
}
/**
* Adds the specified gamepad added listener to receive events when gamepads are added.
*
* @param listener
* The listener to add.
*/
public void onAdded(final GamepadAddedListener listener) {
this.gamepadAddedConsumer.add(listener);
}
/**
* Unregister the specified added listener from this instance.
*
* @param listener
* The listener to remove.
*/
public void removeAddedListener(GamepadAddedListener listener) {
this.gamepadAddedConsumer.remove(listener);
}
/**
* Adds the specified gamepad removed listener to receive events when gamepads are removed.
*
* @param listener
* The listener to add.
*/
public void onRemoved(final GamepadRemovedListener listener) {
this.gamepadRemovedConsumer.add(listener);
}
/**
* Unregister the specified removed listener from this instance.
*
* @param listener
* The listener to remove.
*/
public void removeRemovedListener(GamepadRemovedListener listener) {
this.gamepadRemovedConsumer.remove(listener);
}
/**
* Gets all gamepads that are currently available.
*
* @return All available gamepads.
* @see #get(int)
* @see #current()
*/
public List getAll() {
return this.gamePads;
}
/**
* Gets the first gamepad that is currently available.
*
* @return The first available {@link Gamepad} instance
* @see #get(int)
* @see #getAll()
*/
public Gamepad current() {
return get(0);
}
/**
* Gets the gamepad by the index within the gamepad list.
*
* @param index
* The index of the {@link Gamepad}.
* @return The {@link Gamepad} with the specified index.
* @see #getAll()
* @see #current()
*/
public Gamepad get(final int index) {
if (this.gamePads.isEmpty()) {
return null;
}
return this.gamePads.get(index);
}
/**
* Gets the gamepad with the specified id if it is still plugged in. After re-plugging a controller while the game is
* running, its id might change.
*
* @param id
* The id of the {@link Gamepad}.
* @return The {@link Gamepad} with the specified index.
* @see #getAll()
* @see #current()
*/
public Gamepad getById(final int id) {
for (final Gamepad gamepad : this.gamePads) {
if (gamepad.getId() == id) {
return gamepad;
}
}
return null;
}
@Override
public boolean isPressed(String gamepadComponent) {
final Gamepad current = this.current();
return current != null && current.isPressed(gamepadComponent);
}
@Override
public void onPoll(GamepadPollListener listener) {
super.onPoll(listener);
final var current = this.current();
if (current != null) {
current.onPoll(listener);
}
}
@Override
public void onPressed(GamepadPressedListener listener) {
super.onPressed(listener);
final var current = this.current();
if (current != null) {
current.onPressed(listener);
}
}
@Override
public void onReleased(GamepadReleasedListener listener) {
super.onReleased(listener);
final var current = this.current();
if (current != null) {
current.onReleased(listener);
}
}
@Override
public void onPoll(String identifier, GamepadPollListener listener) {
super.onPoll(identifier, listener);
final var current = this.current();
if (current != null) {
current.onPoll(identifier, listener);
}
}
@Override
public void onPressed(String identifier, GamepadPressedListener listener) {
super.onPressed(identifier, listener);
final var current = this.current();
if (current != null) {
current.onPressed(identifier, listener);
}
}
@Override
public void onReleased(String identifier, GamepadReleasedListener listener) {
super.onReleased(identifier, listener);
final var current = this.current();
if (current != null) {
current.onReleased(identifier, listener);
}
}
@Override
public void clearEventListeners() {
super.clearEventListeners();
final var current = this.current();
if (current != null) {
current.clearEventListeners();
}
}
@Override
public void removePollListener(String identifier, GamepadPollListener listener) {
super.removePollListener(identifier, listener);
final var current = this.current();
if (current != null) {
current.removePollListener(identifier, listener);
}
}
@Override
public void removePressedListener(String identifier, GamepadPressedListener listener) {
super.removePressedListener(identifier, listener);
final var current = this.current();
if (current != null) {
current.removePressedListener(identifier, listener);
}
}
@Override
public void removeReleasedListener(String identifier, GamepadReleasedListener listener) {
super.removeReleasedListener(identifier, listener);
final var current = this.current();
if (current != null) {
current.removeReleasedListener(identifier, listener);
}
}
@Override
public void removePollListener(GamepadPollListener listener) {
super.removePollListener(listener);
final var current = this.current();
if (current != null) {
current.removePollListener(listener);
}
}
@Override
public void removePressedListener(GamepadPressedListener listener) {
super.removePressedListener(listener);
final var current = this.current();
if (current != null) {
current.removePressedListener(listener);
}
}
@Override
public void removeReleasedListener(GamepadReleasedListener listener) {
super.removeReleasedListener(listener);
final var current = this.current();
if (current != null) {
current.removeReleasedListener(listener);
}
}
/**
* DON'T CALL THIS EXPLICITLY! THE LITIENGINE WILL MANAGE THE LIFECYCLE OF THIS INSTANCE.
*/
@Override
public void start() {
this.hotPlugThread.start();
}
/**
* DON'T CALL THIS EXPLICITLY! THE LITIENGINE WILL MANAGE THE LIFECYCLE OF THIS INSTANCE.
*/
@Override
public void terminate() {
int totalWait = 0;
while (handleHotPluggedControllers && totalWait <= 40) {
try {
Thread.sleep(50);
} catch (Exception e) {
break;
}
totalWait++;
}
this.hotPlugThread.interrupt();
}
/**
* DON'T CALL THIS EXPLICITLY! THE LITIENGINE WILL MANAGE THE LIFECYCLE OF GAMEPADS.
*/
void remove(final Gamepad gamepad) {
if (gamepad == null) {
return;
}
this.getAll().remove(gamepad);
for (final GamepadRemovedListener listener : this.gamepadRemovedConsumer) {
listener.removed(gamepad);
}
}
/**
* In JInput it is not possible to get newly added controllers or detached controllers because it will never update its
* controllers. If you would restart the application it would work... so we just reset the environment via reflection
* and it'll do it ;).
*/
private static void hackTheShitOutOfJInput() {
try {
final Field env = ControllerEnvironment.class.getDeclaredField("defaultEnvironment");
env.setAccessible(true);
final Class> clazz = Class.forName("net.java.games.input.DefaultControllerEnvironment");
// kill threads that might still be running.
// otherwise we would spawn a new thread every time this method is called
// without killing the last one
final Set threadSet = Thread.getAllStackTraces().keySet();
for (final Thread thread : threadSet) {
final String name = thread.getClass().getName();
if (name.equals("net.java.games.input.RawInputEventQueue$QueueThread")) {
thread.interrupt();
try {
thread.join();
} catch (InterruptedException e) {
log.log(Level.FINE, e.getMessage(), e);
Thread.currentThread().interrupt();
}
}
}
final Constructor> ctor = clazz.getConstructor();
ctor.setAccessible(true);
env.set(null, ctor.newInstance());
} catch (ReflectiveOperationException e) {
log.log(Level.SEVERE, e.getMessage(), e);
}
}
private void hookupToGamepad(final Gamepad pad) {
for (final Map.Entry> entry : this.componentPollListeners.entrySet()) {
for (final GamepadPollListener listener : entry.getValue()) {
pad.onPoll(entry.getKey(), listener);
}
}
for (final Map.Entry> entry : this.componentPressedListeners.entrySet()) {
for (final GamepadPressedListener listener : entry.getValue()) {
pad.onPressed(entry.getKey(), listener);
}
}
for (final Map.Entry> entry : this.componentReleasedListeners.entrySet()) {
for (final GamepadReleasedListener listener : entry.getValue()) {
pad.onReleased(entry.getKey(), listener);
}
}
for (final GamepadPollListener listener : this.pollListeners) {
pad.onPoll(listener);
}
for (final GamepadPressedListener listener : this.pressedListeners) {
pad.onPressed(listener);
}
for (final GamepadReleasedListener listener : this.releasedListeners) {
pad.onReleased(listener);
}
}
private void updateGamepads() {
this.handleHotPluggedControllers = true;
try {
hackTheShitOutOfJInput();
// update plugged in gamepads
for (int i = 0; i < ControllerEnvironment.getDefaultEnvironment().getControllers().length; i++) {
final Controller controller = ControllerEnvironment.getDefaultEnvironment().getControllers()[i];
final Type type = controller.getType();
if (type.equals(Type.KEYBOARD) || type.equals(Type.MOUSE) || type.equals(Type.UNKNOWN)) {
continue;
}
final Gamepad existing = this.getById(i);
if (existing != null && existing.getName().equals(controller.getName())) {
// already added
continue;
}
// add new gamepads
final Gamepad newGamepad = new Gamepad(i, controller);
this.getAll().add(newGamepad);
for (final GamepadAddedListener listener : this.gamepadAddedConsumer) {
listener.added(newGamepad);
}
}
} catch (IllegalStateException e) {
this.hotPlugThread.interrupt();
} finally {
this.handleHotPluggedControllers = false;
}
}
/**
* This listener interface receives events when gamepads gets added.
*
* @see GamepadManager#onAdded(GamepadAddedListener)
*/
@FunctionalInterface
public interface GamepadAddedListener extends EventListener {
/**
* Invoked when a gamepad was added.
*
* @param gamepad
* The added gamepad.
*/
void added(Gamepad gamepad);
}
/**
* This listener interface receives events when gamepads gets removed.
*
* @see GamepadManager#onAdded(GamepadAddedListener)
*/
@FunctionalInterface
public interface GamepadRemovedListener extends EventListener {
/**
* Invoked when a gamepad was removed.
*
* @param gamepad
* The removed gamepad.
*/
void removed(Gamepad gamepad);
}
}