1
0
forked from Clones/Controlify

✏️ New ControllerManager class to store controllers

This commit is contained in:
isXander
2023-05-11 16:32:39 +01:00
parent fa1e1293c7
commit 0e8bf0cc9b
9 changed files with 135 additions and 105 deletions

View File

@ -7,11 +7,10 @@ import dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint;
import dev.isxander.controlify.config.gui.ControllerBindHandler;
import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.ControllerState;
import dev.isxander.controlify.controller.joystick.CompoundJoystickController;
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
import dev.isxander.controlify.debug.DebugProperties;
import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen;
import dev.isxander.controlify.gui.screen.VibrationOnboardingScreen;
import dev.isxander.controlify.gui.screen.SDLOnboardingScreen;
import dev.isxander.controlify.screenop.ScreenProcessorProvider;
import dev.isxander.controlify.config.ControlifyConfig;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
@ -127,7 +126,7 @@ public class Controlify implements ControlifyApi {
// find already connected controllers
for (int jid = 0; jid <= GLFW.GLFW_JOYSTICK_LAST; jid++) {
if (GLFW.glfwJoystickPresent(jid)) {
var controllerOpt = Controller.createOrGet(jid, controllerHIDService.fetchType());
var controllerOpt = ControllerManager.createOrGet(jid, controllerHIDService.fetchType(jid));
if (controllerOpt.isEmpty()) continue;
var controller = controllerOpt.get();
@ -145,14 +144,12 @@ public class Controlify implements ControlifyApi {
}
}
checkCompoundJoysticks();
if (Controller.CONTROLLERS.isEmpty()) {
if (ControllerManager.getConnectedControllers().isEmpty()) {
LOGGER.info("No controllers found.");
}
if (getCurrentController().isEmpty() && config().isFirstLaunch()) {
this.setCurrentController(Controller.CONTROLLERS.values().stream().findFirst().orElse(null));
this.setCurrentController(ControllerManager.getConnectedControllers().stream().findFirst().orElse(null));
} else {
// setCurrentController saves config
config().saveIfDirty();
@ -213,7 +210,7 @@ public class Controlify implements ControlifyApi {
boolean outOfFocus = !config().globalSettings().outOfFocusInput && !client.isWindowActive();
for (var controller : Controller.CONTROLLERS.values()) {
for (var controller : ControllerManager.getConnectedControllers()) {
if (!outOfFocus)
wrapControllerError(controller::updateState, "Updating controller state", controller);
else
@ -295,7 +292,7 @@ public class Controlify implements ControlifyApi {
}
private void onControllerHotplugged(int jid) {
var controllerOpt = Controller.createOrGet(jid, controllerHIDService.fetchType());
var controllerOpt = ControllerManager.createOrGet(jid, controllerHIDService.fetchType(jid));
if (controllerOpt.isEmpty()) return;
var controller = controllerOpt.get();
@ -308,9 +305,7 @@ public class Controlify implements ControlifyApi {
config().setDirty();
}
checkCompoundJoysticks();
if (Controller.CONTROLLERS.size() == 1) {
if (ControllerManager.getConnectedControllers().size() == 1) {
this.setCurrentController(controller);
ToastUtils.sendToast(
@ -325,12 +320,12 @@ public class Controlify implements ControlifyApi {
}
private void onControllerDisconnect(int jid) {
Controller.CONTROLLERS.values().stream().filter(controller -> controller.joystickId() == jid).findAny().ifPresent(controller -> {
Controller.remove(controller);
ControllerManager.getConnectedControllers().stream().filter(controller -> controller.joystickId() == jid).findAny().ifPresent(controller -> {
ControllerManager.disconnect(controller);
controller.hidInfo().ifPresent(controllerHIDService::unconsumeController);
setCurrentController(Controller.CONTROLLERS.values().stream().findFirst().orElse(null));
setCurrentController(ControllerManager.getConnectedControllers().stream().findFirst().orElse(null));
LOGGER.info("Controller disconnected: " + controller.name());
this.setInputMode(currentController == null ? InputMode.KEYBOARD_MOUSE : InputMode.CONTROLLER);
@ -340,28 +335,6 @@ public class Controlify implements ControlifyApi {
false
);
});
checkCompoundJoysticks();
}
private void checkCompoundJoysticks() {
config().getCompoundJoysticks().values().forEach(info -> {
try {
if (info.isLoaded() && !info.canBeUsed()) {
LOGGER.warn("Unloading compound joystick " + info.friendlyName() + " due to missing controllers.");
Controller.CONTROLLERS.remove(info.type().mappingId());
}
if (!info.isLoaded() && info.canBeUsed()) {
LOGGER.info("Loading compound joystick " + info.type().mappingId() + ".");
CompoundJoystickController controller = info.attemptCreate().orElseThrow();
Controller.CONTROLLERS.put(info.type().mappingId(), controller);
config().loadOrCreateControllerData(controller);
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
private void askToSwitchController(Controller<?, ?> controller) {

View File

@ -0,0 +1,107 @@
package dev.isxander.controlify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.gamepad.GamepadController;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.controller.joystick.CompoundJoystickController;
import dev.isxander.controlify.controller.joystick.SingleJoystickController;
import dev.isxander.controlify.debug.DebugProperties;
import dev.isxander.controlify.utils.DebugLog;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import org.hid4java.HidDevice;
import org.lwjgl.glfw.GLFW;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public final class ControllerManager {
private ControllerManager() {
}
private final static Map<String, Controller<?, ?>> CONTROLLERS = new HashMap<>();
public static Optional<Controller<?, ?>> createOrGet(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
try {
Optional<String> uid = hidInfo.createControllerUID();
if (uid.isPresent() && CONTROLLERS.containsKey(uid.get())) {
return Optional.of(CONTROLLERS.get(uid.get()));
}
if (hidInfo.type().dontLoad()) {
DebugLog.log("Preventing load of controller #" + joystickId + " because its type prevents loading.");
return Optional.empty();
}
if (GLFW.glfwJoystickIsGamepad(joystickId) && !DebugProperties.FORCE_JOYSTICK && !hidInfo.type().forceJoystick()) {
GamepadController controller = new GamepadController(joystickId, hidInfo);
CONTROLLERS.put(controller.uid(), controller);
checkCompoundJoysticks();
return Optional.of(controller);
}
SingleJoystickController controller = new SingleJoystickController(joystickId, hidInfo);
CONTROLLERS.put(controller.uid(), controller);
checkCompoundJoysticks();
return Optional.of(controller);
} catch (Throwable e) {
CrashReport crashReport = CrashReport.forThrowable(e, "Creating controller #" + joystickId);
CrashReportCategory category = crashReport.addCategory("Controller Info");
category.setDetail("Joystick ID", joystickId);
category.setDetail("Controller identification", hidInfo.type());
category.setDetail("HID path", hidInfo.hidDevice().map(HidDevice::getPath).orElse("N/A"));
category.setDetail("HID service status", Controlify.instance().controllerHIDService().isDisabled() ? "Disabled" : "Enabled");
category.setDetail("GLFW name", Optional.ofNullable(GLFW.glfwGetJoystickName(joystickId)).orElse("N/A"));
throw new ReportedException(crashReport);
}
}
public static void disconnect(Controller<?, ?> controller) {
controller.close();
CONTROLLERS.remove(controller.uid(), controller);
checkCompoundJoysticks();
}
public static void disconnect(String uid) {
Controller<?, ?> prev = CONTROLLERS.remove(uid);
if (prev != null) {
prev.close();
}
checkCompoundJoysticks();
}
public static List<Controller<?, ?>> getConnectedControllers() {
return ImmutableList.copyOf(CONTROLLERS.values());
}
public static boolean isControllerConnected(String uid) {
return CONTROLLERS.containsKey(uid);
}
private static void checkCompoundJoysticks() {
Controlify.instance().config().getCompoundJoysticks().values().forEach(info -> {
try {
if (info.isLoaded() && !info.canBeUsed()) {
Controlify.LOGGER.warn("Unloading compound joystick " + info.friendlyName() + " due to missing controllers.");
disconnect(info.type().mappingId());
}
if (!info.isLoaded() && info.canBeUsed()) {
Controlify.LOGGER.info("Loading compound joystick " + info.type().mappingId() + ".");
CompoundJoystickController controller = info.attemptCreate().orElseThrow();
CONTROLLERS.put(info.type().mappingId(), controller);
Controlify.instance().config().loadOrCreateControllerData(controller);
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
}

View File

@ -2,6 +2,7 @@ package dev.isxander.controlify.config;
import com.google.gson.*;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.ControllerManager;
import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.joystick.CompoundJoystickInfo;
import dev.isxander.controlify.utils.DebugLog;
@ -78,7 +79,7 @@ public class ControlifyConfig {
JsonObject newControllerData = controllerData.deepCopy(); // we use the old config, so we don't lose disconnected controller data
for (var controller : Controller.CONTROLLERS.values()) {
for (var controller : ControllerManager.getConnectedControllers()) {
// `add` replaces if already existing
newControllerData.add(controller.uid(), generateControllerConfig(controller));
}
@ -111,7 +112,7 @@ public class ControlifyConfig {
JsonObject controllers = object.getAsJsonObject("controllers");
if (controllers != null) {
this.controllerData = controllers;
for (var controller : Controller.CONTROLLERS.values()) {
for (var controller : ControllerManager.getConnectedControllers()) {
loadOrCreateControllerData(controller);
}
} else {

View File

@ -2,9 +2,11 @@ package dev.isxander.controlify.config.gui;
import com.google.common.collect.Iterables;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.ControllerManager;
import dev.isxander.controlify.api.bind.ControllerBinding;
import dev.isxander.controlify.bindings.BindContext;
import dev.isxander.controlify.config.GlobalSettings;
import dev.isxander.controlify.controller.BatteryLevel;
import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.ControllerConfig;
import dev.isxander.controlify.controller.ControllerState;
@ -12,7 +14,9 @@ import dev.isxander.controlify.controller.gamepad.GamepadController;
import dev.isxander.controlify.controller.gamepad.BuiltinGamepadTheme;
import dev.isxander.controlify.controller.joystick.SingleJoystickController;
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen;
import dev.isxander.controlify.gui.screen.SDLOnboardingScreen;
import dev.isxander.controlify.reacharound.ReachAroundMode;
import dev.isxander.controlify.rumble.BasicRumbleEffect;
import dev.isxander.controlify.rumble.RumbleSource;
@ -58,7 +62,7 @@ public class YACLHelper {
.name(Component.translatable("controlify.gui.current_controller"))
.tooltip(Component.translatable("controlify.gui.current_controller.tooltip"))
.binding(Controlify.instance().getCurrentController().orElse(Controller.DUMMY), () -> Controlify.instance().getCurrentController().orElse(Controller.DUMMY), v -> Controlify.instance().setCurrentController(v))
.controller(opt -> new CyclingListController<>(opt, Iterables.concat(List.of(Controller.DUMMY), Controller.CONTROLLERS.values().stream().filter(Controller::canBeUsed).toList()), c -> Component.literal(c == Controller.DUMMY ? "Disabled" : c.name())))
.controller(opt -> new CyclingListController<>(opt, Iterables.concat(List.of(Controller.DUMMY), ControllerManager.getConnectedControllers().stream().filter(Controller::canBeUsed).toList()), c -> Component.literal(c == Controller.DUMMY ? "Disabled" : c.name())))
.build())
.option(globalVibrationOption = Option.createBuilder(boolean.class)
.name(Component.translatable("controlify.gui.load_vibration_natives"))
@ -102,7 +106,7 @@ public class YACLHelper {
yacl.category(globalCategory.build());
for (var controller : Controller.CONTROLLERS.values()) {
for (var controller : ControllerManager.getConnectedControllers()) {
yacl.category(createControllerCategory(controller, globalVibrationOption));
}

View File

@ -4,15 +4,10 @@ import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.InputMode;
import dev.isxander.controlify.api.ControlifyApi;
import dev.isxander.controlify.ControllerManager;
import dev.isxander.controlify.bindings.ControllerBindings;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
import dev.isxander.controlify.rumble.RumbleCapable;
import dev.isxander.controlify.rumble.RumbleManager;
import dev.isxander.controlify.rumble.RumbleSource;
import org.libsdl.SDL;
import org.lwjgl.glfw.GLFW;
import java.util.Objects;
@ -63,7 +58,7 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
protected void setName(String name) {
String uniqueName = name;
int i = 0;
while (CONTROLLERS.values().stream().map(Controller::name).anyMatch(uniqueName::equalsIgnoreCase)) {
while (ControllerManager.getConnectedControllers().stream().map(Controller::name).anyMatch(uniqueName::equalsIgnoreCase)) {
uniqueName = name + " (" + i++ + ")";
if (i > 1000) throw new IllegalStateException("Could not find a unique name for controller " + name + " (" + uid() + ")! (tried " + i + " times)");
}

View File

@ -54,46 +54,6 @@ public interface Controller<S extends ControllerState, C extends ControllerConfi
return true;
}
Map<String, Controller<?, ?>> CONTROLLERS = new HashMap<>();
static Optional<Controller<?, ?>> createOrGet(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
try {
Optional<String> uid = hidInfo.createControllerUID();
if (uid.isPresent() && CONTROLLERS.containsKey(uid.get())) {
return Optional.of(CONTROLLERS.get(uid.get()));
}
if (hidInfo.type().dontLoad()) {
DebugLog.log("Preventing load of controller #" + joystickId + " because its type prevents loading.");
return Optional.empty();
}
if (GLFW.glfwJoystickIsGamepad(joystickId) && !DebugProperties.FORCE_JOYSTICK && !hidInfo.type().forceJoystick()) {
GamepadController controller = new GamepadController(joystickId, hidInfo);
CONTROLLERS.put(controller.uid(), controller);
return Optional.of(controller);
}
SingleJoystickController controller = new SingleJoystickController(joystickId, hidInfo);
CONTROLLERS.put(controller.uid(), controller);
return Optional.of(controller);
} catch (Throwable e) {
CrashReport crashReport = CrashReport.forThrowable(e, "Creating controller #" + joystickId);
CrashReportCategory category = crashReport.addCategory("Controller Info");
category.setDetail("Joystick ID", joystickId);
category.setDetail("Controller identification", hidInfo.type());
category.setDetail("HID path", hidInfo.hidDevice().map(HidDevice::getPath).orElse("N/A"));
category.setDetail("HID service status", Controlify.instance().controllerHIDService().isDisabled() ? "Disabled" : "Enabled");
category.setDetail("GLFW name", Optional.ofNullable(GLFW.glfwGetJoystickName(joystickId)).orElse("N/A"));
throw new ReportedException(crashReport);
}
}
static void remove(Controller<?, ?> controller) {
controller.close();
CONTROLLERS.remove(controller.uid(), controller);
}
@Deprecated
Controller<?, ?> DUMMY = new Controller<>() {
private final ControllerBindings<ControllerState> bindings = new ControllerBindings<>(this);

View File

@ -1,5 +1,6 @@
package dev.isxander.controlify.controller.joystick;
import dev.isxander.controlify.ControllerManager;
import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.ControllerType;
@ -13,7 +14,7 @@ public record CompoundJoystickInfo(Collection<String> joystickUids, String frien
}
public boolean canBeUsed() {
List<Controller<?, ?>> joysticks = Controller.CONTROLLERS.values().stream().filter(c -> joystickUids.contains(c.uid())).toList();
List<Controller<?, ?>> joysticks = ControllerManager.getConnectedControllers().stream().filter(c -> joystickUids.contains(c.uid())).toList();
if (joysticks.size() != joystickUids().size()) {
return false; // not all controllers are connected
}
@ -25,13 +26,13 @@ public record CompoundJoystickInfo(Collection<String> joystickUids, String frien
}
public boolean isLoaded() {
return Controller.CONTROLLERS.containsKey(createUID(joystickUids));
return ControllerManager.isControllerConnected(createUID(joystickUids));
}
public Optional<CompoundJoystickController> attemptCreate() {
if (!canBeUsed()) return Optional.empty();
List<Integer> joystickIDs = Controller.CONTROLLERS.values().stream()
List<Integer> joystickIDs = ControllerManager.getConnectedControllers().stream()
.filter(c -> joystickUids.contains(c.uid()))
.map(Controller::joystickId)
.toList();

View File

@ -2,14 +2,13 @@ package dev.isxander.controlify.mixins.core;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.ControllerManager;
import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.gui.screen.BetaNoticeScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.toasts.SystemToast;
import net.minecraft.client.gui.components.toasts.ToastComponent;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.main.GameConfig;
import net.minecraft.network.chat.Component;
import net.minecraft.server.packs.resources.ReloadInstance;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
@ -54,18 +53,8 @@ public abstract class MinecraftMixin {
setScreen(new BetaNoticeScreen());
}
@ModifyExpressionValue(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/packs/resources/ReloadableResourceManager;createReload(Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;Ljava/util/concurrent/CompletableFuture;Ljava/util/List;)Lnet/minecraft/server/packs/resources/ReloadInstance;"))
private ReloadInstance onReloadResources(ReloadInstance resourceReload) {
resourceReload.done().thenRun(() -> {
if (Controlify.instance().controllerHIDService().isDisabled()) {
getToasts().addToast(SystemToast.multiline((Minecraft) (Object) this, SystemToast.SystemToastIds.UNSECURE_SERVER_WARNING, Component.translatable("controlify.error.hid"), Component.translatable("controlify.error.hid.desc")));
}
});
return resourceReload;
}
@Inject(method = "close", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/telemetry/ClientTelemetryManager;close()V"))
private void onMinecraftClose(CallbackInfo ci) {
Controller.CONTROLLERS.values().forEach(Controller::close);
ControllerManager.getConnectedControllers().forEach(Controller::close);
}
}