Why is this input device read loop slowing down over time?

182 Views Asked by At

Background: I wrote a python script that uses evdev to read from the gamepad on a Steam Deck and use it to send keypresses to a game. From a functionality standpoint, it does everything perfectly.

The Problem: The script slows down over time: after it has run for 15 minutes the game starts stuttering, and at the 30 minute mark the game is juddering to the point of unplayability. The game runs smoothly when I'm not using the script, so the script is definitely the part that's at fault; I just don't know why yet (because I'm a python noob).

What I've tried: I've tried garbage-collecting on every thousand-th iteration of the loop, in case the slowdown is due to eating up memory. I've tried using pypy to make it run faster (but haven't been able to get pypy working on Steam Deck, and it would only be masking the underlying problem anyway). I've tried reading all the posts under the evdev tag on StackOverflow. Now I'm asking a question because I wasn't able to find anything that could solve the slowdown problem.

The Code:

#!/usr/bin/env python

import evdev
import struct
import os
import sys
import time
import subprocess
import gc

from evdev import ecodes

GAME_PROCESS_ID = sys.argv[1]
TRIGGER_THRESHOLD = 70
GAMEPAD_NAME = "Microsoft X-Box 360 pad"

devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
for device in devices:
    if GAMEPAD_NAME in device.name:
        GAMEPAD_PATH = device.path

device = evdev.InputDevice(GAMEPAD_PATH)

isLeftTriggerDown = False
isRightTriggerDown = False
isStartDown = False
isCtrlDown = False
isDpadUpDown = False
isDpadDownDown = False
isDpadLeftDown = False
isDpadRightDown = False

southKey = "Return"
westKey = "KP_Subtract"
eastKey = "Escape"
northKey = "KP_Add"
dpadUpKey = "F1"
dpadRightKey = "F2"
dpadDownKey = "F3"
dpadLeftKey = "F4"
selectKey = "F9"
startKey = "F10"

def sendKeyDown(keycode):
    subprocess.call(["xdotool", "keydown", "--window", GAME_PROCESS_ID, keycode])

def sendKeyUp(keycode):
    subprocess.call(["xdotool", "keyup", "--window", GAME_PROCESS_ID, keycode])

