Many under the hood improvements:

- CPU overhead reduced
- Compatibility increased
- Fix digital triggers auto-disabling
- Fix KeyEvents from DPAD
This commit is contained in:
SerpentSpirale 2021-07-22 18:43:02 +02:00
parent 51e7c2fce8
commit b4a21feaea
2 changed files with 91 additions and 119 deletions

View File

@ -1,6 +1,8 @@
package net.kdt.pojavlaunch.customcontrols.gamepad; package net.kdt.pojavlaunch.customcontrols.gamepad;
import android.os.Handler;
import android.os.Looper;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
@ -34,7 +36,7 @@ public class Gamepad {
private final BaseMainActivity gameActivity; private final BaseMainActivity gameActivity;
private final ImageView pointerView; private final ImageView pointerView;
private final GamepadDpad gamepadDpad = new GamepadDpad(this); private final GamepadDpad gamepadDpad = new GamepadDpad();
private final GamepadJoystick leftJoystick; private final GamepadJoystick leftJoystick;
private int currentJoystickDirection = DIRECTION_NONE; private int currentJoystickDirection = DIRECTION_NONE;
@ -55,86 +57,37 @@ public class Gamepad {
private GamepadMap currentMap = gameMap; private GamepadMap currentMap = gameMap;
private boolean lastGrabbingState = true; private boolean lastGrabbingState = true;
private MotionEvent lastMotionEvent = null; private final boolean hasDigitalTriggers;
private final Thread mouseThread; private final Handler handler = new Handler(Looper.getMainLooper());
private final Runnable mouseRunnable;
private final Runnable switchStateRunnable; private final Runnable switchStateRunnable;
private final Runnable motionEventRunnable;
public Gamepad(BaseMainActivity gameActivity, InputDevice inputDevice){ public Gamepad(BaseMainActivity gameActivity, InputDevice inputDevice){
Toast.makeText(gameActivity.getApplicationContext(),"GAMEPAD CREATED", Toast.LENGTH_LONG).show(); Toast.makeText(gameActivity.getApplicationContext(),"GAMEPAD CREATED", Toast.LENGTH_LONG).show();
leftJoystick = new GamepadJoystick(MotionEvent.AXIS_X, MotionEvent.AXIS_Y, inputDevice); leftJoystick = new GamepadJoystick(MotionEvent.AXIS_X, MotionEvent.AXIS_Y, inputDevice);
rightJoystick = new GamepadJoystick(MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, inputDevice); rightJoystick = new GamepadJoystick(MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, inputDevice);
hasDigitalTriggers = inputDevice.hasKeys(KeyEvent.KEYCODE_BUTTON_R2)[0];
this.gameActivity = gameActivity; this.gameActivity = gameActivity;
pointerView = this.gameActivity.findViewById(R.id.console_pointer); pointerView = this.gameActivity.findViewById(R.id.console_pointer);
pointerView.getDrawable().setFilterBitmap(false); pointerView.getDrawable().setFilterBitmap(false);
notifyGUISizeChange(gameActivity.getMcScale()); notifyGUISizeChange(gameActivity.getMcScale());
Runnable handlerRunnable = new Runnable() {
mouseThread = new Thread("Gamepad Thread"){
long lastTime = System.nanoTime();
final double ticks = 60D;
final double ns = 1000000000 / ticks;
double delta = 0;
@Override @Override
public void run() { public void run() {
updateGrabbingState();
tick();
while (!isInterrupted()) { handler.postDelayed(this, 16);
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
if(delta >= 1) {
updateGrabbingState();
tick();
delta--;
try{ sleep(Math.max((long) ( (1 - delta) * (1000/ticks) ), 0)); } catch (InterruptedException e) {e.printStackTrace();}
}
}
}
private void tick(){
//Update motion events, and the mouse position
if(lastMotionEvent != null)gameActivity.runOnUiThread(motionEventRunnable);
if(lastHorizontalValue != 0 || lastVerticalValue != 0){
GamepadJoystick currentJoystick = lastGrabbingState ? leftJoystick : rightJoystick;
acceleration = (mouseMagnitude - currentJoystick.getDeadzone())/(1 - currentJoystick.getDeadzone());
acceleration = Math.pow(acceleration, mouseMaxAcceleration);
if(acceleration > 1) acceleration = 1;
CallbackBridge.mouseX += Math.cos(mouseAngle) * acceleration * mouseSensitivity;
CallbackBridge.mouseY -= Math.sin(mouseAngle) * acceleration * mouseSensitivity;
gameActivity.runOnUiThread(mouseRunnable);
if(!lastGrabbingState){
CallbackBridge.mouseX = MathUtils.clamp(CallbackBridge.mouseX, 0, CallbackBridge.windowWidth);
CallbackBridge.mouseY = MathUtils.clamp(CallbackBridge.mouseY, 0, CallbackBridge.windowHeight);
placePointerView((int) (CallbackBridge.mouseX /gameActivity.scaleFactor), (int) (CallbackBridge.mouseY/gameActivity.scaleFactor));
}
gameActivity.mouse_x = CallbackBridge.mouseX;
gameActivity.mouse_y = CallbackBridge.mouseY;
}
} }
}; };
mouseThread.setPriority(Thread.MAX_PRIORITY);
mouseThread.start();
handler.postDelayed(handlerRunnable, 16);
//Initialize runnables to be used by the input system, avoiding generating one each time is better memory. //Initialize runnables to be used by the input system, avoiding generating one each time is better memory.
mouseRunnable = () -> CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
switchStateRunnable = () -> { switchStateRunnable = () -> {
currentMap.resetPressedState(); currentMap.resetPressedState();
if(lastGrabbingState){ if(lastGrabbingState){
@ -155,12 +108,34 @@ public class Gamepad {
mouseSensitivity = 14; //sensitivity in game doesn't need to be resolution dependent mouseSensitivity = 14; //sensitivity in game doesn't need to be resolution dependent
}; };
motionEventRunnable = () -> { }
updateDirectionalJoystick(lastMotionEvent);
updateMouseJoystick(lastMotionEvent);
updateAnalogTriggers(lastMotionEvent);
gamepadDpad.update(lastMotionEvent); private void tick(){
}; //update mouse position
if(lastHorizontalValue != 0 || lastVerticalValue != 0){
GamepadJoystick currentJoystick = lastGrabbingState ? leftJoystick : rightJoystick;
acceleration = (mouseMagnitude - currentJoystick.getDeadzone())/(1 - currentJoystick.getDeadzone());
acceleration = Math.pow(acceleration, mouseMaxAcceleration);
if(acceleration > 1) acceleration = 1;
CallbackBridge.mouseX += Math.cos(mouseAngle) * acceleration * mouseSensitivity;
CallbackBridge.mouseY -= Math.sin(mouseAngle) * acceleration * mouseSensitivity;
if(!lastGrabbingState){
CallbackBridge.mouseX = MathUtils.clamp(CallbackBridge.mouseX, 0, CallbackBridge.windowWidth);
CallbackBridge.mouseY = MathUtils.clamp(CallbackBridge.mouseY, 0, CallbackBridge.windowHeight);
placePointerView((int) (CallbackBridge.mouseX /gameActivity.scaleFactor), (int) (CallbackBridge.mouseY/gameActivity.scaleFactor));
}
gameActivity.mouse_x = CallbackBridge.mouseX;
gameActivity.mouse_y = CallbackBridge.mouseY;
//Send the mouse to the game
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
}
} }
@ -173,13 +148,14 @@ public class Gamepad {
} }
public void update(KeyEvent event){ public void update(KeyEvent event){
gamepadDpad.update(event);
sendButton(event); sendButton(event);
} }
public void update(MotionEvent event){ public void update(MotionEvent event){
//Motion events are taken into account every 1/60th second updateDirectionalJoystick(event);
lastMotionEvent = event; updateMouseJoystick(event);
updateAnalogTriggers(event);
sendButton(gamepadDpad.convertEvent(event));
} }
private void updateMouseJoystick(MotionEvent event){ private void updateMouseJoystick(MotionEvent event){
@ -204,8 +180,10 @@ public class Gamepad {
} }
private void updateAnalogTriggers(MotionEvent event){ private void updateAnalogTriggers(MotionEvent event){
getCurrentMap().TRIGGER_LEFT.update(event.getAxisValue(MotionEvent.AXIS_LTRIGGER) > 0.5); if(!hasDigitalTriggers){
getCurrentMap().TRIGGER_RIGHT.update(event.getAxisValue(MotionEvent.AXIS_RTRIGGER) > 0.5); getCurrentMap().TRIGGER_LEFT.update((event.getAxisValue(MotionEvent.AXIS_LTRIGGER) > 0.5) || (event.getAxisValue(MotionEvent.AXIS_BRAKE) > 0.5));
getCurrentMap().TRIGGER_RIGHT.update((event.getAxisValue(MotionEvent.AXIS_RTRIGGER) > 0.5) || (event.getAxisValue(MotionEvent.AXIS_GAS) > 0.5));
}
} }
public void notifyGUISizeChange(int newSize){ public void notifyGUISizeChange(int newSize){
@ -298,19 +276,19 @@ public class Gamepad {
break; break;
//DPAD //DPAD
case GamepadDpad.UP: case KeyEvent.KEYCODE_DPAD_UP:
getCurrentMap().DPAD_UP.update(event); getCurrentMap().DPAD_UP.update(event);
break; break;
case GamepadDpad.DOWN: case KeyEvent.KEYCODE_DPAD_DOWN:
getCurrentMap().DPAD_DOWN.update(event); getCurrentMap().DPAD_DOWN.update(event);
break; break;
case GamepadDpad.LEFT: case KeyEvent.KEYCODE_DPAD_LEFT:
getCurrentMap().DPAD_LEFT.update(event); getCurrentMap().DPAD_LEFT.update(event);
break; break;
case GamepadDpad.RIGHT: case KeyEvent.KEYCODE_DPAD_RIGHT:
getCurrentMap().DPAD_RIGHT.update(event); getCurrentMap().DPAD_RIGHT.update(event);
break; break;
case GamepadDpad.CENTER: case KeyEvent.KEYCODE_DPAD_CENTER:
getCurrentMap().DPAD_RIGHT.update(false); getCurrentMap().DPAD_RIGHT.update(false);
getCurrentMap().DPAD_LEFT.update(false); getCurrentMap().DPAD_LEFT.update(false);
getCurrentMap().DPAD_UP.update(false); getCurrentMap().DPAD_UP.update(false);

View File

@ -1,91 +1,77 @@
package net.kdt.pojavlaunch.customcontrols.gamepad; package net.kdt.pojavlaunch.customcontrols.gamepad;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import static android.view.InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC; import static android.view.InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC;
import static android.view.InputDevice.SOURCE_DPAD;
import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
/* /*
Code from the android documentation Reflection is used to avoid memory churning, and only has an negative impact at start
*/ */
public class GamepadDpad { public class GamepadDpad {
final static int UP = 999;
final static int LEFT = 9999;
final static int RIGHT = 99999;
final static int DOWN = 999999;
final static int CENTER = 9999999;
int pressedDirection = -1;
Gamepad parentPad; private int lastKeycode = KEYCODE_DPAD_CENTER;
KeyEvent dummyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, CENTER); private KeyEvent dummyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, lastKeycode);
Field eventCodeField; private Field eventCodeField;
private Field eventActionField;
{ {
try { try {
eventCodeField = dummyEvent.getClass().getDeclaredField("mKeyCode"); eventCodeField = dummyEvent.getClass().getDeclaredField("mKeyCode");
eventCodeField.setAccessible(true); eventCodeField.setAccessible(true);
eventActionField = dummyEvent.getClass().getDeclaredField("mAction");
eventActionField.setAccessible(true);
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public GamepadDpad(Gamepad parentPad){ public KeyEvent convertEvent(MotionEvent event){
this.parentPad = parentPad;
}
public void update(KeyEvent event){
//TODO check if the event is valid
if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
pressedDirection = LEFT;
} else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
pressedDirection = RIGHT;
} else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
pressedDirection = UP;
} else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
pressedDirection = DOWN;
} else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
pressedDirection = CENTER;
}
setDummyEventKeyCode(pressedDirection);
parentPad.sendButton(dummyEvent);
}
public void update(MotionEvent event){
//TODO check if the event is valid
// Use the hat axis value to find the D-pad direction // Use the hat axis value to find the D-pad direction
float xaxis = event.getAxisValue(MotionEvent.AXIS_HAT_X); float xaxis = event.getAxisValue(MotionEvent.AXIS_HAT_X);
float yaxis = event.getAxisValue(MotionEvent.AXIS_HAT_Y); float yaxis = event.getAxisValue(MotionEvent.AXIS_HAT_Y);
int action = KeyEvent.ACTION_DOWN;
// Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
// LEFT and RIGHT direction accordingly. // LEFT and RIGHT direction accordingly.
if (Float.compare(xaxis, -1.0f) == 0) { if (Float.compare(xaxis, -1.0f) == 0) {
pressedDirection = LEFT; lastKeycode = KEYCODE_DPAD_LEFT;
} else if (Float.compare(xaxis, 1.0f) == 0) { } else if (Float.compare(xaxis, 1.0f) == 0) {
pressedDirection = RIGHT; lastKeycode = KEYCODE_DPAD_RIGHT;
} }
// Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
// UP and DOWN direction accordingly. // UP and DOWN direction accordingly.
else if (Float.compare(yaxis, -1.0f) == 0) { else if (Float.compare(yaxis, -1.0f) == 0) {
pressedDirection = UP; lastKeycode = KEYCODE_DPAD_UP;
} else if (Float.compare(yaxis, 1.0f) == 0) { } else if (Float.compare(yaxis, 1.0f) == 0) {
pressedDirection = DOWN; lastKeycode = KEYCODE_DPAD_DOWN;
}else { }else {
pressedDirection = CENTER; //No keycode change
action = KeyEvent.ACTION_UP;
} }
setDummyEventKeyCode(pressedDirection); setDummyEventKeycode(lastKeycode);
parentPad.sendButton(dummyEvent); setDummyEventAction(action);
dummyEvent.setSource(SOURCE_DPAD);
return dummyEvent;
} }
private void setDummyEventKeyCode(int fakeKeycode){ private void setDummyEventKeycode(int fakeKeycode){
try { try {
eventCodeField.setInt(dummyEvent, fakeKeycode); eventCodeField.setInt(dummyEvent, fakeKeycode);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
@ -93,6 +79,14 @@ public class GamepadDpad {
} }
} }
private void setDummyEventAction(int action){
try {
eventActionField.setInt(dummyEvent, action);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static boolean isDpadEvent(MotionEvent event) { public static boolean isDpadEvent(MotionEvent event) {
// Check that input comes from a device with directional pads. // Check that input comes from a device with directional pads.
// And... also the joystick since it declares sometimes as a joystick. // And... also the joystick since it declares sometimes as a joystick.