mirror of https://github.com/jetkvm/kvm.git
perf(usbgadget): reduce input latency by pre-opening HID files and removing throttling
Pre-open HID files during initialization to minimize I/O overhead during operation. Remove mouse event throttling mechanism to improve input responsiveness. Keep HID files open on write errors to avoid repeated file operations.
This commit is contained in:
parent
5f905e7eee
commit
4b693b4279
|
@ -201,6 +201,9 @@ func (u *UsbGadget) Init() error {
|
|||
return u.logError("unable to initialize USB stack", err)
|
||||
}
|
||||
|
||||
// Pre-open HID files to reduce input latency
|
||||
u.PreOpenHidFiles()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -203,8 +203,7 @@ func (u *UsbGadget) keyboardWriteHidFile(data []byte) error {
|
|||
_, err := u.keyboardHidFile.Write(data)
|
||||
if err != nil {
|
||||
u.logWithSuppression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0")
|
||||
u.keyboardHidFile.Close()
|
||||
u.keyboardHidFile = nil
|
||||
// Keep file open on write errors to reduce I/O overhead
|
||||
return err
|
||||
}
|
||||
u.resetLogSuppressionCounter("keyboardWriteHidFile")
|
||||
|
|
|
@ -77,8 +77,7 @@ func (u *UsbGadget) absMouseWriteHidFile(data []byte) error {
|
|||
_, err := u.absMouseHidFile.Write(data)
|
||||
if err != nil {
|
||||
u.logWithSuppression("absMouseWriteHidFile", 100, u.log, err, "failed to write to hidg1")
|
||||
u.absMouseHidFile.Close()
|
||||
u.absMouseHidFile = nil
|
||||
// Keep file open on write errors to reduce I/O overhead
|
||||
return err
|
||||
}
|
||||
u.resetLogSuppressionCounter("absMouseWriteHidFile")
|
||||
|
|
|
@ -60,15 +60,14 @@ func (u *UsbGadget) relMouseWriteHidFile(data []byte) error {
|
|||
var err error
|
||||
u.relMouseHidFile, err = os.OpenFile("/dev/hidg2", os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open hidg1: %w", err)
|
||||
return fmt.Errorf("failed to open hidg2: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err := u.relMouseHidFile.Write(data)
|
||||
if err != nil {
|
||||
u.logWithSuppression("relMouseWriteHidFile", 100, u.log, err, "failed to write to hidg2")
|
||||
u.relMouseHidFile.Close()
|
||||
u.relMouseHidFile = nil
|
||||
// Keep file open on write errors to reduce I/O overhead
|
||||
return err
|
||||
}
|
||||
u.resetLogSuppressionCounter("relMouseWriteHidFile")
|
||||
|
|
|
@ -95,6 +95,33 @@ func NewUsbGadget(name string, enabledDevices *Devices, config *Config, logger *
|
|||
return newUsbGadget(name, defaultGadgetConfig, enabledDevices, config, logger)
|
||||
}
|
||||
|
||||
// PreOpenHidFiles opens all HID files to reduce input latency
|
||||
func (u *UsbGadget) PreOpenHidFiles() {
|
||||
if u.enabledDevices.Keyboard {
|
||||
if err := u.openKeyboardHidFile(); err != nil {
|
||||
u.log.Debug().Err(err).Msg("failed to pre-open keyboard HID file")
|
||||
}
|
||||
}
|
||||
if u.enabledDevices.AbsoluteMouse {
|
||||
if u.absMouseHidFile == nil {
|
||||
var err error
|
||||
u.absMouseHidFile, err = os.OpenFile("/dev/hidg1", os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
u.log.Debug().Err(err).Msg("failed to pre-open absolute mouse HID file")
|
||||
}
|
||||
}
|
||||
}
|
||||
if u.enabledDevices.RelativeMouse {
|
||||
if u.relMouseHidFile == nil {
|
||||
var err error
|
||||
u.relMouseHidFile, err = os.OpenFile("/dev/hidg2", os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
u.log.Debug().Err(err).Msg("failed to pre-open relative mouse HID file")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newUsbGadget(name string, configMap map[string]gadgetConfigItem, enabledDevices *Devices, config *Config, logger *zerolog.Logger) *UsbGadget {
|
||||
if logger == nil {
|
||||
logger = defaultLogger
|
||||
|
|
69
jsonrpc.go
69
jsonrpc.go
|
@ -10,7 +10,6 @@ import (
|
|||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/webrtc/v4"
|
||||
|
@ -19,73 +18,7 @@ import (
|
|||
"github.com/jetkvm/kvm/internal/usbgadget"
|
||||
)
|
||||
|
||||
// Mouse event processing with single worker
|
||||
var (
|
||||
mouseEventChan = make(chan mouseEventData, 100) // Buffered channel for mouse events
|
||||
mouseWorkerOnce sync.Once
|
||||
)
|
||||
|
||||
type mouseEventData struct {
|
||||
message webrtc.DataChannelMessage
|
||||
session *Session
|
||||
}
|
||||
|
||||
// startMouseWorker starts a single worker goroutine for processing mouse events
|
||||
func startMouseWorker() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(16 * time.Millisecond) // ~60 FPS
|
||||
defer ticker.Stop()
|
||||
|
||||
var latestMouseEvent *mouseEventData
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-mouseEventChan:
|
||||
// Always keep the latest mouse event
|
||||
latestMouseEvent = &event
|
||||
|
||||
case <-ticker.C:
|
||||
// Process the latest mouse event at regular intervals
|
||||
if latestMouseEvent != nil {
|
||||
onRPCMessage(latestMouseEvent.message, latestMouseEvent.session)
|
||||
latestMouseEvent = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// onRPCMessageThrottled handles RPC messages with special throttling for mouse events
|
||||
func onRPCMessageThrottled(message webrtc.DataChannelMessage, session *Session) {
|
||||
var request JSONRPCRequest
|
||||
err := json.Unmarshal(message.Data, &request)
|
||||
if err != nil {
|
||||
onRPCMessage(message, session)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this is a mouse event that should be throttled
|
||||
if isMouseEvent(request.Method) {
|
||||
// Start the mouse worker if not already started
|
||||
mouseWorkerOnce.Do(startMouseWorker)
|
||||
|
||||
// Send to mouse worker (non-blocking)
|
||||
select {
|
||||
case mouseEventChan <- mouseEventData{message: message, session: session}:
|
||||
// Event queued successfully
|
||||
default:
|
||||
// Channel is full, drop the event (this prevents blocking)
|
||||
}
|
||||
} else {
|
||||
// Non-mouse events are processed immediately
|
||||
go onRPCMessage(message, session)
|
||||
}
|
||||
}
|
||||
|
||||
// isMouseEvent checks if the RPC method is a mouse-related event
|
||||
func isMouseEvent(method string) bool {
|
||||
return method == "absMouseReport" || method == "relMouseReport"
|
||||
}
|
||||
// Direct RPC message handling for optimal input responsiveness
|
||||
|
||||
type JSONRPCRequest struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
|
|
|
@ -119,7 +119,7 @@ func newSession(config SessionConfig) (*Session, error) {
|
|||
case "rpc":
|
||||
session.RPCChannel = d
|
||||
d.OnMessage(func(msg webrtc.DataChannelMessage) {
|
||||
go onRPCMessageThrottled(msg, session)
|
||||
go onRPCMessage(msg, session)
|
||||
})
|
||||
triggerOTAStateUpdate()
|
||||
triggerVideoStateUpdate()
|
||||
|
|
Loading…
Reference in New Issue