iterations = 0
for event in device.read_loop():
    iterations = iterations + 1
    if iterations > 1000:
        gc.collect()
        iterations = 0

    if event.type == ecodes.EV_ABS:
        if event.code == ecodes.ABS_Z:
            if event.value < TRIGGER_THRESHOLD:
                if isLeftTriggerDown:
                    isLeftTriggerDown = False
                    sendKeyUp("F11")
            else:
                if not isLeftTriggerDown:
                    isLeftTriggerDown = True
                    if not isCtrlDown:
                        isCtrlDown = True
                        sendKeyDown("Control_L")
                    sendKeyDown("F11")

        if event.code == ecodes.ABS_RZ:
            if event.value < TRIGGER_THRESHOLD:
                if isRightTriggerDown:
                    isRightTriggerDown = False
                    sendKeyUp("F12")
            else:
                if not isRightTriggerDown:
                    isRightTriggerDown = True
                    if not isCtrlDown:
                        isCtrlDown = True
                        sendKeyDown("Control_L")
                    sendKeyDown("F12")

        if isCtrlDown and not (isLeftTriggerDown or isRightTriggerDown):
            isCtrlDown = False
            sendKeyUp("Control_L")

        if isLeftTriggerDown or isRightTriggerDown:
            southKey = "F5"
            westKey = "F6"
            eastKey = "F7"
            northKey = "F8"
        else:
            southKey = "Return"
            westKey = "KP_Subtract"
            eastKey = "Escape"
            northKey = "KP_Add"

        if isLeftTriggerDown or isRightTriggerDown or isStartDown:
            if event.code == ecodes.ABS_HAT0X:
                if event.value == 1:
                    if not isDpadRightDown:
                        sendKeyDown(dpadRightKey)
                    isDpadRightDown = True
                    isDpadLeftDown = False
                    if isDpadLeftDown:
                        sendKeyUp(dpadLeftKey)
                    if isDpadUpDown:
                        sendKeyUp(dpadUpKey)
                    if isDpadLeftDown:
                        sendKeyUp(dpadDownKey)
                elif event.value == -1:
                    if not isDpadLeftDown:
                        sendKeyDown(dpadLeftKey)
                    isDpadLeftDown = True
                    isDpadRightDown = False
                    if isDpadRightDown:
                        sendKeyUp(dpadRightKey)
                    if isDpadUpDown:
                        sendKeyUp(dpadUpKey)
                    if isDpadDownDown:
                        sendKeyUp(dpadDownKey)
                else:
                    if isDpadRightDown:
                        sendKeyUp(dpadRightKey)
                    if isDpadLeftDown:
                        sendKeyUp(dpadLeftKey)
                    isDpadRightDown = False
                    isDpadLeftDown = False

            if event.code == ecodes.ABS_HAT0Y:
                if event.value == 1:
                    if not isDpadDownDown:
                        sendKeyDown(dpadDownKey)
                    isDpadDownDown = True
                    isDpadUpDown = False
                    if isDpadLeftDown:
                        sendKeyUp(dpadLeftKey)
                    if isDpadUpDown:
                        sendKeyUp(dpadUpKey)
                    if isDpadRightDown:
                        sendKeyUp(dpadRightKey)
                elif event.value == -1:
                    if not isDpadUpDown:
                        sendKeyDown(dpadUpKey)
                    isDpadUpDown = True
                    isDpadDownDown = False
                    if isDpadLeftDown:
                        sendKeyUp(dpadLeftKey)
                    if isDpadRightDown:
                        sendKeyUp(dpadRightKey)
                    if isDpadLeftDown:
                        sendKeyUp(dpadDownKey)
                else:
                    if isDpadDownDown:
                        sendKeyUp(dpadDownKey)
                    if isDpadUpDown:
                        sendKeyUp(dpadUpKey)
                    isDpadDownDown = False
                    isDpadUpDown = False

    if event.type == ecodes.EV_KEY:
        # For some reason, BTN_NORTH and BTN_WEST are switched on Steam Decks
        if event.code == ecodes.BTN_NORTH:
            if event.value == 1:
                sendKeyDown(westKey)
            else:
                sendKeyUp(westKey)
        if event.code == ecodes.BTN_SOUTH:
            if event.value == 1:
                sendKeyDown(southKey)
            else:
                sendKeyUp(southKey)
        if event.code == ecodes.BTN_EAST:
            if event.value == 1:
                sendKeyDown(eastKey)
            else:
                sendKeyUp(eastKey)
        # For some reason, BTN_NORTH and BTN_WEST are switched on Steam Decks
        if event.code == ecodes.BTN_WEST:
            if event.value == 1:
                sendKeyDown(northKey)
            else:
                sendKeyUp(northKey)

        if event.code == ecodes.BTN_START:
            isStartDown = event.value == 1
            if event.value == 1:
                sendKeyDown("Control_L")
                sendKeyDown(startKey)
            else:
                sendKeyUp(startKey)
                sendKeyUp("Control_L")
        if event.code == ecodes.BTN_SELECT:
            if event.value == 1:
                sendKeyDown("Control_L")
                sendKeyDown(selectKey)
            else:
                sendKeyUp(selectKey)
                sendKeyUp("Control_L")

Possible Suspects: The two things I suspect could be the cause of the slowdown are either 1) the subprocesses being called by sendKeyDown() and sendKeyUp() are for some reason not closing after sending a key, or 2) events objects are piling up because for event in device.read_loop(): is causing the events to not be garbage collected. I'm not sure how to verify these possibilities: it shouldn't be #1 because leaving the game (and gamepad) idle for 15+ minutes still results in slowdown, and it shouldn't be #2 because calling gc.collect() should take care that, right?

Any ideas what might be causing this to slow down over time, and/or how to test likely candidates?

0

There are 0 best solutions below