Skip to content

Commit

Permalink
Improvements to hotplugging on Desktop. (#31)
Browse files Browse the repository at this point in the history
* Improvements to hotplugging on Desktop.

Changes JamepadControllerMonitor to track controllers by SDL instance ID instead of SDL index. This keeps the association of Controller java objects and physical devices constant.

Cuts out the check for new controllers in JamepadControllerMonitor by instead updating the controller list when Jamepad reports that its controller list was updated.

Adds the ability for JamepadController to report its name when disconnected by storing the name on connect.

Allows JampadController to treat a null ControllerIndex as a disconnected controller.

Fixes a bug in JamepadControllerManager that caused the controller update code to run twice as often as intended.

Fixes a bug in ControllersTest that caused the first controller to be unresponsive when the program first starts.

Fixes a bug in ControllersTest that caused duplicate disconnect messages in sone circumstances.

Updates to the newest snapshot of Jamepad.

* JamepadController always returns stored name.

Updates Jamepad to stable version.

Improves code formatting.
  • Loading branch information
Yontipon committed Apr 27, 2023
1 parent 494fa60 commit 6c892a3
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 41 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ allprojects {
appName = 'gdx-controllers'
gdxVersion = '1.9.11'
roboVMVersion = '2.3.16'
jamepadVersion = '2.0.20.0'
jamepadVersion = '2.26.4.0'

isReleaseBuild = {
return project.hasProperty("RELEASE")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public JamepadControllerManager() {
monitor.run();

Gdx.app.addLifecycleListener(new JamepadShutdownHook(controllerManager));
Gdx.app.postRunnable(monitor);

nativeLibInitialized = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ public class JamepadController implements Controller {
}

private final CompositeControllerListener compositeControllerListener = new CompositeControllerListener();
private final ControllerIndex controllerIndex;
private final IntMap<Boolean> buttonState = new IntMap<>();
private final IntMap<Float> axisState = new IntMap<>();
private final String uuid;
private final String name;
private ControllerIndex controllerIndex;
private boolean connected = true;
private Boolean canVibrate = null;
private long vibrationEndMs;
Expand All @@ -43,6 +44,7 @@ public class JamepadController implements Controller {
public JamepadController(ControllerIndex controllerIndex) {
this.controllerIndex = controllerIndex;
this.uuid = UUID.randomUUID().toString();
this.name = getInitialName();
initializeState();
}

Expand All @@ -51,7 +53,7 @@ public boolean getButton(final int buttonCode) {
try {
ControllerButton button = toButton(buttonCode);
return button != null && controllerIndex.isButtonPressed(button);
} catch (ControllerUnpluggedException e) {
} catch (ControllerUnpluggedException | NullPointerException e) {
setDisconnected();
}
return false;
Expand All @@ -67,26 +69,36 @@ public float getAxis(final int axisCode) {
} else {
return controllerIndex.getAxisState(axis);
}
} catch (ControllerUnpluggedException e) {
} catch (ControllerUnpluggedException | NullPointerException e) {
setDisconnected();
}
return 0f;
}

@Override
public String getName() {
private String getInitialName() {
try {
return controllerIndex.getName();
} catch (ControllerUnpluggedException e) {
setDisconnected();
} catch (ControllerUnpluggedException | NullPointerException e) {
// this is only called in the constructor, so disconnecting here wouldn't make sense
}
return "Unknown";
}

private void setDisconnected() {
@Override
public String getName() {
return name;
}

public void setControllerIndex(ControllerIndex controllerIndex) {
this.controllerIndex = controllerIndex;
}

public void setDisconnected() {
if (connected) {
connected = false;
logger.info("Failed querying controller at index: " + controllerIndex.getIndex());
if (controllerIndex != null) {
logger.info("Failed querying controller at index: " + controllerIndex.getIndex());
}
compositeControllerListener.disconnected(this);
}
}
Expand Down Expand Up @@ -165,7 +177,7 @@ public boolean canVibrate() {
if (canVibrate == null) {
try {
canVibrate = controllerIndex.canVibrate();
} catch (ControllerUnpluggedException e) {
} catch (ControllerUnpluggedException | NullPointerException e) {
setDisconnected();
}
}
Expand All @@ -185,7 +197,7 @@ public void startVibration(int duration, float strength) {
vibrationEndMs = TimeUtils.millis() + duration;
canVibrate = true;
}
} catch (ControllerUnpluggedException e) {
} catch (ControllerUnpluggedException | NullPointerException e) {
setDisconnected();
}
}
Expand All @@ -212,7 +224,7 @@ public boolean supportsPlayerIndex() {
public int getPlayerIndex() {
try {
return controllerIndex.getPlayerIndex();
} catch (ControllerUnpluggedException e) {
} catch (ControllerUnpluggedException | NullPointerException e) {
setDisconnected();
return PLAYER_IDX_UNSET;
}
Expand All @@ -222,7 +234,7 @@ public int getPlayerIndex() {
public void setPlayerIndex(int index) {
try {
controllerIndex.setPlayerIndex(index);
} catch (ControllerUnpluggedException e) {
} catch (ControllerUnpluggedException | NullPointerException e) {
setDisconnected();
}
}
Expand All @@ -243,7 +255,7 @@ public int getMaxButtonIndex() {
while (maxButtonIndex > 0 && !controllerIndex.isButtonAvailable(CODE_TO_BUTTON.get(maxButtonIndex))) {
maxButtonIndex--;
}
} catch (ControllerUnpluggedException e) {
} catch (ControllerUnpluggedException | NullPointerException e) {
setDisconnected();
}

Expand All @@ -261,7 +273,7 @@ public int getAxisCount() {
while (axisCount > 0 && !controllerIndex.isAxisAvailable(CODE_TO_AXIS.get(axisCount - 1))) {
axisCount--;
}
} catch (ControllerUnpluggedException e) {
} catch (ControllerUnpluggedException | NullPointerException e) {
setDisconnected();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,107 @@

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.controllers.ControllerListener;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntMap;
import com.badlogic.gdx.controllers.desktop.JamepadControllerManager;
import com.studiohartman.jamepad.ControllerIndex;
import com.studiohartman.jamepad.ControllerManager;
import com.studiohartman.jamepad.ControllerUnpluggedException;

public class JamepadControllerMonitor implements Runnable {
private final ControllerManager controllerManager;
private final ControllerListener listener;
private final IntMap<Tuple> indexToController = new IntMap<>();
//temporary array for cleaning disconnected controllers
private final IntArray disconnectedControllers = new IntArray();
private final IntMap<Tuple> indexToController
= new IntMap<>(JamepadControllerManager.jamepadConfiguration.maxNumControllers);
// temporary array for delaying connect messages
private final Array<JamepadController> connectedControllers = new Array<JamepadController>();

public JamepadControllerMonitor(ControllerManager controllerManager, ControllerListener listener) {
this.controllerManager = controllerManager;
this.listener = listener;

reconcileControllers();
}

@Override
public void run() {
controllerManager.update();
boolean controllersChanged = controllerManager.update();

if (controllersChanged) {
reconcileControllers();
}

checkForNewControllers();
update();

Gdx.app.postRunnable(this);
}

private void checkForNewControllers() {
private void reconcileControllers() {
// Break old connections to help detect disconnected objects later.
for (Tuple tuple : indexToController.values()) {
JamepadController controller = tuple.controller;
tuple.index = null;
controller.setControllerIndex(null);
}

// Get already-connected controllers paired with their existing objects.
// Create objects for new controllers, but don't send connect messages yet.
connectedControllers.clear();
int numControllers = JamepadControllerManager.jamepadConfiguration.maxNumControllers;
for (int i = 0; i < numControllers; i++) try {
ControllerIndex controllerIndex = controllerManager.getControllerIndex(i);

if (!indexToController.containsKey(controllerIndex.getIndex()) && controllerIndex.isConnected()) {
Tuple tuple1 = new Tuple(controllerIndex);
tuple1.controller.addListener(listener);

indexToController.put(controllerIndex.getIndex(), tuple1);
listener.connected(tuple1.controller);
try {
int instanceID = controllerIndex.getDeviceInstanceID();
if (indexToController.containsKey(instanceID)) {
// Pre-existing controller, pair with existing object.
Tuple tuple1 = indexToController.get(instanceID);
tuple1.index = controllerIndex;
tuple1.controller.setControllerIndex(controllerIndex);
} else {
// New controller. Create new object, and store it for connect message later.
Tuple tuple1 = new Tuple(controllerIndex);
indexToController.put(instanceID, tuple1);
connectedControllers.add(tuple1.controller);
}
} catch (ControllerUnpluggedException e) {
// controller not connected, no need to pair it
}
} catch (ArrayIndexOutOfBoundsException t) {
// more controllers connected than we can handle according to our config
}

// Remove disconnected objects.
IntMap.Values<Tuple> values = indexToController.values();
while (values.hasNext()) {
Tuple tuple = values.next();
if (tuple.index == null) {
tuple.controller.setDisconnected();
values.remove();
}
}

// Set up listeners for new controllers and send connect messages.
for (JamepadController controller: connectedControllers) {
controller.addListener(listener);
listener.connected(controller);
}
}

private void update() {
disconnectedControllers.clear();
for (Tuple tuple : indexToController.values()) {
IntMap.Values<Tuple> values = indexToController.values();
while (values.hasNext()) {
Tuple tuple = values.next();
JamepadController controller = tuple.controller;
boolean connected = controller.update();

if (!connected) {
disconnectedControllers.add(tuple.index.getIndex());
values.remove();
}
}

for (int i = 0; i < disconnectedControllers.size; i++) {
indexToController.remove(disconnectedControllers.get(i));
}
}

private class Tuple {
public final ControllerIndex index;
public ControllerIndex index;
public final JamepadController controller;

public Tuple(ControllerIndex index) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ public void disconnected(Controller controller) {
}
};

refreshControllersList();

Controllers.addListener(new ControllerAdapter() {
@Override
public void connected(final Controller controller) {
Expand Down Expand Up @@ -226,6 +224,8 @@ public void changed(ChangeEvent event, Actor actor) {
stage.addActor(table);

Gdx.input.setInputProcessor(stage);

refreshControllersList();
}

private void addAxisLabels() {
Expand Down Expand Up @@ -293,6 +293,9 @@ private void refreshControllersList() {
if (name.length() > 30)
name = name.substring(0, 28) + "...";
controllerNames.add(name);

// avoid adding the listener to the same controller twice
controller.removeListener(controllerListener);
controller.addListener(controllerListener);
}
controllerList.setItems(controllerNames);
Expand Down

0 comments on commit 6c892a3

Please sign in to comment.