Latenz

Warum sich Dish wie ein Kabel anfühlt.

Latenz ist die eine Metrik, die bei einem Controller zählt. Dish wurde vom ersten Tag an darum herum gebaut.

Das Latenzbudget

Ein kabelgebundener Xbox-Controller pollt unter Windows mit 250 Hz. Das heißt, dein Spiel kann einen Tastendruck bis zu 4 ms nach dem Ereignis lesen, allein wegen der Polling-Rate. Pack einen 60-fps-Frame oben drauf und der Worst-Case Finger-zu-Pixel bei einem kabelgebundenen Pad landet bei rund 20 ms.

Dish wurde so gebaut, dass es diesem Budget in einem normalen Wi-Fi-6-LAN fast nichts hinzufügt. Hier ist, wo jede Millisekunde hingeht:

Stufe Typische Zeit
Touch- / Tasten-Event taucht im OS auf < 1 ms
Dish baut + verschlüsselt 12-byte-Paket < 0.1 ms
UDP-Send + Wi-Fi-Airtime + UDP-Recv 1–4 ms (5 GHz)
Satellite verifiziert + injiziert via ViGEmBus < 0.5 ms
Spiel pollt den neuen Pad-Zustand 0–4 ms (vom Spiel abhängig)
Frame an den Monitor gerendert 0–16 ms (von fps abhängig)
Ende-zu-Ende (typisch) ~6–25 ms

Zum Vergleich: Bluetooth-Pads addieren typischerweise 8 bis 15 ms auf ihre kabelgebundenen Geschwister. Der Worst-Case von Dish in einem gesunden LAN ist ungefähr der Best-Case eines Bluetooth-Pads.

Wie wir es eng halten

  • Keine Queue, keine Async-Runtime. Der Input-Event-Handler ruft sendto inline auf. Keine Producer/Consumer-Queue, kein Event-Loop-Hop, kein Combine, keine Kotlin-Coroutine. Der Input-Thread ist der Netzwerk-Thread.
  • Rohes UDP. Kein TCP, keine WebSockets, kein gRPC, kein QUIC, nicht mal NWConnection auf Apple-Plattformen. Rohe POSIX-Sockets, damit wir IP_TOS selbst setzen können.
  • DSCP-EF-Markierung. Ausgehende Pakete sind als DSCP-Klasse EF (0xB8) markiert. QoS-fähige Router und Access Points schieben sie vor Bulk-Traffic.
  • Winzige Pakete. 12 Bytes Payload, rund 50 Bytes auf der Leitung, wenn UDP-, IP- und 802.11-Header drin sind. Ein Wi-Fi-Frame.
  • Null Allocations auf dem Hot-Path. Buffer sind Stack oder vorab allokiert. Keine GC-Pause kann einen Paketsend strecken.
  • Direkte Kernel-Injektion. Satellite ruft DeviceIoControl direkt in ViGEmBus hinein auf. Kein DLL-Marshalling, kein IPC, kein Service-Round-Trip. Der Empfänger-Hot-Path ist drei Syscalls mit null Allocations: recvfrom()memcpy()DeviceIoControl().
  • Time-Critical-Empfangs-Thread. Satellite pinnt seinen UDP-Empfangs-Thread auf THREAD_PRIORITY_TIME_CRITICAL unter Windows und auf Core-0-Affinität, sodass ein außer Kontrolle geratener Browser-Tab deine Inputs nicht aushungern kann.

Was Latenz in der Praxis frisst

Jeder „Dish fühlt sich laggy an"-Report, den wir gesehen haben, lässt sich auf einen dieser Punkte zurückführen. Keiner ist Dishs Schuld, alle haben einen Fix:

  • 2,4-GHz-Wi-Fi. Mikrowellenstörung, Nachbarstau, niedrigere Bandbreitendecke. Fix: Pack sowohl Smartphone als auch Gaming-PC auf 5 GHz oder 6 GHz.
  • Wi-Fi-Power-Save auf Android. Manche Android-Smartphones verzögern ausgehende Pakete um 30 bis 100 ms, sobald der Bildschirm ruht. Dish hält alle 2 Sekunden einen Heartbeat – klein genug, um auf dem Funk gratis zu sein, häufig genug, um das OS davon abzuhalten, den Wi-Fi-Chip schlafen zu legen.
  • Mesh-Repeater. Jeder Hop addiert 1 bis 3 ms. Park Satellite auf einem Knoten, der am Haupt-Router verkabelt ist, wenn möglich.
  • USB-C-Dock mit Ethernet an einem Thunderbolt-Hub. Manche billigen Docks puffern. Nutze stattdessen das eingebaute Wi-Fi des Laptops oder einen Passthrough-Adapter.
  • 120 Hz vs. 60 Hz. Ein 120-Hz-Monitor halbiert das Worst-Case-Frame-Time-Stück des Budgets. Gratis 8 ms.

Selber messen?

Die Web-UI von Satellite unter localhost:9877 zeigt Live-RTT pro Verbindung, die letzten Loop-Mikrosekunden, die Spitzen-Loop-Mikrosekunden, Submit-Erfolg- und -Fehler-Zähler und Replay-Drop-Zähler. Die /debug-Seite ist das vollständige Performance-Readout des Empfängers. Falls du irgendetwas über 10 ms RTT im selben Wi-Fi siehst, spinnt etwas anderes in deinem Netzwerk – eröffne ein Issue mit einem Screenshot der Debug-Seite und wir helfen dir beim Suchen.