feat: Fully use protobuf, fix controller issues and cleanup (#305)

## Description
### First commit
Restructured protobuf schemas to make them easier to use across
languages, switched to using them in-place of JSON for signaling as
well, so there's no 2 different message formats flying about. Few new
message types to deal with clients and nestri-servers better (not final
format, may see changes still).

General cleanup of dead/unused code along some bug squashing and package
updates.

TODO for future commits:
- [x] Fix additional controllers not doing inputs (possibly needs
vimputti changes)
- [x] ~~Restructure relay protocols code a bit, to reduce bloatiness of
the currently single file for them, more code re-use.~~
- Gonna keep this PR somewhat manageable without poking more at relay..
- [x] ~~Try to fix issue where with multiple clients, static stream
content causes video to freeze until there's some movement.~~
- Was caused by server tuned profile being `throughput-performance`,
causing CPU latency to be too high.
- [x] Ponder the orb


### Second + third commit
Redid the controller polling handling and fixed multi-controller
handling in vimputti and nestri code sides. Remove some dead relay code
as well to clean up the protocol source file, we'll revisit the meshing
functionality later.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added software rendering option and MangoHud runtime config;
controller sessions now support reconnection and batched state updates
with persistent session IDs.

* **Bug Fixes**
* Restored previously-filtered NES-like gamepads so they connect
correctly.

* **Chores**
* Modernized dependencies and protobuf tooling, migrated to
protobuf-based messaging and streaming, and removed obsolete CUDA build
steps.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
This commit is contained in:
Kristian Ollikainen
2025-11-08 13:10:28 +02:00
committed by GitHub
parent 32341574dc
commit d87a0b35dd
50 changed files with 4413 additions and 3883 deletions

View File

@@ -1,18 +1,14 @@
import {WebRTCStream} from "./webrtc-stream";
import {LatencyTracker} from "./latency";
import {ProtoMessageInput, ProtoMessageBase, ProtoMessageInputSchema} from "./proto/messages_pb";
import { WebRTCStream } from "./webrtc-stream";
import {
ProtoInput, ProtoInputSchema,
ProtoMouseKeyDown, ProtoMouseKeyDownSchema,
ProtoMouseKeyUp, ProtoMouseKeyUpSchema,
ProtoMouseMove,
ProtoMouseKeyDownSchema,
ProtoMouseKeyUpSchema,
ProtoMouseMoveSchema,
ProtoMouseWheel, ProtoMouseWheelSchema
ProtoMouseWheelSchema,
} from "./proto/types_pb";
import {mouseButtonToLinuxEventCode} from "./codes";
import {ProtoLatencyTracker, ProtoTimestampEntry} from "./proto/latency_tracker_pb";
import {create, toBinary} from "@bufbuild/protobuf";
import {timestampFromDate} from "@bufbuild/protobuf/wkt";
import { mouseButtonToLinuxEventCode } from "./codes";
import { create, toBinary } from "@bufbuild/protobuf";
import { createMessage } from "./utils";
import { ProtoMessageSchema } from "./proto/messages_pb";
interface Props {
webrtc: WebRTCStream;
@@ -24,7 +20,7 @@ export class Mouse {
protected canvas: HTMLCanvasElement;
protected connected!: boolean;
private sendInterval = 10 // 100 updates per second
private sendInterval = 10; // 100 updates per second
// Store references to event listeners
private readonly mousemoveListener: (e: MouseEvent) => void;
@@ -35,7 +31,7 @@ export class Mouse {
private readonly mouseupListener: (e: MouseEvent) => void;
private readonly mousewheelListener: (e: WheelEvent) => void;
constructor({webrtc, canvas}: Props) {
constructor({ webrtc, canvas }: Props) {
this.wrtc = webrtc;
this.canvas = canvas;
@@ -48,65 +44,56 @@ export class Mouse {
this.movementY += e.movementY;
};
this.mousedownListener = this.createMouseListener((e: any) => create(ProtoInputSchema, {
$typeName: "proto.ProtoInput",
inputType: {
case: "mouseKeyDown",
value: create(ProtoMouseKeyDownSchema, {
type: "MouseKeyDown",
key: this.keyToVirtualKeyCode(e.button)
}),
}
}));
this.mouseupListener = this.createMouseListener((e: any) => create(ProtoInputSchema, {
$typeName: "proto.ProtoInput",
inputType: {
case: "mouseKeyUp",
value: create(ProtoMouseKeyUpSchema, {
type: "MouseKeyUp",
key: this.keyToVirtualKeyCode(e.button)
}),
}
}));
this.mousewheelListener = this.createMouseListener((e: any) => create(ProtoInputSchema, {
$typeName: "proto.ProtoInput",
inputType: {
case: "mouseWheel",
value: create(ProtoMouseWheelSchema, {
type: "MouseWheel",
x: Math.round(e.deltaX),
y: Math.round(e.deltaY),
}),
}
}));
this.mousedownListener = this.createMouseListener((e: any) =>
create(ProtoMouseKeyDownSchema, {
key: this.keyToVirtualKeyCode(e.button),
}),
);
this.mouseupListener = this.createMouseListener((e: any) =>
create(ProtoMouseKeyUpSchema, {
key: this.keyToVirtualKeyCode(e.button),
}),
);
this.mousewheelListener = this.createMouseListener((e: any) =>
create(ProtoMouseWheelSchema, {
x: Math.round(e.deltaX),
y: Math.round(e.deltaY),
}),
);
this.run()
this.run();
this.startProcessing();
}
private run() {
//calls all the other functions
if (!document.pointerLockElement) {
console.log("no pointerlock")
console.log("no pointerlock");
if (this.connected) {
this.stop()
this.stop();
}
return;
}
if (document.pointerLockElement == this.canvas) {
this.connected = true
this.canvas.addEventListener("mousemove", this.mousemoveListener, {passive: false});
this.canvas.addEventListener("mousedown", this.mousedownListener, {passive: false});
this.canvas.addEventListener("mouseup", this.mouseupListener, {passive: false});
this.canvas.addEventListener("wheel", this.mousewheelListener, {passive: false});
this.connected = true;
this.canvas.addEventListener("mousemove", this.mousemoveListener, {
passive: false,
});
this.canvas.addEventListener("mousedown", this.mousedownListener, {
passive: false,
});
this.canvas.addEventListener("mouseup", this.mouseupListener, {
passive: false,
});
this.canvas.addEventListener("wheel", this.mousewheelListener, {
passive: false,
});
} else {
if (this.connected) {
this.stop()
this.stop();
}
}
}
private stop() {
@@ -128,79 +115,26 @@ export class Mouse {
}
private sendAggregatedMouseMove() {
const data = create(ProtoInputSchema, {
$typeName: "proto.ProtoInput",
inputType: {
case: "mouseMove",
value: create(ProtoMouseMoveSchema, {
type: "MouseMove",
x: Math.round(this.movementX),
y: Math.round(this.movementY),
}),
},
const data = create(ProtoMouseMoveSchema, {
x: Math.round(this.movementX),
y: Math.round(this.movementY),
});
// Latency tracking
const tracker = new LatencyTracker("input-mouse");
tracker.addTimestamp("client_send");
const protoTracker: ProtoLatencyTracker = {
$typeName: "proto.ProtoLatencyTracker",
sequenceId: tracker.sequence_id,
timestamps: [],
};
for (const t of tracker.timestamps) {
protoTracker.timestamps.push({
$typeName: "proto.ProtoTimestampEntry",
stage: t.stage,
time: timestampFromDate(t.time),
} as ProtoTimestampEntry);
}
const message: ProtoMessageInput = {
$typeName: "proto.ProtoMessageInput",
messageBase: {
$typeName: "proto.ProtoMessageBase",
payloadType: "input",
latency: protoTracker,
} as ProtoMessageBase,
data: data,
};
this.wrtc.sendBinary(toBinary(ProtoMessageInputSchema, message));
const message = createMessage(data, "input");
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, message));
}
// Helper function to create and return mouse listeners
private createMouseListener(dataCreator: (e: Event) => ProtoInput): (e: Event) => void {
private createMouseListener(
dataCreator: (e: Event) => any,
): (e: Event) => void {
return (e: Event) => {
e.preventDefault();
e.stopPropagation();
const data = dataCreator(e as any);
// Latency tracking
const tracker = new LatencyTracker("input-mouse");
tracker.addTimestamp("client_send");
const protoTracker: ProtoLatencyTracker = {
$typeName: "proto.ProtoLatencyTracker",
sequenceId: tracker.sequence_id,
timestamps: [],
};
for (const t of tracker.timestamps) {
protoTracker.timestamps.push({
$typeName: "proto.ProtoTimestampEntry",
stage: t.stage,
time: timestampFromDate(t.time),
} as ProtoTimestampEntry);
}
const message: ProtoMessageInput = {
$typeName: "proto.ProtoMessageInput",
messageBase: {
$typeName: "proto.ProtoMessageBase",
payloadType: "input",
latency: protoTracker,
} as ProtoMessageBase,
data: data,
};
this.wrtc.sendBinary(toBinary(ProtoMessageInputSchema, message));
const message = createMessage(data, "input");
this.wrtc.sendBinary(toBinary(ProtoMessageSchema, message));
};
}
@@ -213,4 +147,4 @@ export class Mouse {
private keyToVirtualKeyCode(code: number) {
return mouseButtonToLinuxEventCode[code] || undefined;
}
}
}