diff --git a/containerfiles/runner-builder.Containerfile b/containerfiles/runner-builder.Containerfile index e6613bb4..d18d94cd 100644 --- a/containerfiles/runner-builder.Containerfile +++ b/containerfiles/runner-builder.Containerfile @@ -41,7 +41,7 @@ RUN --mount=type=cache,target=/var/cache/pacman/pkg \ pacman -Sy --noconfirm lib32-gcc-libs # Clone repository -RUN git clone --depth 1 --rev "f2f21561ddcb814d74455311969d3e8934b052c6" https://github.com/DatCaptainHorse/vimputti.git +RUN git clone --depth 1 --rev "2fde5376b6b9a38cdbd94ccc6a80c9d29a81a417" https://github.com/DatCaptainHorse/vimputti.git #-------------------------------------------------------------------- FROM vimputti-manager-deps AS vimputti-manager-planner diff --git a/packages/input/src/controller.ts b/packages/input/src/controller.ts index 6742f6e5..ba71c69d 100644 --- a/packages/input/src/controller.ts +++ b/packages/input/src/controller.ts @@ -3,10 +3,8 @@ import { WebRTCStream } from "./webrtc-stream"; import { ProtoControllerAttachSchema, ProtoControllerDetachSchema, - ProtoControllerButtonSchema, - ProtoControllerTriggerSchema, - ProtoControllerAxisSchema, - ProtoControllerStickSchema, + ProtoControllerStateBatchSchema, + ProtoControllerStateBatch, ProtoControllerRumble, } from "./proto/types_pb"; import { create, toBinary, fromBinary } from "@bufbuild/protobuf"; @@ -19,6 +17,7 @@ interface Props { } interface GamepadState { + previousButtonState: Map; buttonState: Map; leftTrigger: number; rightTrigger: number; @@ -30,11 +29,17 @@ interface GamepadState { dpadY: number; } +enum PollState { + IDLE, + RUNNING, +} + export class Controller { protected wrtc: WebRTCStream; protected connected: boolean = false; protected gamepad: Gamepad | null = null; - protected lastState: GamepadState = { + protected state: GamepadState = { + previousButtonState: new Map(), buttonState: new Map(), leftTrigger: 0, rightTrigger: 0, @@ -48,22 +53,34 @@ export class Controller { // TODO: As user configurable, set quite low now for decent controllers (not Nintendo ones :P) protected stickDeadzone: number = 2048; // 2048 / 32768 = ~0.06 (6% of stick range) - private updateInterval = 10.0; // 100 updates per second - private isIdle: boolean = true; + // Polling configuration + private readonly FULL_RATE_MS = 10; // 100 UPS + private readonly IDLE_THRESHOLD = 100; // ms before considering idle/hands off controller + private readonly FULL_INTERVAL= 250; // ms before sending full state occassionally, to verify inputs are synced + + // Polling state + private pollingState: PollState = PollState.IDLE; private lastInputTime: number = Date.now(); - private idleUpdateInterval: number = 150.0; // ~6-7 updates per second for keep-alive packets - private inputDetected: boolean = false; - private lastFullStateSend: number = Date.now(); - private fullStateSendInterval: number = 500.0; // send full state every 0.5 seconds (helps packet loss) - private forceFullStateSend: boolean = false; + private lastFullTime: number = Date.now(); + private pollInterval: any = null; + + // Controller batch vars + private sequence: number = 0; + private readonly CHANGED_BUTTONS_STATE = 1 << 0; + private readonly CHANGED_LEFT_STICK_X = 1 << 1; + private readonly CHANGED_LEFT_STICK_Y = 1 << 2; + private readonly CHANGED_RIGHT_STICK_X = 1 << 3; + private readonly CHANGED_RIGHT_STICK_Y = 1 << 4; + private readonly CHANGED_LEFT_TRIGGER = 1 << 5; + private readonly CHANGED_RIGHT_TRIGGER = 1 << 6; + private readonly CHANGED_DPAD_X = 1 << 7; + private readonly CHANGED_DPAD_Y = 1 << 8; private _dcHandler: ((data: ArrayBuffer) => void) | null = null; constructor({ webrtc, e }: Props) { this.wrtc = webrtc; - this.updateInterval = 1000 / webrtc.currentFrameRate; - // Get vendor of gamepad from id string (i.e. "... Vendor: 054c Product: 09cc") const vendorMatch = e.gamepad.id.match(/Vendor:\s?([0-9a-fA-F]{4})/); const vendorId = vendorMatch ? vendorMatch[1].toLowerCase() : "unknown"; @@ -89,6 +106,7 @@ export class Controller { console.log( `Gamepad connected: ${e.gamepad.id}, local slot ${e.gamepad.index}, msg: ${attachMsg.sessionSlot}`, ); + this.run(); } } catch (err) { console.error("Error decoding datachannel message:", err); @@ -162,266 +180,283 @@ export class Controller { return ((value - fromMin) * (toMax - toMin)) / (fromMax - fromMin) + toMin; } - private pollGamepad() { - // Get updated gamepad state - const gamepads = navigator.getGamepads(); - - // Periodically force send full state to clear stuck inputs - if (Date.now() - this.lastFullStateSend > this.fullStateSendInterval) { - this.forceFullStateSend = true; - this.lastFullStateSend = Date.now(); + private restartPolling() { + // Clear existing interval + if (this.pollInterval) { + clearInterval(this.pollInterval); + this.pollInterval = null; } - if (this.gamepad) { - if (gamepads[this.gamepad.index]) { - this.gamepad = gamepads[this.gamepad!.index]; - /* Button handling */ - this.gamepad.buttons.forEach((button, index) => { - // Ignore d-pad buttons (12-15) as we handle those as axis - if (index >= 12 && index <= 15) return; - // ignore trigger buttons (6-7) as we handle those as axis - if (index === 6 || index === 7) return; - // If state differs, send - if (button.pressed !== this.lastState.buttonState.get(index) || this.forceFullStateSend) { - const linuxCode = this.controllerButtonToVirtualKeyCode(index); - if (linuxCode === undefined) { - // Skip unmapped button index - this.lastState.buttonState.set(index, button.pressed); - return; - } + // Restart with active polling + this.pollingState = PollState.RUNNING; + this.lastInputTime = Date.now(); - const buttonMessage = createMessage( - create(ProtoControllerButtonSchema, { - sessionSlot: this.gamepad.index, - sessionId: this.wrtc.getSessionID(), - button: linuxCode, - pressed: button.pressed, - }), - "controllerInput", - ); - this.wrtc.sendBinary(toBinary(ProtoMessageSchema, buttonMessage)); - this.inputDetected = true; - // Store button state - this.lastState.buttonState.set(index, button.pressed); - } - }); + // Start interval + this.pollInterval = setInterval( + () => this.pollGamepad(), + this.FULL_RATE_MS, + ); + } - /* Trigger handling */ - // map trigger value from 0.0 to 1.0 to -32768 to 32767 - const leftTrigger = Math.round( - this.remapFromTo( - this.gamepad.buttons[6]?.value ?? 0, - 0, - 1, - -32768, - 32767, - ), - ); - // If state differs, send - if (leftTrigger !== this.lastState.leftTrigger || this.forceFullStateSend) { - const triggerMessage = createMessage( - create(ProtoControllerTriggerSchema, { - sessionSlot: this.gamepad.index, - sessionId: this.wrtc.getSessionID(), - trigger: 0, // 0 = left, 1 = right - value: leftTrigger, - }), - "controllerInput", - ); - this.wrtc.sendBinary(toBinary(ProtoMessageSchema, triggerMessage)); - this.inputDetected = true; - this.lastState.leftTrigger = leftTrigger; - } - const rightTrigger = Math.round( - this.remapFromTo( - this.gamepad.buttons[7]?.value ?? 0, - 0, - 1, - -32768, - 32767, - ), - ); - // If state differs, send - if (rightTrigger !== this.lastState.rightTrigger || this.forceFullStateSend) { - const triggerMessage = createMessage( - create(ProtoControllerTriggerSchema, { - sessionSlot: this.gamepad.index, - sessionId: this.wrtc.getSessionID(), - trigger: 1, // 0 = left, 1 = right - value: rightTrigger, - }), - "controllerInput", - ); - this.wrtc.sendBinary(toBinary(ProtoMessageSchema, triggerMessage)); - this.inputDetected = true; - this.lastState.rightTrigger = rightTrigger; - } + private pollGamepad() { + if (!this.connected || !this.gamepad) return; - /* DPad handling */ - // We send dpad buttons as axis values -1 to 1 for left/up, right/down - const dpadLeft = this.gamepad.buttons[14]?.pressed ? 1 : 0; - const dpadRight = this.gamepad.buttons[15]?.pressed ? 1 : 0; - const dpadX = dpadLeft ? -1 : dpadRight ? 1 : 0; - if (dpadX !== this.lastState.dpadX || this.forceFullStateSend) { - const dpadMessage = createMessage( - create(ProtoControllerAxisSchema, { - sessionSlot: this.gamepad.index, - sessionId: this.wrtc.getSessionID(), - axis: 0, // 0 = dpadX, 1 = dpadY - value: dpadX, - }), - "controllerInput", - ); - this.wrtc.sendBinary(toBinary(ProtoMessageSchema, dpadMessage)); - this.inputDetected = true; - this.lastState.dpadX = dpadX; - } + const gamepads = navigator.getGamepads(); + if (!gamepads[this.gamepad.index]) return; - const dpadUp = this.gamepad.buttons[12]?.pressed ? 1 : 0; - const dpadDown = this.gamepad.buttons[13]?.pressed ? 1 : 0; - const dpadY = dpadUp ? -1 : dpadDown ? 1 : 0; - if (dpadY !== this.lastState.dpadY || this.forceFullStateSend) { - const dpadMessage = createMessage( - create(ProtoControllerAxisSchema, { - sessionSlot: this.gamepad.index, - sessionId: this.wrtc.getSessionID(), - axis: 1, // 0 = dpadX, 1 = dpadY - value: dpadY, - }), - "controllerInput", - ); - this.wrtc.sendBinary(toBinary(ProtoMessageSchema, dpadMessage)); - this.inputDetected = true; - this.lastState.dpadY = dpadY; - } + this.gamepad = gamepads[this.gamepad.index]; - /* Stick handling */ - // stick values need to be mapped from -1.0 to 1.0 to -32768 to 32767 - const leftX = this.remapFromTo( - this.gamepad.axes[0] ?? 0, - -1, - 1, - -32768, - 32767, - ); - const leftY = this.remapFromTo( - this.gamepad.axes[1] ?? 0, - -1, - 1, - -32768, - 32767, - ); - // Apply deadzone - const sendLeftX = - Math.abs(leftX) > this.stickDeadzone ? Math.round(leftX) : 0; - const sendLeftY = - Math.abs(leftY) > this.stickDeadzone ? Math.round(leftY) : 0; - // if outside deadzone, send normally if changed - // if moves inside deadzone, zero it if not inside deadzone last time - if ( - sendLeftX !== this.lastState.leftX || - sendLeftY !== this.lastState.leftY || this.forceFullStateSend - ) { - const stickMessage = createMessage( - create(ProtoControllerStickSchema, { - sessionSlot: this.gamepad.index, - sessionId: this.wrtc.getSessionID(), - stick: 0, // 0 = left, 1 = right - x: sendLeftX, - y: sendLeftY, - }), - "controllerInput", - ); - this.wrtc.sendBinary(toBinary(ProtoMessageSchema, stickMessage)); - this.inputDetected = true; - this.lastState.leftX = sendLeftX; - this.lastState.leftY = sendLeftY; - } + // Collect state changes + const changedFields = this.collectStateChanges(); - const rightX = this.remapFromTo( - this.gamepad.axes[2] ?? 0, - -1, - 1, - -32768, - 32767, - ); - const rightY = this.remapFromTo( - this.gamepad.axes[3] ?? 0, - -1, - 1, - -32768, - 32767, - ); - // Apply deadzone - const sendRightX = - Math.abs(rightX) > this.stickDeadzone ? Math.round(rightX) : 0; - const sendRightY = - Math.abs(rightY) > this.stickDeadzone ? Math.round(rightY) : 0; - if ( - sendRightX !== this.lastState.rightX || - sendRightY !== this.lastState.rightY || this.forceFullStateSend - ) { - const stickMessage = createMessage( - create(ProtoControllerStickSchema, { - sessionSlot: this.gamepad.index, - sessionId: this.wrtc.getSessionID(), - stick: 1, // 0 = left, 1 = right - x: sendRightX, - y: sendRightY, - }), - "controllerInput", - ); - this.wrtc.sendBinary(toBinary(ProtoMessageSchema, stickMessage)); - this.inputDetected = true; - this.lastState.rightX = sendRightX; - this.lastState.rightY = sendRightY; - } + // Send batched changes update if there's changes + if (changedFields > 0) { + let send_type = 1; + const timeSinceFull = Date.now() - this.lastFullTime; + if (timeSinceFull > this.FULL_INTERVAL) { + send_type = 0; + this.lastFullTime = Date.now(); + } + + this.sendBatchedState(changedFields, send_type); + this.lastInputTime = Date.now(); + if (this.pollingState !== PollState.RUNNING) { + this.pollingState = PollState.RUNNING; } } - this.forceFullStateSend = false; + const timeSinceInput = Date.now() - this.lastInputTime; + if (timeSinceInput > this.IDLE_THRESHOLD) { + // Changing from running to idle.. + if (this.pollingState === PollState.RUNNING) { + // Send full state on idle assumption + this.sendBatchedState(0xFF, 0); + this.pollingState = PollState.IDLE; + } + } + + this.state.buttonState.forEach((b, i) => + this.state.previousButtonState.set(i, b), + ); } - private loopInterval: any = null; + private collectStateChanges(): number { + let changedFields = 0; + + // Collect analog values + const leftTrigger = Math.round( + this.remapFromTo( + this.gamepad.buttons[6]?.value ?? 0, + 0, + 1, + -32768, + 32767, + ), + ); + const rightTrigger = Math.round( + this.remapFromTo( + this.gamepad.buttons[7]?.value ?? 0, + 0, + 1, + -32768, + 32767, + ), + ); + + const leftX = this.remapFromTo( + this.gamepad.axes[0] ?? 0, + -1, + 1, + -32768, + 32767, + ); + const leftY = this.remapFromTo( + this.gamepad.axes[1] ?? 0, + -1, + 1, + -32768, + 32767, + ); + const sendLeftX = + Math.abs(leftX) > this.stickDeadzone ? Math.round(leftX) : 0; + const sendLeftY = + Math.abs(leftY) > this.stickDeadzone ? Math.round(leftY) : 0; + + const rightX = this.remapFromTo( + this.gamepad.axes[2] ?? 0, + -1, + 1, + -32768, + 32767, + ); + const rightY = this.remapFromTo( + this.gamepad.axes[3] ?? 0, + -1, + 1, + -32768, + 32767, + ); + const sendRightX = + Math.abs(rightX) > this.stickDeadzone ? Math.round(rightX) : 0; + const sendRightY = + Math.abs(rightY) > this.stickDeadzone ? Math.round(rightY) : 0; + + const dpadX = + (this.gamepad.buttons[14]?.pressed ? -1 : 0) + + (this.gamepad.buttons[15]?.pressed ? 1 : 0); + const dpadY = + (this.gamepad.buttons[12]?.pressed ? -1 : 0) + + (this.gamepad.buttons[13]?.pressed ? 1 : 0); + + // Check what changed + for (let i = 0; i < this.gamepad.buttons.length; i++) { + if (i >= 6 && i <= 7) continue; // Skip triggers + if (i >= 12 && i <= 15) continue; // Skip d-pad + if (this.state.buttonState.get(i) !== this.gamepad.buttons[i].pressed) { + changedFields |= this.CHANGED_BUTTONS_STATE; + } + this.state.buttonState.set(i, this.gamepad.buttons[i].pressed); + } + if (leftTrigger !== this.state.leftTrigger) { + changedFields |= this.CHANGED_LEFT_TRIGGER; + } + this.state.leftTrigger = leftTrigger; + if (rightTrigger !== this.state.rightTrigger) { + changedFields |= this.CHANGED_RIGHT_TRIGGER; + } + this.state.rightTrigger = rightTrigger; + if (sendLeftX !== this.state.leftX) { + changedFields |= this.CHANGED_LEFT_STICK_X; + } + this.state.leftX = sendLeftX; + if (sendLeftY !== this.state.leftY) { + changedFields |= this.CHANGED_LEFT_STICK_Y; + } + this.state.leftY = sendLeftY; + if (sendRightX !== this.state.rightX) { + changedFields |= this.CHANGED_RIGHT_STICK_X; + } + this.state.rightX = sendRightX; + if (sendRightY !== this.state.rightY) { + changedFields |= this.CHANGED_RIGHT_STICK_Y; + } + this.state.rightY = sendRightY; + if (dpadX !== this.state.dpadX) { + changedFields |= this.CHANGED_DPAD_X; + } + this.state.dpadX = dpadX; + if (dpadY !== this.state.dpadY) { + changedFields |= this.CHANGED_DPAD_Y; + } + this.state.dpadY = dpadY; + + return changedFields; + } + + private sendBatchedState(changedFields: number, updateType: number) { + // @ts-ignore + let message: ProtoControllerStateBatch = { + sessionSlot: this.gamepad.index, + sessionId: this.wrtc.getSessionID(), + updateType: updateType, + sequence: this.sequence++, + }; + + // For FULL_STATE, include everything + if (updateType === 0) { + message.changedFields = 0xFF; + + message.buttonChangedMask = Object.fromEntries( + Array.from(this.state.buttonState).map(([key, value]) => { + return [this.controllerButtonToVirtualKeyCode(key), value]; + }), + ); + message.leftStickX = this.state.leftX; + message.leftStickY = this.state.leftY; + message.rightStickX = this.state.rightX; + message.rightStickY = this.state.rightY; + message.leftTrigger = this.state.leftTrigger; + message.rightTrigger = this.state.rightTrigger; + message.dpadX = this.state.dpadX; + message.dpadY = this.state.dpadY; + } + // For DELTA, only include changed fields + else { + message.changedFields = changedFields; + + if (changedFields & this.CHANGED_BUTTONS_STATE) { + const currentStateMap = this.state.buttonState; + const previousStateMap = this.state.previousButtonState; + const allKeys = new Set([ + // @ts-ignore + ...currentStateMap.keys(), + // @ts-ignore + ...previousStateMap.keys(), + ]); + message.buttonChangedMask = Object.fromEntries( + Array.from(allKeys) + .filter((key) => { + const newState = currentStateMap.get(key); + const oldState = previousStateMap.get(key); + return newState !== oldState; + }) + .map((key) => { + const newValue = currentStateMap.get(key) ?? false; + + return [this.controllerButtonToVirtualKeyCode(key), newValue]; + }), + ); + } + if (changedFields & this.CHANGED_LEFT_STICK_X) { + message.leftStickX = this.state.leftX; + } + if (changedFields & this.CHANGED_LEFT_STICK_Y) { + message.leftStickY = this.state.leftY; + } + if (changedFields & this.CHANGED_RIGHT_STICK_X) { + message.rightStickX = this.state.rightX; + } + if (changedFields & this.CHANGED_RIGHT_STICK_Y) { + message.rightStickY = this.state.rightY; + } + if (changedFields & this.CHANGED_LEFT_TRIGGER) { + message.leftTrigger = this.state.leftTrigger; + } + if (changedFields & this.CHANGED_RIGHT_TRIGGER) { + message.rightTrigger = this.state.rightTrigger; + } + if (changedFields & this.CHANGED_DPAD_X) { + message.dpadX = this.state.dpadX; + } + if (changedFields & this.CHANGED_DPAD_Y) { + message.dpadY = this.state.dpadY; + } + } + + // Send message + const batchMessage = createMessage( + create( + ProtoControllerStateBatchSchema, + message as ProtoControllerStateBatch, + ), + "controllerInput", + ); + this.wrtc.sendBinary(toBinary(ProtoMessageSchema, batchMessage)); + } public run() { if (this.connected) this.stop(); this.connected = true; - this.isIdle = true; - this.lastInputTime = Date.now(); - this.loopInterval = setInterval(() => { - if (this.connected) { - this.inputDetected = false; // Reset before poll - this.pollGamepad(); - - // Switch polling rate based on input - if (this.inputDetected) { - this.lastInputTime = Date.now(); - if (this.isIdle) { - this.isIdle = false; - clearInterval(this.loopInterval); - this.loopInterval = setInterval(() => { - if (this.connected) this.pollGamepad(); - }, this.updateInterval); - } - } else if (!this.isIdle && Date.now() - this.lastInputTime > 200) { - // Switch to idle polling after 200ms of no input - this.isIdle = true; - clearInterval(this.loopInterval); - this.loopInterval = setInterval(() => { - if (this.connected) this.pollGamepad(); - }, this.idleUpdateInterval); - } - } - }, this.isIdle ? this.idleUpdateInterval : this.updateInterval); + // Start with active polling + this.restartPolling(); } public stop() { - if (this.loopInterval) { - clearInterval(this.loopInterval); - this.loopInterval = null; + if (this.pollInterval) { + clearInterval(this.pollInterval); + this.pollInterval = null; } this.connected = false; } @@ -443,17 +478,18 @@ export class Controller { this.wrtc.sendBinary(toBinary(ProtoMessageSchema, detachMsg)); } - private controllerButtonToVirtualKeyCode(code: number) { + private controllerButtonToVirtualKeyCode(code: number): number | undefined { return controllerButtonToLinuxEventCode[code] || undefined; } private rumbleCallback(rumbleMsg: ProtoControllerRumble) { - // If not connected, ignore - if (!this.connected) return; + if (!this.connected || !this.gamepad) return; - // Check if aimed at this controller slot - if (rumbleMsg.sessionId !== this.wrtc.getSessionID() && - rumbleMsg.sessionSlot !== this.gamepad.index) + // Check if this rumble is for us + if ( + rumbleMsg.sessionId !== this.wrtc.getSessionID() && + rumbleMsg.sessionSlot !== this.gamepad.index + ) return; // Trigger actual rumble diff --git a/packages/input/src/proto/messages_pb.ts b/packages/input/src/proto/messages_pb.ts index 4e8c70de..51243dab 100644 --- a/packages/input/src/proto/messages_pb.ts +++ b/packages/input/src/proto/messages_pb.ts @@ -4,7 +4,7 @@ import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2"; -import type { ProtoClientDisconnected, ProtoClientRequestRoomStream, ProtoControllerAttach, ProtoControllerAxis, ProtoControllerButton, ProtoControllerDetach, ProtoControllerRumble, ProtoControllerStick, ProtoControllerTrigger, ProtoICE, ProtoKeyDown, ProtoKeyUp, ProtoMouseKeyDown, ProtoMouseKeyUp, ProtoMouseMove, ProtoMouseMoveAbs, ProtoMouseWheel, ProtoRaw, ProtoSDP, ProtoServerPushStream } from "./types_pb"; +import type { ProtoClientDisconnected, ProtoClientRequestRoomStream, ProtoControllerAttach, ProtoControllerDetach, ProtoControllerRumble, ProtoControllerStateBatch, ProtoICE, ProtoKeyDown, ProtoKeyUp, ProtoMouseKeyDown, ProtoMouseKeyUp, ProtoMouseMove, ProtoMouseMoveAbs, ProtoMouseWheel, ProtoRaw, ProtoSDP, ProtoServerPushStream } from "./types_pb"; import { file_types } from "./types_pb"; import type { ProtoLatencyTracker } from "./latency_tracker_pb"; import { file_latency_tracker } from "./latency_tracker_pb"; @@ -14,7 +14,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file messages.proto. */ export const file_messages: GenFile = /*@__PURE__*/ - fileDesc("Cg5tZXNzYWdlcy5wcm90bxIFcHJvdG8iVQoQUHJvdG9NZXNzYWdlQmFzZRIUCgxwYXlsb2FkX3R5cGUYASABKAkSKwoHbGF0ZW5jeRgCIAEoCzIaLnByb3RvLlByb3RvTGF0ZW5jeVRyYWNrZXIiyQgKDFByb3RvTWVzc2FnZRItCgxtZXNzYWdlX2Jhc2UYASABKAsyFy5wcm90by5Qcm90b01lc3NhZ2VCYXNlEisKCm1vdXNlX21vdmUYAiABKAsyFS5wcm90by5Qcm90b01vdXNlTW92ZUgAEjIKDm1vdXNlX21vdmVfYWJzGAMgASgLMhgucHJvdG8uUHJvdG9Nb3VzZU1vdmVBYnNIABItCgttb3VzZV93aGVlbBgEIAEoCzIWLnByb3RvLlByb3RvTW91c2VXaGVlbEgAEjIKDm1vdXNlX2tleV9kb3duGAUgASgLMhgucHJvdG8uUHJvdG9Nb3VzZUtleURvd25IABIuCgxtb3VzZV9rZXlfdXAYBiABKAsyFi5wcm90by5Qcm90b01vdXNlS2V5VXBIABInCghrZXlfZG93bhgHIAEoCzITLnByb3RvLlByb3RvS2V5RG93bkgAEiMKBmtleV91cBgIIAEoCzIRLnByb3RvLlByb3RvS2V5VXBIABI5ChFjb250cm9sbGVyX2F0dGFjaBgJIAEoCzIcLnByb3RvLlByb3RvQ29udHJvbGxlckF0dGFjaEgAEjkKEWNvbnRyb2xsZXJfZGV0YWNoGAogASgLMhwucHJvdG8uUHJvdG9Db250cm9sbGVyRGV0YWNoSAASOQoRY29udHJvbGxlcl9idXR0b24YCyABKAsyHC5wcm90by5Qcm90b0NvbnRyb2xsZXJCdXR0b25IABI7ChJjb250cm9sbGVyX3RyaWdnZXIYDCABKAsyHS5wcm90by5Qcm90b0NvbnRyb2xsZXJUcmlnZ2VySAASNwoQY29udHJvbGxlcl9zdGljaxgNIAEoCzIbLnByb3RvLlByb3RvQ29udHJvbGxlclN0aWNrSAASNQoPY29udHJvbGxlcl9heGlzGA4gASgLMhoucHJvdG8uUHJvdG9Db250cm9sbGVyQXhpc0gAEjkKEWNvbnRyb2xsZXJfcnVtYmxlGA8gASgLMhwucHJvdG8uUHJvdG9Db250cm9sbGVyUnVtYmxlSAASHgoDaWNlGBQgASgLMg8ucHJvdG8uUHJvdG9JQ0VIABIeCgNzZHAYFSABKAsyDy5wcm90by5Qcm90b1NEUEgAEh4KA3JhdxgWIAEoCzIPLnByb3RvLlByb3RvUmF3SAASSQoaY2xpZW50X3JlcXVlc3Rfcm9vbV9zdHJlYW0YFyABKAsyIy5wcm90by5Qcm90b0NsaWVudFJlcXVlc3RSb29tU3RyZWFtSAASPQoTY2xpZW50X2Rpc2Nvbm5lY3RlZBgYIAEoCzIeLnByb3RvLlByb3RvQ2xpZW50RGlzY29ubmVjdGVkSAASOgoSc2VydmVyX3B1c2hfc3RyZWFtGBkgASgLMhwucHJvdG8uUHJvdG9TZXJ2ZXJQdXNoU3RyZWFtSABCCQoHcGF5bG9hZEIWWhRyZWxheS9pbnRlcm5hbC9wcm90b2IGcHJvdG8z", [file_types, file_latency_tracker]); + fileDesc("Cg5tZXNzYWdlcy5wcm90bxIFcHJvdG8iVQoQUHJvdG9NZXNzYWdlQmFzZRIUCgxwYXlsb2FkX3R5cGUYASABKAkSKwoHbGF0ZW5jeRgCIAEoCzIaLnByb3RvLlByb3RvTGF0ZW5jeVRyYWNrZXIipQcKDFByb3RvTWVzc2FnZRItCgxtZXNzYWdlX2Jhc2UYASABKAsyFy5wcm90by5Qcm90b01lc3NhZ2VCYXNlEisKCm1vdXNlX21vdmUYAiABKAsyFS5wcm90by5Qcm90b01vdXNlTW92ZUgAEjIKDm1vdXNlX21vdmVfYWJzGAMgASgLMhgucHJvdG8uUHJvdG9Nb3VzZU1vdmVBYnNIABItCgttb3VzZV93aGVlbBgEIAEoCzIWLnByb3RvLlByb3RvTW91c2VXaGVlbEgAEjIKDm1vdXNlX2tleV9kb3duGAUgASgLMhgucHJvdG8uUHJvdG9Nb3VzZUtleURvd25IABIuCgxtb3VzZV9rZXlfdXAYBiABKAsyFi5wcm90by5Qcm90b01vdXNlS2V5VXBIABInCghrZXlfZG93bhgHIAEoCzITLnByb3RvLlByb3RvS2V5RG93bkgAEiMKBmtleV91cBgIIAEoCzIRLnByb3RvLlByb3RvS2V5VXBIABI5ChFjb250cm9sbGVyX2F0dGFjaBgJIAEoCzIcLnByb3RvLlByb3RvQ29udHJvbGxlckF0dGFjaEgAEjkKEWNvbnRyb2xsZXJfZGV0YWNoGAogASgLMhwucHJvdG8uUHJvdG9Db250cm9sbGVyRGV0YWNoSAASOQoRY29udHJvbGxlcl9ydW1ibGUYCyABKAsyHC5wcm90by5Qcm90b0NvbnRyb2xsZXJSdW1ibGVIABJCChZjb250cm9sbGVyX3N0YXRlX2JhdGNoGAwgASgLMiAucHJvdG8uUHJvdG9Db250cm9sbGVyU3RhdGVCYXRjaEgAEh4KA2ljZRgUIAEoCzIPLnByb3RvLlByb3RvSUNFSAASHgoDc2RwGBUgASgLMg8ucHJvdG8uUHJvdG9TRFBIABIeCgNyYXcYFiABKAsyDy5wcm90by5Qcm90b1Jhd0gAEkkKGmNsaWVudF9yZXF1ZXN0X3Jvb21fc3RyZWFtGBcgASgLMiMucHJvdG8uUHJvdG9DbGllbnRSZXF1ZXN0Um9vbVN0cmVhbUgAEj0KE2NsaWVudF9kaXNjb25uZWN0ZWQYGCABKAsyHi5wcm90by5Qcm90b0NsaWVudERpc2Nvbm5lY3RlZEgAEjoKEnNlcnZlcl9wdXNoX3N0cmVhbRgZIAEoCzIcLnByb3RvLlByb3RvU2VydmVyUHVzaFN0cmVhbUgAQgkKB3BheWxvYWRCFloUcmVsYXkvaW50ZXJuYWwvcHJvdG9iBnByb3RvMw", [file_types, file_latency_tracker]); /** * @generated from message proto.ProtoMessageBase @@ -96,6 +96,8 @@ export type ProtoMessage = Message<"proto.ProtoMessage"> & { case: "keyUp"; } | { /** + * Controller input types + * * @generated from field: proto.ProtoControllerAttach controller_attach = 9; */ value: ProtoControllerAttach; @@ -108,34 +110,16 @@ export type ProtoMessage = Message<"proto.ProtoMessage"> & { case: "controllerDetach"; } | { /** - * @generated from field: proto.ProtoControllerButton controller_button = 11; - */ - value: ProtoControllerButton; - case: "controllerButton"; - } | { - /** - * @generated from field: proto.ProtoControllerTrigger controller_trigger = 12; - */ - value: ProtoControllerTrigger; - case: "controllerTrigger"; - } | { - /** - * @generated from field: proto.ProtoControllerStick controller_stick = 13; - */ - value: ProtoControllerStick; - case: "controllerStick"; - } | { - /** - * @generated from field: proto.ProtoControllerAxis controller_axis = 14; - */ - value: ProtoControllerAxis; - case: "controllerAxis"; - } | { - /** - * @generated from field: proto.ProtoControllerRumble controller_rumble = 15; + * @generated from field: proto.ProtoControllerRumble controller_rumble = 11; */ value: ProtoControllerRumble; case: "controllerRumble"; + } | { + /** + * @generated from field: proto.ProtoControllerStateBatch controller_state_batch = 12; + */ + value: ProtoControllerStateBatch; + case: "controllerStateBatch"; } | { /** * Signaling types diff --git a/packages/input/src/proto/types_pb.ts b/packages/input/src/proto/types_pb.ts index 6da4bf1e..d4a8c6cc 100644 --- a/packages/input/src/proto/types_pb.ts +++ b/packages/input/src/proto/types_pb.ts @@ -2,15 +2,15 @@ // @generated from file types.proto (package proto, syntax proto3) /* eslint-disable */ -import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2"; -import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2"; +import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2"; +import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2"; import type { Message } from "@bufbuild/protobuf"; /** * Describes the file types.proto. */ export const file_types: GenFile = /*@__PURE__*/ - fileDesc("Cgt0eXBlcy5wcm90bxIFcHJvdG8iJgoOUHJvdG9Nb3VzZU1vdmUSCQoBeBgBIAEoBRIJCgF5GAIgASgFIikKEVByb3RvTW91c2VNb3ZlQWJzEgkKAXgYASABKAUSCQoBeRgCIAEoBSInCg9Qcm90b01vdXNlV2hlZWwSCQoBeBgBIAEoBRIJCgF5GAIgASgFIiAKEVByb3RvTW91c2VLZXlEb3duEgsKA2tleRgBIAEoBSIeCg9Qcm90b01vdXNlS2V5VXASCwoDa2V5GAEgASgFIhsKDFByb3RvS2V5RG93bhILCgNrZXkYASABKAUiGQoKUHJvdG9LZXlVcBILCgNrZXkYASABKAUiTQoVUHJvdG9Db250cm9sbGVyQXR0YWNoEgoKAmlkGAEgASgJEhQKDHNlc3Npb25fc2xvdBgCIAEoBRISCgpzZXNzaW9uX2lkGAMgASgJIkEKFVByb3RvQ29udHJvbGxlckRldGFjaBIUCgxzZXNzaW9uX3Nsb3QYASABKAUSEgoKc2Vzc2lvbl9pZBgCIAEoCSJiChVQcm90b0NvbnRyb2xsZXJCdXR0b24SFAoMc2Vzc2lvbl9zbG90GAEgASgFEhIKCnNlc3Npb25faWQYAiABKAkSDgoGYnV0dG9uGAMgASgFEg8KB3ByZXNzZWQYBCABKAgiYgoWUHJvdG9Db250cm9sbGVyVHJpZ2dlchIUCgxzZXNzaW9uX3Nsb3QYASABKAUSEgoKc2Vzc2lvbl9pZBgCIAEoCRIPCgd0cmlnZ2VyGAMgASgFEg0KBXZhbHVlGAQgASgFImUKFFByb3RvQ29udHJvbGxlclN0aWNrEhQKDHNlc3Npb25fc2xvdBgBIAEoBRISCgpzZXNzaW9uX2lkGAIgASgJEg0KBXN0aWNrGAMgASgFEgkKAXgYBCABKAUSCQoBeRgFIAEoBSJcChNQcm90b0NvbnRyb2xsZXJBeGlzEhQKDHNlc3Npb25fc2xvdBgBIAEoBRISCgpzZXNzaW9uX2lkGAIgASgJEgwKBGF4aXMYAyABKAUSDQoFdmFsdWUYBCABKAUiggEKFVByb3RvQ29udHJvbGxlclJ1bWJsZRIUCgxzZXNzaW9uX3Nsb3QYASABKAUSEgoKc2Vzc2lvbl9pZBgCIAEoCRIVCg1sb3dfZnJlcXVlbmN5GAMgASgFEhYKDmhpZ2hfZnJlcXVlbmN5GAQgASgFEhAKCGR1cmF0aW9uGAUgASgFIqoBChNSVENJY2VDYW5kaWRhdGVJbml0EhEKCWNhbmRpZGF0ZRgBIAEoCRIaCg1zZHBNTGluZUluZGV4GAIgASgNSACIAQESEwoGc2RwTWlkGAMgASgJSAGIAQESHQoQdXNlcm5hbWVGcmFnbWVudBgEIAEoCUgCiAEBQhAKDl9zZHBNTGluZUluZGV4QgkKB19zZHBNaWRCEwoRX3VzZXJuYW1lRnJhZ21lbnQiNgoZUlRDU2Vzc2lvbkRlc2NyaXB0aW9uSW5pdBILCgNzZHAYASABKAkSDAoEdHlwZRgCIAEoCSI5CghQcm90b0lDRRItCgljYW5kaWRhdGUYASABKAsyGi5wcm90by5SVENJY2VDYW5kaWRhdGVJbml0IjkKCFByb3RvU0RQEi0KA3NkcBgBIAEoCzIgLnByb3RvLlJUQ1Nlc3Npb25EZXNjcmlwdGlvbkluaXQiGAoIUHJvdG9SYXcSDAoEZGF0YRgBIAEoCSJFChxQcm90b0NsaWVudFJlcXVlc3RSb29tU3RyZWFtEhEKCXJvb21fbmFtZRgBIAEoCRISCgpzZXNzaW9uX2lkGAIgASgJIkcKF1Byb3RvQ2xpZW50RGlzY29ubmVjdGVkEhIKCnNlc3Npb25faWQYASABKAkSGAoQY29udHJvbGxlcl9zbG90cxgCIAMoBSIqChVQcm90b1NlcnZlclB1c2hTdHJlYW0SEQoJcm9vbV9uYW1lGAEgASgJQhZaFHJlbGF5L2ludGVybmFsL3Byb3RvYgZwcm90bzM"); + fileDesc("Cgt0eXBlcy5wcm90bxIFcHJvdG8iJgoOUHJvdG9Nb3VzZU1vdmUSCQoBeBgBIAEoBRIJCgF5GAIgASgFIikKEVByb3RvTW91c2VNb3ZlQWJzEgkKAXgYASABKAUSCQoBeRgCIAEoBSInCg9Qcm90b01vdXNlV2hlZWwSCQoBeBgBIAEoBRIJCgF5GAIgASgFIiAKEVByb3RvTW91c2VLZXlEb3duEgsKA2tleRgBIAEoBSIeCg9Qcm90b01vdXNlS2V5VXASCwoDa2V5GAEgASgFIhsKDFByb3RvS2V5RG93bhILCgNrZXkYASABKAUiGQoKUHJvdG9LZXlVcBILCgNrZXkYASABKAUiTQoVUHJvdG9Db250cm9sbGVyQXR0YWNoEgoKAmlkGAEgASgJEhQKDHNlc3Npb25fc2xvdBgCIAEoBRISCgpzZXNzaW9uX2lkGAMgASgJIkEKFVByb3RvQ29udHJvbGxlckRldGFjaBIUCgxzZXNzaW9uX3Nsb3QYASABKAUSEgoKc2Vzc2lvbl9pZBgCIAEoCSKCAQoVUHJvdG9Db250cm9sbGVyUnVtYmxlEhQKDHNlc3Npb25fc2xvdBgBIAEoBRISCgpzZXNzaW9uX2lkGAIgASgJEhUKDWxvd19mcmVxdWVuY3kYAyABKAUSFgoOaGlnaF9mcmVxdWVuY3kYBCABKAUSEAoIZHVyYXRpb24YBSABKAUi0AUKGVByb3RvQ29udHJvbGxlclN0YXRlQmF0Y2gSFAoMc2Vzc2lvbl9zbG90GAEgASgFEhIKCnNlc3Npb25faWQYAiABKAkSQAoLdXBkYXRlX3R5cGUYAyABKA4yKy5wcm90by5Qcm90b0NvbnRyb2xsZXJTdGF0ZUJhdGNoLlVwZGF0ZVR5cGUSEAoIc2VxdWVuY2UYBCABKA0SVAoTYnV0dG9uX2NoYW5nZWRfbWFzaxgFIAMoCzI3LnByb3RvLlByb3RvQ29udHJvbGxlclN0YXRlQmF0Y2guQnV0dG9uQ2hhbmdlZE1hc2tFbnRyeRIZCgxsZWZ0X3N0aWNrX3gYBiABKAVIAIgBARIZCgxsZWZ0X3N0aWNrX3kYByABKAVIAYgBARIaCg1yaWdodF9zdGlja194GAggASgFSAKIAQESGgoNcmlnaHRfc3RpY2tfeRgJIAEoBUgDiAEBEhkKDGxlZnRfdHJpZ2dlchgKIAEoBUgEiAEBEhoKDXJpZ2h0X3RyaWdnZXIYCyABKAVIBYgBARITCgZkcGFkX3gYDCABKAVIBogBARITCgZkcGFkX3kYDSABKAVIB4gBARIbCg5jaGFuZ2VkX2ZpZWxkcxgOIAEoDUgIiAEBGjgKFkJ1dHRvbkNoYW5nZWRNYXNrRW50cnkSCwoDa2V5GAEgASgFEg0KBXZhbHVlGAIgASgIOgI4ASInCgpVcGRhdGVUeXBlEg4KCkZVTExfU1RBVEUQABIJCgVERUxUQRABQg8KDV9sZWZ0X3N0aWNrX3hCDwoNX2xlZnRfc3RpY2tfeUIQCg5fcmlnaHRfc3RpY2tfeEIQCg5fcmlnaHRfc3RpY2tfeUIPCg1fbGVmdF90cmlnZ2VyQhAKDl9yaWdodF90cmlnZ2VyQgkKB19kcGFkX3hCCQoHX2RwYWRfeUIRCg9fY2hhbmdlZF9maWVsZHMiqgEKE1JUQ0ljZUNhbmRpZGF0ZUluaXQSEQoJY2FuZGlkYXRlGAEgASgJEhoKDXNkcE1MaW5lSW5kZXgYAiABKA1IAIgBARITCgZzZHBNaWQYAyABKAlIAYgBARIdChB1c2VybmFtZUZyYWdtZW50GAQgASgJSAKIAQFCEAoOX3NkcE1MaW5lSW5kZXhCCQoHX3NkcE1pZEITChFfdXNlcm5hbWVGcmFnbWVudCI2ChlSVENTZXNzaW9uRGVzY3JpcHRpb25Jbml0EgsKA3NkcBgBIAEoCRIMCgR0eXBlGAIgASgJIjkKCFByb3RvSUNFEi0KCWNhbmRpZGF0ZRgBIAEoCzIaLnByb3RvLlJUQ0ljZUNhbmRpZGF0ZUluaXQiOQoIUHJvdG9TRFASLQoDc2RwGAEgASgLMiAucHJvdG8uUlRDU2Vzc2lvbkRlc2NyaXB0aW9uSW5pdCIYCghQcm90b1JhdxIMCgRkYXRhGAEgASgJIkUKHFByb3RvQ2xpZW50UmVxdWVzdFJvb21TdHJlYW0SEQoJcm9vbV9uYW1lGAEgASgJEhIKCnNlc3Npb25faWQYAiABKAkiRwoXUHJvdG9DbGllbnREaXNjb25uZWN0ZWQSEgoKc2Vzc2lvbl9pZBgBIAEoCRIYChBjb250cm9sbGVyX3Nsb3RzGAIgAygFIioKFVByb3RvU2VydmVyUHVzaFN0cmVhbRIRCglyb29tX25hbWUYASABKAlCFloUcmVsYXkvaW50ZXJuYWwvcHJvdG9iBnByb3RvMw"); /** * MouseMove message @@ -223,181 +223,6 @@ export type ProtoControllerDetach = Message<"proto.ProtoControllerDetach"> & { export const ProtoControllerDetachSchema: GenMessage = /*@__PURE__*/ messageDesc(file_types, 8); -/** - * ControllerButton message - * - * @generated from message proto.ProtoControllerButton - */ -export type ProtoControllerButton = Message<"proto.ProtoControllerButton"> & { - /** - * Session specific slot number (0-3) - * - * @generated from field: int32 session_slot = 1; - */ - sessionSlot: number; - - /** - * Session ID of the client - * - * @generated from field: string session_id = 2; - */ - sessionId: string; - - /** - * Button code (linux input event code) - * - * @generated from field: int32 button = 3; - */ - button: number; - - /** - * true if pressed, false if released - * - * @generated from field: bool pressed = 4; - */ - pressed: boolean; -}; - -/** - * Describes the message proto.ProtoControllerButton. - * Use `create(ProtoControllerButtonSchema)` to create a new message. - */ -export const ProtoControllerButtonSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_types, 9); - -/** - * ControllerTriggers message - * - * @generated from message proto.ProtoControllerTrigger - */ -export type ProtoControllerTrigger = Message<"proto.ProtoControllerTrigger"> & { - /** - * Session specific slot number (0-3) - * - * @generated from field: int32 session_slot = 1; - */ - sessionSlot: number; - - /** - * Session ID of the client - * - * @generated from field: string session_id = 2; - */ - sessionId: string; - - /** - * Trigger number (0 for left, 1 for right) - * - * @generated from field: int32 trigger = 3; - */ - trigger: number; - - /** - * trigger value (-32768 to 32767) - * - * @generated from field: int32 value = 4; - */ - value: number; -}; - -/** - * Describes the message proto.ProtoControllerTrigger. - * Use `create(ProtoControllerTriggerSchema)` to create a new message. - */ -export const ProtoControllerTriggerSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_types, 10); - -/** - * ControllerSticks message - * - * @generated from message proto.ProtoControllerStick - */ -export type ProtoControllerStick = Message<"proto.ProtoControllerStick"> & { - /** - * Session specific slot number (0-3) - * - * @generated from field: int32 session_slot = 1; - */ - sessionSlot: number; - - /** - * Session ID of the client - * - * @generated from field: string session_id = 2; - */ - sessionId: string; - - /** - * Stick number (0 for left, 1 for right) - * - * @generated from field: int32 stick = 3; - */ - stick: number; - - /** - * X axis value (-32768 to 32767) - * - * @generated from field: int32 x = 4; - */ - x: number; - - /** - * Y axis value (-32768 to 32767) - * - * @generated from field: int32 y = 5; - */ - y: number; -}; - -/** - * Describes the message proto.ProtoControllerStick. - * Use `create(ProtoControllerStickSchema)` to create a new message. - */ -export const ProtoControllerStickSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_types, 11); - -/** - * ControllerAxis message - * - * @generated from message proto.ProtoControllerAxis - */ -export type ProtoControllerAxis = Message<"proto.ProtoControllerAxis"> & { - /** - * Session specific slot number (0-3) - * - * @generated from field: int32 session_slot = 1; - */ - sessionSlot: number; - - /** - * Session ID of the client - * - * @generated from field: string session_id = 2; - */ - sessionId: string; - - /** - * Axis number (0 for d-pad horizontal, 1 for d-pad vertical) - * - * @generated from field: int32 axis = 3; - */ - axis: number; - - /** - * axis value (-1 to 1) - * - * @generated from field: int32 value = 4; - */ - value: number; -}; - -/** - * Describes the message proto.ProtoControllerAxis. - * Use `create(ProtoControllerAxisSchema)` to create a new message. - */ -export const ProtoControllerAxisSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_types, 12); - /** * ControllerRumble message * @@ -445,7 +270,145 @@ export type ProtoControllerRumble = Message<"proto.ProtoControllerRumble"> & { * Use `create(ProtoControllerRumbleSchema)` to create a new message. */ export const ProtoControllerRumbleSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_types, 13); + messageDesc(file_types, 9); + +/** + * ControllerStateBatch - single message containing full or partial controller state + * + * @generated from message proto.ProtoControllerStateBatch + */ +export type ProtoControllerStateBatch = Message<"proto.ProtoControllerStateBatch"> & { + /** + * Session specific slot number (0-3) + * + * @generated from field: int32 session_slot = 1; + */ + sessionSlot: number; + + /** + * Session ID of the client + * + * @generated from field: string session_id = 2; + */ + sessionId: string; + + /** + * @generated from field: proto.ProtoControllerStateBatch.UpdateType update_type = 3; + */ + updateType: ProtoControllerStateBatch_UpdateType; + + /** + * Sequence number for packet loss detection + * + * @generated from field: uint32 sequence = 4; + */ + sequence: number; + + /** + * Button state map (Linux event codes) + * + * @generated from field: map button_changed_mask = 5; + */ + buttonChangedMask: { [key: number]: boolean }; + + /** + * Analog inputs + * + * -32768 to 32767 + * + * @generated from field: optional int32 left_stick_x = 6; + */ + leftStickX?: number; + + /** + * -32768 to 32767 + * + * @generated from field: optional int32 left_stick_y = 7; + */ + leftStickY?: number; + + /** + * -32768 to 32767 + * + * @generated from field: optional int32 right_stick_x = 8; + */ + rightStickX?: number; + + /** + * -32768 to 32767 + * + * @generated from field: optional int32 right_stick_y = 9; + */ + rightStickY?: number; + + /** + * -32768 to 32767 + * + * @generated from field: optional int32 left_trigger = 10; + */ + leftTrigger?: number; + + /** + * -32768 to 32767 + * + * @generated from field: optional int32 right_trigger = 11; + */ + rightTrigger?: number; + + /** + * -1, 0, or 1 + * + * @generated from field: optional int32 dpad_x = 12; + */ + dpadX?: number; + + /** + * -1, 0, or 1 + * + * @generated from field: optional int32 dpad_y = 13; + */ + dpadY?: number; + + /** + * Bitmask indicating which fields have changed + * Bit 0: button_changed_mask, Bit 1: left_stick_x, Bit 2: left_stick_y, etc. + * + * @generated from field: optional uint32 changed_fields = 14; + */ + changedFields?: number; +}; + +/** + * Describes the message proto.ProtoControllerStateBatch. + * Use `create(ProtoControllerStateBatchSchema)` to create a new message. + */ +export const ProtoControllerStateBatchSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_types, 10); + +/** + * @generated from enum proto.ProtoControllerStateBatch.UpdateType + */ +export enum ProtoControllerStateBatch_UpdateType { + /** + * Complete controller state + * + * @generated from enum value: FULL_STATE = 0; + */ + FULL_STATE = 0, + + /** + * Only changed fields + * + * @generated from enum value: DELTA = 1; + */ + DELTA = 1, +} + +/** + * Describes the enum proto.ProtoControllerStateBatch.UpdateType. + */ +export const ProtoControllerStateBatch_UpdateTypeSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_types, 10, 0); /** * @generated from message proto.RTCIceCandidateInit @@ -477,7 +440,7 @@ export type RTCIceCandidateInit = Message<"proto.RTCIceCandidateInit"> & { * Use `create(RTCIceCandidateInitSchema)` to create a new message. */ export const RTCIceCandidateInitSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_types, 14); + messageDesc(file_types, 11); /** * @generated from message proto.RTCSessionDescriptionInit @@ -499,7 +462,7 @@ export type RTCSessionDescriptionInit = Message<"proto.RTCSessionDescriptionInit * Use `create(RTCSessionDescriptionInitSchema)` to create a new message. */ export const RTCSessionDescriptionInitSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_types, 15); + messageDesc(file_types, 12); /** * ProtoICE message @@ -518,7 +481,7 @@ export type ProtoICE = Message<"proto.ProtoICE"> & { * Use `create(ProtoICESchema)` to create a new message. */ export const ProtoICESchema: GenMessage = /*@__PURE__*/ - messageDesc(file_types, 16); + messageDesc(file_types, 13); /** * ProtoSDP message @@ -537,7 +500,7 @@ export type ProtoSDP = Message<"proto.ProtoSDP"> & { * Use `create(ProtoSDPSchema)` to create a new message. */ export const ProtoSDPSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_types, 17); + messageDesc(file_types, 14); /** * ProtoRaw message @@ -556,7 +519,7 @@ export type ProtoRaw = Message<"proto.ProtoRaw"> & { * Use `create(ProtoRawSchema)` to create a new message. */ export const ProtoRawSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_types, 18); + messageDesc(file_types, 15); /** * ProtoClientRequestRoomStream message @@ -580,7 +543,7 @@ export type ProtoClientRequestRoomStream = Message<"proto.ProtoClientRequestRoom * Use `create(ProtoClientRequestRoomStreamSchema)` to create a new message. */ export const ProtoClientRequestRoomStreamSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_types, 19); + messageDesc(file_types, 16); /** * ProtoClientDisconnected message @@ -604,7 +567,7 @@ export type ProtoClientDisconnected = Message<"proto.ProtoClientDisconnected"> & * Use `create(ProtoClientDisconnectedSchema)` to create a new message. */ export const ProtoClientDisconnectedSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_types, 20); + messageDesc(file_types, 17); /** * ProtoServerPushStream message @@ -623,5 +586,5 @@ export type ProtoServerPushStream = Message<"proto.ProtoServerPushStream"> & { * Use `create(ProtoServerPushStreamSchema)` to create a new message. */ export const ProtoServerPushStreamSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_types, 21); + messageDesc(file_types, 18); diff --git a/packages/play-standalone/package.json b/packages/play-standalone/package.json index 9db4e167..7b43d6e6 100644 --- a/packages/play-standalone/package.json +++ b/packages/play-standalone/package.json @@ -9,8 +9,8 @@ "astro": "astro" }, "dependencies": { - "@astrojs/node": "^9.4.2", + "@astrojs/node": "9.5.0", "@nestri/input": "*", - "astro": "5.14.5" + "astro": "5.15.1" } } \ No newline at end of file diff --git a/packages/relay/go.mod b/packages/relay/go.mod index f0f53dd3..28e53f7f 100644 --- a/packages/relay/go.mod +++ b/packages/relay/go.mod @@ -10,7 +10,7 @@ require ( github.com/oklog/ulid/v2 v2.1.1 github.com/pion/ice/v4 v4.0.10 github.com/pion/interceptor v0.1.41 - github.com/pion/rtp v1.8.24 + github.com/pion/rtp v1.8.25 github.com/pion/webrtc/v4 v4.1.6 github.com/prometheus/client_golang v1.23.2 google.golang.org/protobuf v1.36.10 @@ -30,7 +30,7 @@ require ( github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/huin/goupnp v1.3.0 // indirect - github.com/ipfs/go-cid v0.5.0 // indirect + github.com/ipfs/go-cid v0.6.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/klauspost/compress v1.18.1 // indirect @@ -40,7 +40,7 @@ require ( github.com/libp2p/go-flow-metrics v0.3.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect github.com/libp2p/go-msgio v0.3.0 // indirect - github.com/libp2p/go-netroute v0.3.0 // indirect + github.com/libp2p/go-netroute v0.4.0 // indirect github.com/libp2p/go-yamux/v5 v5.1.0 // indirect github.com/libp2p/zeroconf/v2 v2.2.0 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect @@ -71,13 +71,13 @@ require ( github.com/pion/sdp/v3 v3.0.16 // indirect github.com/pion/srtp/v3 v3.0.8 // indirect github.com/pion/stun v0.6.1 // indirect - github.com/pion/stun/v3 v3.0.0 // indirect + github.com/pion/stun/v3 v3.0.1 // indirect github.com/pion/transport/v2 v2.2.10 // indirect github.com/pion/transport/v3 v3.0.8 // indirect - github.com/pion/turn/v4 v4.1.1 // indirect + github.com/pion/turn/v4 v4.1.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.1 // indirect - github.com/prometheus/procfs v0.17.0 // indirect + github.com/prometheus/common v0.67.2 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.55.0 // indirect github.com/quic-go/webtransport-go v0.9.0 // indirect @@ -91,12 +91,12 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/crypto v0.43.0 // indirect - golang.org/x/exp v0.0.0-20251017212417-90e834f514db // indirect + golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/mod v0.29.0 // indirect golang.org/x/net v0.46.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.37.0 // indirect - golang.org/x/telemetry v0.0.0-20251014153721-24f779f6aaef // indirect + golang.org/x/telemetry v0.0.0-20251028164327-d7a2859f34e8 // indirect golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.38.0 // indirect diff --git a/packages/relay/go.sum b/packages/relay/go.sum index 7f5ff0b3..0500e36c 100644 --- a/packages/relay/go.sum +++ b/packages/relay/go.sum @@ -71,8 +71,8 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= -github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg= -github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk= +github.com/ipfs/go-cid v0.6.0 h1:DlOReBV1xhHBhhfy/gBNNTSyfOM6rLiIx9J7A4DGf30= +github.com/ipfs/go-cid v0.6.0/go.mod h1:NC4kS1LZjzfhK40UGmpXv5/qD2kcMzACYJNntCUiDhQ= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= @@ -113,8 +113,8 @@ github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUI github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= -github.com/libp2p/go-netroute v0.3.0 h1:nqPCXHmeNmgTJnktosJ/sIef9hvwYCrsLxXmfNks/oc= -github.com/libp2p/go-netroute v0.3.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA= +github.com/libp2p/go-netroute v0.4.0 h1:sZZx9hyANYUx9PZyqcgE/E1GUG3iEtTZHUEvdtXT7/Q= +github.com/libp2p/go-netroute v0.4.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/libp2p/go-yamux/v5 v5.1.0 h1:8Qlxj4E9JGJAQVW6+uj2o7mqkqsIVlSUGmTWhlXzoHE= @@ -199,8 +199,8 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo= github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo= -github.com/pion/rtp v1.8.24 h1:+ICyZXUQDv95EsHN70RrA4XKJf5MGWyC6QQc1u6/ynI= -github.com/pion/rtp v1.8.24/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= +github.com/pion/rtp v1.8.25 h1:b8+y44GNbwOJTYWuVan7SglX/hMlicVCAtL50ztyZHw= +github.com/pion/rtp v1.8.25/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= github.com/pion/sctp v1.8.40 h1:bqbgWYOrUhsYItEnRObUYZuzvOMsVplS3oNgzedBlG8= github.com/pion/sctp v1.8.40/go.mod h1:SPBBUENXE6ThkEksN5ZavfAhFYll+h+66ZiG6IZQuzo= github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo= @@ -209,16 +209,16 @@ github.com/pion/srtp/v3 v3.0.8 h1:RjRrjcIeQsilPzxvdaElN0CpuQZdMvcl9VZ5UY9suUM= github.com/pion/srtp/v3 v3.0.8/go.mod h1:2Sq6YnDH7/UDCvkSoHSDNDeyBcFgWL0sAVycVbAsXFg= github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= -github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= -github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= +github.com/pion/stun/v3 v3.0.1 h1:jx1uUq6BdPihF0yF33Jj2mh+C9p0atY94IkdnW174kA= +github.com/pion/stun/v3 v3.0.1/go.mod h1:RHnvlKFg+qHgoKIqtQWMOJF52wsImCAf/Jh5GjX+4Tw= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= github.com/pion/transport/v3 v3.0.8 h1:oI3myyYnTKUSTthu/NZZ8eu2I5sHbxbUNNFW62olaYc= github.com/pion/transport/v3 v3.0.8/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= -github.com/pion/turn/v4 v4.1.1 h1:9UnY2HB99tpDyz3cVVZguSxcqkJ1DsTSZ+8TGruh4fc= -github.com/pion/turn/v4 v4.1.1/go.mod h1:2123tHk1O++vmjI5VSD0awT50NywDAq5A2NNNU4Jjs8= +github.com/pion/turn/v4 v4.1.2 h1:Em2svpl6aBFa88dLhxypMUzaLjC79kWZWx8FIov01cc= +github.com/pion/turn/v4 v4.1.2/go.mod h1:ISYWfZYy0Z3tXzRpyYZHTL+U23yFQIspfxogdQ8pn9Y= github.com/pion/webrtc/v4 v4.1.6 h1:srHH2HwvCGwPba25EYJgUzgLqCQoXl1VCUnrGQMSzUw= github.com/pion/webrtc/v4 v4.1.6/go.mod h1:wKecGRlkl3ox/As/MYghJL+b/cVXMEhoPMJWPuGQFhU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -231,11 +231,11 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= -github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= +github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8= +github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= -github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= @@ -323,8 +323,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20251017212417-90e834f514db h1:by6IehL4BH5k3e3SJmcoNbOobMey2SLpAF79iPOEBvw= -golang.org/x/exp v0.0.0-20251017212417-90e834f514db/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -396,8 +396,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251014153721-24f779f6aaef h1:5xFtU4tmJMJSxSeDlr1dgBff2tDXrq0laLdS1EA3LYw= -golang.org/x/telemetry v0.0.0-20251014153721-24f779f6aaef/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= +golang.org/x/telemetry v0.0.0-20251028164327-d7a2859f34e8 h1:DwMAzqwLj2rVin75cRFh1kfhwQY3hyHrU1oCEDZXPmQ= +golang.org/x/telemetry v0.0.0-20251028164327-d7a2859f34e8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/packages/relay/internal/core/core.go b/packages/relay/internal/core/core.go index 6288d5f6..09b26bb2 100644 --- a/packages/relay/internal/core/core.go +++ b/packages/relay/internal/core/core.go @@ -10,7 +10,6 @@ import ( "os" "relay/internal/common" "relay/internal/shared" - "time" "github.com/libp2p/go-libp2p" pubsub "github.com/libp2p/go-libp2p-pubsub" @@ -38,16 +37,6 @@ var globalRelay *Relay // -- Structs -- -// ClientSession tracks browser client connections -type ClientSession struct { - PeerID peer.ID - SessionID string - RoomName string - ConnectedAt time.Time - LastActivity time.Time - ControllerSlots []int32 // Track which controller slots this client owns -} - // Relay structure enhanced with metrics and state type Relay struct { *PeerInfo @@ -59,7 +48,6 @@ type Relay struct { // Local LocalRooms *common.SafeMap[ulid.ULID, *shared.Room] // room ID -> local Room struct (hosted by this relay) LocalMeshConnections *common.SafeMap[peer.ID, *webrtc.PeerConnection] // peer ID -> PeerConnection (connected to this relay) - ClientSessions *common.SafeMap[peer.ID, *ClientSession] // peer ID -> ClientSession // Protocols ProtocolRegistry @@ -156,7 +144,6 @@ func NewRelay(ctx context.Context, port int, identityKey crypto.PrivKey) (*Relay PingService: pingSvc, LocalRooms: common.NewSafeMap[ulid.ULID, *shared.Room](), LocalMeshConnections: common.NewSafeMap[peer.ID, *webrtc.PeerConnection](), - ClientSessions: common.NewSafeMap[peer.ID, *ClientSession](), } // Add network notifier after relay is initialized diff --git a/packages/relay/internal/core/protocol_stream.go b/packages/relay/internal/core/protocol_stream.go index 19f3934f..e7566ed9 100644 --- a/packages/relay/internal/core/protocol_stream.go +++ b/packages/relay/internal/core/protocol_stream.go @@ -11,7 +11,6 @@ import ( "relay/internal/common" "relay/internal/connections" "relay/internal/shared" - "time" gen "relay/internal/proto" @@ -111,16 +110,7 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) { sessionID = ulid.String() } - session := &ClientSession{ - PeerID: stream.Conn().RemotePeer(), - SessionID: sessionID, - RoomName: reqMsg.RoomName, - ConnectedAt: time.Now(), - LastActivity: time.Now(), - } - sp.relay.ClientSessions.Set(stream.Conn().RemotePeer(), session) - - slog.Info("Client session established", "peer", session.PeerID, "session", sessionID, "room", reqMsg.RoomName) + slog.Info("Client session requested room stream", "session", sessionID, "room", reqMsg.RoomName) // Send session ID back to client sesMsg, err := common.CreateMessage( @@ -177,7 +167,7 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) { // Create participant for this viewer participant, err := shared.NewParticipant( - "", + sessionID, stream.Conn().RemotePeer(), ) if err != nil { @@ -185,11 +175,6 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) { continue } - // If this is a client session, link it - if session, ok := sp.relay.ClientSessions.Get(stream.Conn().RemotePeer()); ok { - participant.SessionID = session.SessionID - } - // Assign peer connection participant.PeerConnection = pc @@ -265,57 +250,9 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) { // Track controller input separately ndc.RegisterMessageCallback("controllerInput", func(data []byte) { // Parse the message to track controller slots for client sessions - var msgWrapper gen.ProtoMessage - if err = proto.Unmarshal(data, &msgWrapper); err != nil { + var controllerMsgWrapper gen.ProtoMessage + if err = proto.Unmarshal(data, &controllerMsgWrapper); err != nil { slog.Error("Failed to unmarshal controller input", "err", err) - } else if msgWrapper.Payload != nil { - // Get the peer ID for this connection - peerID := stream.Conn().RemotePeer() - - // Check if it's a controller attach with assigned slot - if attach := msgWrapper.GetControllerAttach(); attach != nil && attach.SessionSlot >= 0 { - if session, ok := sp.relay.ClientSessions.Get(peerID); ok { - // Check if slot already tracked - hasSlot := false - for _, slot := range session.ControllerSlots { - if slot == attach.SessionSlot { - hasSlot = true - break - } - } - if !hasSlot { - session.ControllerSlots = append(session.ControllerSlots, attach.SessionSlot) - session.LastActivity = time.Now() - slog.Info("Controller slot assigned to client session", - "session", session.SessionID, - "slot", attach.SessionSlot, - "total_slots", len(session.ControllerSlots)) - } - } - } - - // Check if it's a controller detach - if detach := msgWrapper.GetControllerDetach(); detach != nil && detach.SessionSlot >= 0 { - if session, ok := sp.relay.ClientSessions.Get(peerID); ok { - newSlots := make([]int32, 0, len(session.ControllerSlots)) - for _, slot := range session.ControllerSlots { - if slot != detach.SessionSlot { - newSlots = append(newSlots, slot) - } - } - session.ControllerSlots = newSlots - session.LastActivity = time.Now() - slog.Info("Controller slot removed from client session", - "session", session.SessionID, - "slot", detach.SessionSlot, - "remaining_slots", len(session.ControllerSlots)) - } - } - - // Update last activity on any controller input - if session, ok := sp.relay.ClientSessions.Get(peerID); ok { - session.LastActivity = time.Now() - } } // Forward to upstream room @@ -609,7 +546,12 @@ func (sp *StreamProtocol) handleStreamPush(stream network.Stream) { roomMap.Range(func(peerID peer.ID, conn *StreamConnection) bool { if conn.ndc != nil { if err = conn.ndc.SendBinary(data); err != nil { - slog.Error("Failed to forward controller input from pushed stream to viewer", "room", room.Name, "peer", peerID, "err", err) + if errors.Is(err, io.ErrClosedPipe) { + slog.Warn("Failed to forward controller input to viewer, treating as disconnected", "err", err) + sp.relay.onPeerDisconnected(peerID) + } else { + slog.Error("Failed to forward controller input from pushed stream to viewer", "room", room.Name, "peer", peerID, "err", err) + } } } return true diff --git a/packages/relay/internal/core/state.go b/packages/relay/internal/core/state.go index 9323eadc..46bfa69b 100644 --- a/packages/relay/internal/core/state.go +++ b/packages/relay/internal/core/state.go @@ -5,14 +5,9 @@ import ( "encoding/json" "errors" "log/slog" - "relay/internal/common" "relay/internal/shared" "time" - gen "relay/internal/proto" - - "google.golang.org/protobuf/proto" - pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" @@ -134,46 +129,6 @@ func (r *Relay) onPeerConnected(peerID peer.ID) { // onPeerDisconnected marks a peer as disconnected in our status view and removes latency info func (r *Relay) onPeerDisconnected(peerID peer.ID) { - // Check if this was a client session disconnect - if session, ok := r.ClientSessions.Get(peerID); ok { - slog.Info("Client session disconnected", - "peer", peerID, - "session", session.SessionID, - "room", session.RoomName, - "controller_slots", session.ControllerSlots) - - // Send cleanup message to nestri-server if client had active controllers - if len(session.ControllerSlots) > 0 { - room := r.GetRoomByName(session.RoomName) - if room != nil && room.DataChannel != nil { - // Create disconnect notification - disconnectMsg, err := common.CreateMessage(&gen.ProtoClientDisconnected{ - SessionId: session.SessionID, - ControllerSlots: session.ControllerSlots, - }, "client-disconnected", nil) - if err != nil { - slog.Error("Failed to create client disconnect message", "err", err) - } - - disMarshal, err := proto.Marshal(disconnectMsg) - if err != nil { - slog.Error("Failed to marshal client disconnect message", "err", err) - } else { - if err = room.DataChannel.SendBinary(disMarshal); err != nil { - slog.Error("Failed to send client disconnect notification", "err", err) - } else { - slog.Info("Sent controller cleanup notification to nestri-server", - "session", session.SessionID, - "slots", session.ControllerSlots) - } - } - } - } - - r.ClientSessions.Delete(peerID) - return - } - // Relay peer disconnect handling slog.Info("Mesh peer disconnected, deleting from local peer map", "peer", peerID) if r.Peers.Has(peerID) { diff --git a/packages/relay/internal/proto/messages.pb.go b/packages/relay/internal/proto/messages.pb.go index fb9a2f6e..19ec59e5 100644 --- a/packages/relay/internal/proto/messages.pb.go +++ b/packages/relay/internal/proto/messages.pb.go @@ -87,11 +87,8 @@ type ProtoMessage struct { // *ProtoMessage_KeyUp // *ProtoMessage_ControllerAttach // *ProtoMessage_ControllerDetach - // *ProtoMessage_ControllerButton - // *ProtoMessage_ControllerTrigger - // *ProtoMessage_ControllerStick - // *ProtoMessage_ControllerAxis // *ProtoMessage_ControllerRumble + // *ProtoMessage_ControllerStateBatch // *ProtoMessage_Ice // *ProtoMessage_Sdp // *ProtoMessage_Raw @@ -228,42 +225,6 @@ func (x *ProtoMessage) GetControllerDetach() *ProtoControllerDetach { return nil } -func (x *ProtoMessage) GetControllerButton() *ProtoControllerButton { - if x != nil { - if x, ok := x.Payload.(*ProtoMessage_ControllerButton); ok { - return x.ControllerButton - } - } - return nil -} - -func (x *ProtoMessage) GetControllerTrigger() *ProtoControllerTrigger { - if x != nil { - if x, ok := x.Payload.(*ProtoMessage_ControllerTrigger); ok { - return x.ControllerTrigger - } - } - return nil -} - -func (x *ProtoMessage) GetControllerStick() *ProtoControllerStick { - if x != nil { - if x, ok := x.Payload.(*ProtoMessage_ControllerStick); ok { - return x.ControllerStick - } - } - return nil -} - -func (x *ProtoMessage) GetControllerAxis() *ProtoControllerAxis { - if x != nil { - if x, ok := x.Payload.(*ProtoMessage_ControllerAxis); ok { - return x.ControllerAxis - } - } - return nil -} - func (x *ProtoMessage) GetControllerRumble() *ProtoControllerRumble { if x != nil { if x, ok := x.Payload.(*ProtoMessage_ControllerRumble); ok { @@ -273,6 +234,15 @@ func (x *ProtoMessage) GetControllerRumble() *ProtoControllerRumble { return nil } +func (x *ProtoMessage) GetControllerStateBatch() *ProtoControllerStateBatch { + if x != nil { + if x, ok := x.Payload.(*ProtoMessage_ControllerStateBatch); ok { + return x.ControllerStateBatch + } + } + return nil +} + func (x *ProtoMessage) GetIce() *ProtoICE { if x != nil { if x, ok := x.Payload.(*ProtoMessage_Ice); ok { @@ -361,6 +331,7 @@ type ProtoMessage_KeyUp struct { } type ProtoMessage_ControllerAttach struct { + // Controller input types ControllerAttach *ProtoControllerAttach `protobuf:"bytes,9,opt,name=controller_attach,json=controllerAttach,proto3,oneof"` } @@ -368,24 +339,12 @@ type ProtoMessage_ControllerDetach struct { ControllerDetach *ProtoControllerDetach `protobuf:"bytes,10,opt,name=controller_detach,json=controllerDetach,proto3,oneof"` } -type ProtoMessage_ControllerButton struct { - ControllerButton *ProtoControllerButton `protobuf:"bytes,11,opt,name=controller_button,json=controllerButton,proto3,oneof"` -} - -type ProtoMessage_ControllerTrigger struct { - ControllerTrigger *ProtoControllerTrigger `protobuf:"bytes,12,opt,name=controller_trigger,json=controllerTrigger,proto3,oneof"` -} - -type ProtoMessage_ControllerStick struct { - ControllerStick *ProtoControllerStick `protobuf:"bytes,13,opt,name=controller_stick,json=controllerStick,proto3,oneof"` -} - -type ProtoMessage_ControllerAxis struct { - ControllerAxis *ProtoControllerAxis `protobuf:"bytes,14,opt,name=controller_axis,json=controllerAxis,proto3,oneof"` -} - type ProtoMessage_ControllerRumble struct { - ControllerRumble *ProtoControllerRumble `protobuf:"bytes,15,opt,name=controller_rumble,json=controllerRumble,proto3,oneof"` + ControllerRumble *ProtoControllerRumble `protobuf:"bytes,11,opt,name=controller_rumble,json=controllerRumble,proto3,oneof"` +} + +type ProtoMessage_ControllerStateBatch struct { + ControllerStateBatch *ProtoControllerStateBatch `protobuf:"bytes,12,opt,name=controller_state_batch,json=controllerStateBatch,proto3,oneof"` } type ProtoMessage_Ice struct { @@ -431,16 +390,10 @@ func (*ProtoMessage_ControllerAttach) isProtoMessage_Payload() {} func (*ProtoMessage_ControllerDetach) isProtoMessage_Payload() {} -func (*ProtoMessage_ControllerButton) isProtoMessage_Payload() {} - -func (*ProtoMessage_ControllerTrigger) isProtoMessage_Payload() {} - -func (*ProtoMessage_ControllerStick) isProtoMessage_Payload() {} - -func (*ProtoMessage_ControllerAxis) isProtoMessage_Payload() {} - func (*ProtoMessage_ControllerRumble) isProtoMessage_Payload() {} +func (*ProtoMessage_ControllerStateBatch) isProtoMessage_Payload() {} + func (*ProtoMessage_Ice) isProtoMessage_Payload() {} func (*ProtoMessage_Sdp) isProtoMessage_Payload() {} @@ -460,8 +413,7 @@ const file_messages_proto_rawDesc = "" + "\x0emessages.proto\x12\x05proto\x1a\vtypes.proto\x1a\x15latency_tracker.proto\"k\n" + "\x10ProtoMessageBase\x12!\n" + "\fpayload_type\x18\x01 \x01(\tR\vpayloadType\x124\n" + - "\alatency\x18\x02 \x01(\v2\x1a.proto.ProtoLatencyTrackerR\alatency\"\xef\n" + - "\n" + + "\alatency\x18\x02 \x01(\v2\x1a.proto.ProtoLatencyTrackerR\alatency\"\x9b\t\n" + "\fProtoMessage\x12:\n" + "\fmessage_base\x18\x01 \x01(\v2\x17.proto.ProtoMessageBaseR\vmessageBase\x126\n" + "\n" + @@ -477,11 +429,8 @@ const file_messages_proto_rawDesc = "" + "\x11controller_attach\x18\t \x01(\v2\x1c.proto.ProtoControllerAttachH\x00R\x10controllerAttach\x12K\n" + "\x11controller_detach\x18\n" + " \x01(\v2\x1c.proto.ProtoControllerDetachH\x00R\x10controllerDetach\x12K\n" + - "\x11controller_button\x18\v \x01(\v2\x1c.proto.ProtoControllerButtonH\x00R\x10controllerButton\x12N\n" + - "\x12controller_trigger\x18\f \x01(\v2\x1d.proto.ProtoControllerTriggerH\x00R\x11controllerTrigger\x12H\n" + - "\x10controller_stick\x18\r \x01(\v2\x1b.proto.ProtoControllerStickH\x00R\x0fcontrollerStick\x12E\n" + - "\x0fcontroller_axis\x18\x0e \x01(\v2\x1a.proto.ProtoControllerAxisH\x00R\x0econtrollerAxis\x12K\n" + - "\x11controller_rumble\x18\x0f \x01(\v2\x1c.proto.ProtoControllerRumbleH\x00R\x10controllerRumble\x12#\n" + + "\x11controller_rumble\x18\v \x01(\v2\x1c.proto.ProtoControllerRumbleH\x00R\x10controllerRumble\x12X\n" + + "\x16controller_state_batch\x18\f \x01(\v2 .proto.ProtoControllerStateBatchH\x00R\x14controllerStateBatch\x12#\n" + "\x03ice\x18\x14 \x01(\v2\x0f.proto.ProtoICEH\x00R\x03ice\x12#\n" + "\x03sdp\x18\x15 \x01(\v2\x0f.proto.ProtoSDPH\x00R\x03sdp\x12#\n" + "\x03raw\x18\x16 \x01(\v2\x0f.proto.ProtoRawH\x00R\x03raw\x12b\n" + @@ -516,17 +465,14 @@ var file_messages_proto_goTypes = []any{ (*ProtoKeyUp)(nil), // 9: proto.ProtoKeyUp (*ProtoControllerAttach)(nil), // 10: proto.ProtoControllerAttach (*ProtoControllerDetach)(nil), // 11: proto.ProtoControllerDetach - (*ProtoControllerButton)(nil), // 12: proto.ProtoControllerButton - (*ProtoControllerTrigger)(nil), // 13: proto.ProtoControllerTrigger - (*ProtoControllerStick)(nil), // 14: proto.ProtoControllerStick - (*ProtoControllerAxis)(nil), // 15: proto.ProtoControllerAxis - (*ProtoControllerRumble)(nil), // 16: proto.ProtoControllerRumble - (*ProtoICE)(nil), // 17: proto.ProtoICE - (*ProtoSDP)(nil), // 18: proto.ProtoSDP - (*ProtoRaw)(nil), // 19: proto.ProtoRaw - (*ProtoClientRequestRoomStream)(nil), // 20: proto.ProtoClientRequestRoomStream - (*ProtoClientDisconnected)(nil), // 21: proto.ProtoClientDisconnected - (*ProtoServerPushStream)(nil), // 22: proto.ProtoServerPushStream + (*ProtoControllerRumble)(nil), // 12: proto.ProtoControllerRumble + (*ProtoControllerStateBatch)(nil), // 13: proto.ProtoControllerStateBatch + (*ProtoICE)(nil), // 14: proto.ProtoICE + (*ProtoSDP)(nil), // 15: proto.ProtoSDP + (*ProtoRaw)(nil), // 16: proto.ProtoRaw + (*ProtoClientRequestRoomStream)(nil), // 17: proto.ProtoClientRequestRoomStream + (*ProtoClientDisconnected)(nil), // 18: proto.ProtoClientDisconnected + (*ProtoServerPushStream)(nil), // 19: proto.ProtoServerPushStream } var file_messages_proto_depIdxs = []int32{ 2, // 0: proto.ProtoMessageBase.latency:type_name -> proto.ProtoLatencyTracker @@ -540,22 +486,19 @@ var file_messages_proto_depIdxs = []int32{ 9, // 8: proto.ProtoMessage.key_up:type_name -> proto.ProtoKeyUp 10, // 9: proto.ProtoMessage.controller_attach:type_name -> proto.ProtoControllerAttach 11, // 10: proto.ProtoMessage.controller_detach:type_name -> proto.ProtoControllerDetach - 12, // 11: proto.ProtoMessage.controller_button:type_name -> proto.ProtoControllerButton - 13, // 12: proto.ProtoMessage.controller_trigger:type_name -> proto.ProtoControllerTrigger - 14, // 13: proto.ProtoMessage.controller_stick:type_name -> proto.ProtoControllerStick - 15, // 14: proto.ProtoMessage.controller_axis:type_name -> proto.ProtoControllerAxis - 16, // 15: proto.ProtoMessage.controller_rumble:type_name -> proto.ProtoControllerRumble - 17, // 16: proto.ProtoMessage.ice:type_name -> proto.ProtoICE - 18, // 17: proto.ProtoMessage.sdp:type_name -> proto.ProtoSDP - 19, // 18: proto.ProtoMessage.raw:type_name -> proto.ProtoRaw - 20, // 19: proto.ProtoMessage.client_request_room_stream:type_name -> proto.ProtoClientRequestRoomStream - 21, // 20: proto.ProtoMessage.client_disconnected:type_name -> proto.ProtoClientDisconnected - 22, // 21: proto.ProtoMessage.server_push_stream:type_name -> proto.ProtoServerPushStream - 22, // [22:22] is the sub-list for method output_type - 22, // [22:22] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 12, // 11: proto.ProtoMessage.controller_rumble:type_name -> proto.ProtoControllerRumble + 13, // 12: proto.ProtoMessage.controller_state_batch:type_name -> proto.ProtoControllerStateBatch + 14, // 13: proto.ProtoMessage.ice:type_name -> proto.ProtoICE + 15, // 14: proto.ProtoMessage.sdp:type_name -> proto.ProtoSDP + 16, // 15: proto.ProtoMessage.raw:type_name -> proto.ProtoRaw + 17, // 16: proto.ProtoMessage.client_request_room_stream:type_name -> proto.ProtoClientRequestRoomStream + 18, // 17: proto.ProtoMessage.client_disconnected:type_name -> proto.ProtoClientDisconnected + 19, // 18: proto.ProtoMessage.server_push_stream:type_name -> proto.ProtoServerPushStream + 19, // [19:19] is the sub-list for method output_type + 19, // [19:19] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name } func init() { file_messages_proto_init() } @@ -575,11 +518,8 @@ func file_messages_proto_init() { (*ProtoMessage_KeyUp)(nil), (*ProtoMessage_ControllerAttach)(nil), (*ProtoMessage_ControllerDetach)(nil), - (*ProtoMessage_ControllerButton)(nil), - (*ProtoMessage_ControllerTrigger)(nil), - (*ProtoMessage_ControllerStick)(nil), - (*ProtoMessage_ControllerAxis)(nil), (*ProtoMessage_ControllerRumble)(nil), + (*ProtoMessage_ControllerStateBatch)(nil), (*ProtoMessage_Ice)(nil), (*ProtoMessage_Sdp)(nil), (*ProtoMessage_Raw)(nil), diff --git a/packages/relay/internal/proto/types.pb.go b/packages/relay/internal/proto/types.pb.go index e71fa071..917530d8 100644 --- a/packages/relay/internal/proto/types.pb.go +++ b/packages/relay/internal/proto/types.pb.go @@ -21,6 +21,52 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type ProtoControllerStateBatch_UpdateType int32 + +const ( + ProtoControllerStateBatch_FULL_STATE ProtoControllerStateBatch_UpdateType = 0 // Complete controller state + ProtoControllerStateBatch_DELTA ProtoControllerStateBatch_UpdateType = 1 // Only changed fields +) + +// Enum value maps for ProtoControllerStateBatch_UpdateType. +var ( + ProtoControllerStateBatch_UpdateType_name = map[int32]string{ + 0: "FULL_STATE", + 1: "DELTA", + } + ProtoControllerStateBatch_UpdateType_value = map[string]int32{ + "FULL_STATE": 0, + "DELTA": 1, + } +) + +func (x ProtoControllerStateBatch_UpdateType) Enum() *ProtoControllerStateBatch_UpdateType { + p := new(ProtoControllerStateBatch_UpdateType) + *p = x + return p +} + +func (x ProtoControllerStateBatch_UpdateType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ProtoControllerStateBatch_UpdateType) Descriptor() protoreflect.EnumDescriptor { + return file_types_proto_enumTypes[0].Descriptor() +} + +func (ProtoControllerStateBatch_UpdateType) Type() protoreflect.EnumType { + return &file_types_proto_enumTypes[0] +} + +func (x ProtoControllerStateBatch_UpdateType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ProtoControllerStateBatch_UpdateType.Descriptor instead. +func (ProtoControllerStateBatch_UpdateType) EnumDescriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{10, 0} +} + // MouseMove message type ProtoMouseMove struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -474,290 +520,6 @@ func (x *ProtoControllerDetach) GetSessionId() string { return "" } -// ControllerButton message -type ProtoControllerButton struct { - state protoimpl.MessageState `protogen:"open.v1"` - SessionSlot int32 `protobuf:"varint,1,opt,name=session_slot,json=sessionSlot,proto3" json:"session_slot,omitempty"` // Session specific slot number (0-3) - SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // Session ID of the client - Button int32 `protobuf:"varint,3,opt,name=button,proto3" json:"button,omitempty"` // Button code (linux input event code) - Pressed bool `protobuf:"varint,4,opt,name=pressed,proto3" json:"pressed,omitempty"` // true if pressed, false if released - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ProtoControllerButton) Reset() { - *x = ProtoControllerButton{} - mi := &file_types_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ProtoControllerButton) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ProtoControllerButton) ProtoMessage() {} - -func (x *ProtoControllerButton) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[9] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ProtoControllerButton.ProtoReflect.Descriptor instead. -func (*ProtoControllerButton) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{9} -} - -func (x *ProtoControllerButton) GetSessionSlot() int32 { - if x != nil { - return x.SessionSlot - } - return 0 -} - -func (x *ProtoControllerButton) GetSessionId() string { - if x != nil { - return x.SessionId - } - return "" -} - -func (x *ProtoControllerButton) GetButton() int32 { - if x != nil { - return x.Button - } - return 0 -} - -func (x *ProtoControllerButton) GetPressed() bool { - if x != nil { - return x.Pressed - } - return false -} - -// ControllerTriggers message -type ProtoControllerTrigger struct { - state protoimpl.MessageState `protogen:"open.v1"` - SessionSlot int32 `protobuf:"varint,1,opt,name=session_slot,json=sessionSlot,proto3" json:"session_slot,omitempty"` // Session specific slot number (0-3) - SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // Session ID of the client - Trigger int32 `protobuf:"varint,3,opt,name=trigger,proto3" json:"trigger,omitempty"` // Trigger number (0 for left, 1 for right) - Value int32 `protobuf:"varint,4,opt,name=value,proto3" json:"value,omitempty"` // trigger value (-32768 to 32767) - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ProtoControllerTrigger) Reset() { - *x = ProtoControllerTrigger{} - mi := &file_types_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ProtoControllerTrigger) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ProtoControllerTrigger) ProtoMessage() {} - -func (x *ProtoControllerTrigger) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[10] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ProtoControllerTrigger.ProtoReflect.Descriptor instead. -func (*ProtoControllerTrigger) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{10} -} - -func (x *ProtoControllerTrigger) GetSessionSlot() int32 { - if x != nil { - return x.SessionSlot - } - return 0 -} - -func (x *ProtoControllerTrigger) GetSessionId() string { - if x != nil { - return x.SessionId - } - return "" -} - -func (x *ProtoControllerTrigger) GetTrigger() int32 { - if x != nil { - return x.Trigger - } - return 0 -} - -func (x *ProtoControllerTrigger) GetValue() int32 { - if x != nil { - return x.Value - } - return 0 -} - -// ControllerSticks message -type ProtoControllerStick struct { - state protoimpl.MessageState `protogen:"open.v1"` - SessionSlot int32 `protobuf:"varint,1,opt,name=session_slot,json=sessionSlot,proto3" json:"session_slot,omitempty"` // Session specific slot number (0-3) - SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // Session ID of the client - Stick int32 `protobuf:"varint,3,opt,name=stick,proto3" json:"stick,omitempty"` // Stick number (0 for left, 1 for right) - X int32 `protobuf:"varint,4,opt,name=x,proto3" json:"x,omitempty"` // X axis value (-32768 to 32767) - Y int32 `protobuf:"varint,5,opt,name=y,proto3" json:"y,omitempty"` // Y axis value (-32768 to 32767) - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ProtoControllerStick) Reset() { - *x = ProtoControllerStick{} - mi := &file_types_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ProtoControllerStick) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ProtoControllerStick) ProtoMessage() {} - -func (x *ProtoControllerStick) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[11] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ProtoControllerStick.ProtoReflect.Descriptor instead. -func (*ProtoControllerStick) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{11} -} - -func (x *ProtoControllerStick) GetSessionSlot() int32 { - if x != nil { - return x.SessionSlot - } - return 0 -} - -func (x *ProtoControllerStick) GetSessionId() string { - if x != nil { - return x.SessionId - } - return "" -} - -func (x *ProtoControllerStick) GetStick() int32 { - if x != nil { - return x.Stick - } - return 0 -} - -func (x *ProtoControllerStick) GetX() int32 { - if x != nil { - return x.X - } - return 0 -} - -func (x *ProtoControllerStick) GetY() int32 { - if x != nil { - return x.Y - } - return 0 -} - -// ControllerAxis message -type ProtoControllerAxis struct { - state protoimpl.MessageState `protogen:"open.v1"` - SessionSlot int32 `protobuf:"varint,1,opt,name=session_slot,json=sessionSlot,proto3" json:"session_slot,omitempty"` // Session specific slot number (0-3) - SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // Session ID of the client - Axis int32 `protobuf:"varint,3,opt,name=axis,proto3" json:"axis,omitempty"` // Axis number (0 for d-pad horizontal, 1 for d-pad vertical) - Value int32 `protobuf:"varint,4,opt,name=value,proto3" json:"value,omitempty"` // axis value (-1 to 1) - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ProtoControllerAxis) Reset() { - *x = ProtoControllerAxis{} - mi := &file_types_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ProtoControllerAxis) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ProtoControllerAxis) ProtoMessage() {} - -func (x *ProtoControllerAxis) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[12] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ProtoControllerAxis.ProtoReflect.Descriptor instead. -func (*ProtoControllerAxis) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{12} -} - -func (x *ProtoControllerAxis) GetSessionSlot() int32 { - if x != nil { - return x.SessionSlot - } - return 0 -} - -func (x *ProtoControllerAxis) GetSessionId() string { - if x != nil { - return x.SessionId - } - return "" -} - -func (x *ProtoControllerAxis) GetAxis() int32 { - if x != nil { - return x.Axis - } - return 0 -} - -func (x *ProtoControllerAxis) GetValue() int32 { - if x != nil { - return x.Value - } - return 0 -} - // ControllerRumble message type ProtoControllerRumble struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -772,7 +534,7 @@ type ProtoControllerRumble struct { func (x *ProtoControllerRumble) Reset() { *x = ProtoControllerRumble{} - mi := &file_types_proto_msgTypes[13] + mi := &file_types_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -784,7 +546,7 @@ func (x *ProtoControllerRumble) String() string { func (*ProtoControllerRumble) ProtoMessage() {} func (x *ProtoControllerRumble) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[13] + mi := &file_types_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -797,7 +559,7 @@ func (x *ProtoControllerRumble) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoControllerRumble.ProtoReflect.Descriptor instead. func (*ProtoControllerRumble) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{13} + return file_types_proto_rawDescGZIP(), []int{9} } func (x *ProtoControllerRumble) GetSessionSlot() int32 { @@ -835,6 +597,160 @@ func (x *ProtoControllerRumble) GetDuration() int32 { return 0 } +// ControllerStateBatch - single message containing full or partial controller state +type ProtoControllerStateBatch struct { + state protoimpl.MessageState `protogen:"open.v1"` + SessionSlot int32 `protobuf:"varint,1,opt,name=session_slot,json=sessionSlot,proto3" json:"session_slot,omitempty"` // Session specific slot number (0-3) + SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // Session ID of the client + UpdateType ProtoControllerStateBatch_UpdateType `protobuf:"varint,3,opt,name=update_type,json=updateType,proto3,enum=proto.ProtoControllerStateBatch_UpdateType" json:"update_type,omitempty"` + // Sequence number for packet loss detection + Sequence uint32 `protobuf:"varint,4,opt,name=sequence,proto3" json:"sequence,omitempty"` + // Button state map (Linux event codes) + ButtonChangedMask map[int32]bool `protobuf:"bytes,5,rep,name=button_changed_mask,json=buttonChangedMask,proto3" json:"button_changed_mask,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + // Analog inputs + LeftStickX *int32 `protobuf:"varint,6,opt,name=left_stick_x,json=leftStickX,proto3,oneof" json:"left_stick_x,omitempty"` // -32768 to 32767 + LeftStickY *int32 `protobuf:"varint,7,opt,name=left_stick_y,json=leftStickY,proto3,oneof" json:"left_stick_y,omitempty"` // -32768 to 32767 + RightStickX *int32 `protobuf:"varint,8,opt,name=right_stick_x,json=rightStickX,proto3,oneof" json:"right_stick_x,omitempty"` // -32768 to 32767 + RightStickY *int32 `protobuf:"varint,9,opt,name=right_stick_y,json=rightStickY,proto3,oneof" json:"right_stick_y,omitempty"` // -32768 to 32767 + LeftTrigger *int32 `protobuf:"varint,10,opt,name=left_trigger,json=leftTrigger,proto3,oneof" json:"left_trigger,omitempty"` // -32768 to 32767 + RightTrigger *int32 `protobuf:"varint,11,opt,name=right_trigger,json=rightTrigger,proto3,oneof" json:"right_trigger,omitempty"` // -32768 to 32767 + DpadX *int32 `protobuf:"varint,12,opt,name=dpad_x,json=dpadX,proto3,oneof" json:"dpad_x,omitempty"` // -1, 0, or 1 + DpadY *int32 `protobuf:"varint,13,opt,name=dpad_y,json=dpadY,proto3,oneof" json:"dpad_y,omitempty"` // -1, 0, or 1 + // Bitmask indicating which fields have changed + // Bit 0: button_changed_mask, Bit 1: left_stick_x, Bit 2: left_stick_y, etc. + ChangedFields *uint32 `protobuf:"varint,14,opt,name=changed_fields,json=changedFields,proto3,oneof" json:"changed_fields,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProtoControllerStateBatch) Reset() { + *x = ProtoControllerStateBatch{} + mi := &file_types_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProtoControllerStateBatch) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProtoControllerStateBatch) ProtoMessage() {} + +func (x *ProtoControllerStateBatch) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProtoControllerStateBatch.ProtoReflect.Descriptor instead. +func (*ProtoControllerStateBatch) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{10} +} + +func (x *ProtoControllerStateBatch) GetSessionSlot() int32 { + if x != nil { + return x.SessionSlot + } + return 0 +} + +func (x *ProtoControllerStateBatch) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *ProtoControllerStateBatch) GetUpdateType() ProtoControllerStateBatch_UpdateType { + if x != nil { + return x.UpdateType + } + return ProtoControllerStateBatch_FULL_STATE +} + +func (x *ProtoControllerStateBatch) GetSequence() uint32 { + if x != nil { + return x.Sequence + } + return 0 +} + +func (x *ProtoControllerStateBatch) GetButtonChangedMask() map[int32]bool { + if x != nil { + return x.ButtonChangedMask + } + return nil +} + +func (x *ProtoControllerStateBatch) GetLeftStickX() int32 { + if x != nil && x.LeftStickX != nil { + return *x.LeftStickX + } + return 0 +} + +func (x *ProtoControllerStateBatch) GetLeftStickY() int32 { + if x != nil && x.LeftStickY != nil { + return *x.LeftStickY + } + return 0 +} + +func (x *ProtoControllerStateBatch) GetRightStickX() int32 { + if x != nil && x.RightStickX != nil { + return *x.RightStickX + } + return 0 +} + +func (x *ProtoControllerStateBatch) GetRightStickY() int32 { + if x != nil && x.RightStickY != nil { + return *x.RightStickY + } + return 0 +} + +func (x *ProtoControllerStateBatch) GetLeftTrigger() int32 { + if x != nil && x.LeftTrigger != nil { + return *x.LeftTrigger + } + return 0 +} + +func (x *ProtoControllerStateBatch) GetRightTrigger() int32 { + if x != nil && x.RightTrigger != nil { + return *x.RightTrigger + } + return 0 +} + +func (x *ProtoControllerStateBatch) GetDpadX() int32 { + if x != nil && x.DpadX != nil { + return *x.DpadX + } + return 0 +} + +func (x *ProtoControllerStateBatch) GetDpadY() int32 { + if x != nil && x.DpadY != nil { + return *x.DpadY + } + return 0 +} + +func (x *ProtoControllerStateBatch) GetChangedFields() uint32 { + if x != nil && x.ChangedFields != nil { + return *x.ChangedFields + } + return 0 +} + type RTCIceCandidateInit struct { state protoimpl.MessageState `protogen:"open.v1"` Candidate string `protobuf:"bytes,1,opt,name=candidate,proto3" json:"candidate,omitempty"` @@ -847,7 +763,7 @@ type RTCIceCandidateInit struct { func (x *RTCIceCandidateInit) Reset() { *x = RTCIceCandidateInit{} - mi := &file_types_proto_msgTypes[14] + mi := &file_types_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -859,7 +775,7 @@ func (x *RTCIceCandidateInit) String() string { func (*RTCIceCandidateInit) ProtoMessage() {} func (x *RTCIceCandidateInit) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[14] + mi := &file_types_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -872,7 +788,7 @@ func (x *RTCIceCandidateInit) ProtoReflect() protoreflect.Message { // Deprecated: Use RTCIceCandidateInit.ProtoReflect.Descriptor instead. func (*RTCIceCandidateInit) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{14} + return file_types_proto_rawDescGZIP(), []int{11} } func (x *RTCIceCandidateInit) GetCandidate() string { @@ -913,7 +829,7 @@ type RTCSessionDescriptionInit struct { func (x *RTCSessionDescriptionInit) Reset() { *x = RTCSessionDescriptionInit{} - mi := &file_types_proto_msgTypes[15] + mi := &file_types_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -925,7 +841,7 @@ func (x *RTCSessionDescriptionInit) String() string { func (*RTCSessionDescriptionInit) ProtoMessage() {} func (x *RTCSessionDescriptionInit) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[15] + mi := &file_types_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -938,7 +854,7 @@ func (x *RTCSessionDescriptionInit) ProtoReflect() protoreflect.Message { // Deprecated: Use RTCSessionDescriptionInit.ProtoReflect.Descriptor instead. func (*RTCSessionDescriptionInit) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{15} + return file_types_proto_rawDescGZIP(), []int{12} } func (x *RTCSessionDescriptionInit) GetSdp() string { @@ -965,7 +881,7 @@ type ProtoICE struct { func (x *ProtoICE) Reset() { *x = ProtoICE{} - mi := &file_types_proto_msgTypes[16] + mi := &file_types_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -977,7 +893,7 @@ func (x *ProtoICE) String() string { func (*ProtoICE) ProtoMessage() {} func (x *ProtoICE) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[16] + mi := &file_types_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -990,7 +906,7 @@ func (x *ProtoICE) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoICE.ProtoReflect.Descriptor instead. func (*ProtoICE) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{16} + return file_types_proto_rawDescGZIP(), []int{13} } func (x *ProtoICE) GetCandidate() *RTCIceCandidateInit { @@ -1010,7 +926,7 @@ type ProtoSDP struct { func (x *ProtoSDP) Reset() { *x = ProtoSDP{} - mi := &file_types_proto_msgTypes[17] + mi := &file_types_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1022,7 +938,7 @@ func (x *ProtoSDP) String() string { func (*ProtoSDP) ProtoMessage() {} func (x *ProtoSDP) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[17] + mi := &file_types_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1035,7 +951,7 @@ func (x *ProtoSDP) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoSDP.ProtoReflect.Descriptor instead. func (*ProtoSDP) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{17} + return file_types_proto_rawDescGZIP(), []int{14} } func (x *ProtoSDP) GetSdp() *RTCSessionDescriptionInit { @@ -1055,7 +971,7 @@ type ProtoRaw struct { func (x *ProtoRaw) Reset() { *x = ProtoRaw{} - mi := &file_types_proto_msgTypes[18] + mi := &file_types_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1067,7 +983,7 @@ func (x *ProtoRaw) String() string { func (*ProtoRaw) ProtoMessage() {} func (x *ProtoRaw) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[18] + mi := &file_types_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1080,7 +996,7 @@ func (x *ProtoRaw) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoRaw.ProtoReflect.Descriptor instead. func (*ProtoRaw) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{18} + return file_types_proto_rawDescGZIP(), []int{15} } func (x *ProtoRaw) GetData() string { @@ -1101,7 +1017,7 @@ type ProtoClientRequestRoomStream struct { func (x *ProtoClientRequestRoomStream) Reset() { *x = ProtoClientRequestRoomStream{} - mi := &file_types_proto_msgTypes[19] + mi := &file_types_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1113,7 +1029,7 @@ func (x *ProtoClientRequestRoomStream) String() string { func (*ProtoClientRequestRoomStream) ProtoMessage() {} func (x *ProtoClientRequestRoomStream) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[19] + mi := &file_types_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1126,7 +1042,7 @@ func (x *ProtoClientRequestRoomStream) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoClientRequestRoomStream.ProtoReflect.Descriptor instead. func (*ProtoClientRequestRoomStream) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{19} + return file_types_proto_rawDescGZIP(), []int{16} } func (x *ProtoClientRequestRoomStream) GetRoomName() string { @@ -1154,7 +1070,7 @@ type ProtoClientDisconnected struct { func (x *ProtoClientDisconnected) Reset() { *x = ProtoClientDisconnected{} - mi := &file_types_proto_msgTypes[20] + mi := &file_types_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1166,7 +1082,7 @@ func (x *ProtoClientDisconnected) String() string { func (*ProtoClientDisconnected) ProtoMessage() {} func (x *ProtoClientDisconnected) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[20] + mi := &file_types_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1179,7 +1095,7 @@ func (x *ProtoClientDisconnected) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoClientDisconnected.ProtoReflect.Descriptor instead. func (*ProtoClientDisconnected) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{20} + return file_types_proto_rawDescGZIP(), []int{17} } func (x *ProtoClientDisconnected) GetSessionId() string { @@ -1206,7 +1122,7 @@ type ProtoServerPushStream struct { func (x *ProtoServerPushStream) Reset() { *x = ProtoServerPushStream{} - mi := &file_types_proto_msgTypes[21] + mi := &file_types_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1218,7 +1134,7 @@ func (x *ProtoServerPushStream) String() string { func (*ProtoServerPushStream) ProtoMessage() {} func (x *ProtoServerPushStream) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[21] + mi := &file_types_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1231,7 +1147,7 @@ func (x *ProtoServerPushStream) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoServerPushStream.ProtoReflect.Descriptor instead. func (*ProtoServerPushStream) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{21} + return file_types_proto_rawDescGZIP(), []int{18} } func (x *ProtoServerPushStream) GetRoomName() string { @@ -1272,39 +1188,51 @@ const file_types_proto_rawDesc = "" + "\x15ProtoControllerDetach\x12!\n" + "\fsession_slot\x18\x01 \x01(\x05R\vsessionSlot\x12\x1d\n" + "\n" + - "session_id\x18\x02 \x01(\tR\tsessionId\"\x8b\x01\n" + - "\x15ProtoControllerButton\x12!\n" + - "\fsession_slot\x18\x01 \x01(\x05R\vsessionSlot\x12\x1d\n" + - "\n" + - "session_id\x18\x02 \x01(\tR\tsessionId\x12\x16\n" + - "\x06button\x18\x03 \x01(\x05R\x06button\x12\x18\n" + - "\apressed\x18\x04 \x01(\bR\apressed\"\x8a\x01\n" + - "\x16ProtoControllerTrigger\x12!\n" + - "\fsession_slot\x18\x01 \x01(\x05R\vsessionSlot\x12\x1d\n" + - "\n" + - "session_id\x18\x02 \x01(\tR\tsessionId\x12\x18\n" + - "\atrigger\x18\x03 \x01(\x05R\atrigger\x12\x14\n" + - "\x05value\x18\x04 \x01(\x05R\x05value\"\x8a\x01\n" + - "\x14ProtoControllerStick\x12!\n" + - "\fsession_slot\x18\x01 \x01(\x05R\vsessionSlot\x12\x1d\n" + - "\n" + - "session_id\x18\x02 \x01(\tR\tsessionId\x12\x14\n" + - "\x05stick\x18\x03 \x01(\x05R\x05stick\x12\f\n" + - "\x01x\x18\x04 \x01(\x05R\x01x\x12\f\n" + - "\x01y\x18\x05 \x01(\x05R\x01y\"\x81\x01\n" + - "\x13ProtoControllerAxis\x12!\n" + - "\fsession_slot\x18\x01 \x01(\x05R\vsessionSlot\x12\x1d\n" + - "\n" + - "session_id\x18\x02 \x01(\tR\tsessionId\x12\x12\n" + - "\x04axis\x18\x03 \x01(\x05R\x04axis\x12\x14\n" + - "\x05value\x18\x04 \x01(\x05R\x05value\"\xc1\x01\n" + + "session_id\x18\x02 \x01(\tR\tsessionId\"\xc1\x01\n" + "\x15ProtoControllerRumble\x12!\n" + "\fsession_slot\x18\x01 \x01(\x05R\vsessionSlot\x12\x1d\n" + "\n" + "session_id\x18\x02 \x01(\tR\tsessionId\x12#\n" + "\rlow_frequency\x18\x03 \x01(\x05R\flowFrequency\x12%\n" + "\x0ehigh_frequency\x18\x04 \x01(\x05R\rhighFrequency\x12\x1a\n" + - "\bduration\x18\x05 \x01(\x05R\bduration\"\xde\x01\n" + + "\bduration\x18\x05 \x01(\x05R\bduration\"\x87\a\n" + + "\x19ProtoControllerStateBatch\x12!\n" + + "\fsession_slot\x18\x01 \x01(\x05R\vsessionSlot\x12\x1d\n" + + "\n" + + "session_id\x18\x02 \x01(\tR\tsessionId\x12L\n" + + "\vupdate_type\x18\x03 \x01(\x0e2+.proto.ProtoControllerStateBatch.UpdateTypeR\n" + + "updateType\x12\x1a\n" + + "\bsequence\x18\x04 \x01(\rR\bsequence\x12g\n" + + "\x13button_changed_mask\x18\x05 \x03(\v27.proto.ProtoControllerStateBatch.ButtonChangedMaskEntryR\x11buttonChangedMask\x12%\n" + + "\fleft_stick_x\x18\x06 \x01(\x05H\x00R\n" + + "leftStickX\x88\x01\x01\x12%\n" + + "\fleft_stick_y\x18\a \x01(\x05H\x01R\n" + + "leftStickY\x88\x01\x01\x12'\n" + + "\rright_stick_x\x18\b \x01(\x05H\x02R\vrightStickX\x88\x01\x01\x12'\n" + + "\rright_stick_y\x18\t \x01(\x05H\x03R\vrightStickY\x88\x01\x01\x12&\n" + + "\fleft_trigger\x18\n" + + " \x01(\x05H\x04R\vleftTrigger\x88\x01\x01\x12(\n" + + "\rright_trigger\x18\v \x01(\x05H\x05R\frightTrigger\x88\x01\x01\x12\x1a\n" + + "\x06dpad_x\x18\f \x01(\x05H\x06R\x05dpadX\x88\x01\x01\x12\x1a\n" + + "\x06dpad_y\x18\r \x01(\x05H\aR\x05dpadY\x88\x01\x01\x12*\n" + + "\x0echanged_fields\x18\x0e \x01(\rH\bR\rchangedFields\x88\x01\x01\x1aD\n" + + "\x16ButtonChangedMaskEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\x05R\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\bR\x05value:\x028\x01\"'\n" + + "\n" + + "UpdateType\x12\x0e\n" + + "\n" + + "FULL_STATE\x10\x00\x12\t\n" + + "\x05DELTA\x10\x01B\x0f\n" + + "\r_left_stick_xB\x0f\n" + + "\r_left_stick_yB\x10\n" + + "\x0e_right_stick_xB\x10\n" + + "\x0e_right_stick_yB\x0f\n" + + "\r_left_triggerB\x10\n" + + "\x0e_right_triggerB\t\n" + + "\a_dpad_xB\t\n" + + "\a_dpad_yB\x11\n" + + "\x0f_changed_fields\"\xde\x01\n" + "\x13RTCIceCandidateInit\x12\x1c\n" + "\tcandidate\x18\x01 \x01(\tR\tcandidate\x12)\n" + "\rsdpMLineIndex\x18\x02 \x01(\rH\x00R\rsdpMLineIndex\x88\x01\x01\x12\x1b\n" + @@ -1345,39 +1273,41 @@ func file_types_proto_rawDescGZIP() []byte { return file_types_proto_rawDescData } -var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 22) +var file_types_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_types_proto_goTypes = []any{ - (*ProtoMouseMove)(nil), // 0: proto.ProtoMouseMove - (*ProtoMouseMoveAbs)(nil), // 1: proto.ProtoMouseMoveAbs - (*ProtoMouseWheel)(nil), // 2: proto.ProtoMouseWheel - (*ProtoMouseKeyDown)(nil), // 3: proto.ProtoMouseKeyDown - (*ProtoMouseKeyUp)(nil), // 4: proto.ProtoMouseKeyUp - (*ProtoKeyDown)(nil), // 5: proto.ProtoKeyDown - (*ProtoKeyUp)(nil), // 6: proto.ProtoKeyUp - (*ProtoControllerAttach)(nil), // 7: proto.ProtoControllerAttach - (*ProtoControllerDetach)(nil), // 8: proto.ProtoControllerDetach - (*ProtoControllerButton)(nil), // 9: proto.ProtoControllerButton - (*ProtoControllerTrigger)(nil), // 10: proto.ProtoControllerTrigger - (*ProtoControllerStick)(nil), // 11: proto.ProtoControllerStick - (*ProtoControllerAxis)(nil), // 12: proto.ProtoControllerAxis - (*ProtoControllerRumble)(nil), // 13: proto.ProtoControllerRumble - (*RTCIceCandidateInit)(nil), // 14: proto.RTCIceCandidateInit - (*RTCSessionDescriptionInit)(nil), // 15: proto.RTCSessionDescriptionInit - (*ProtoICE)(nil), // 16: proto.ProtoICE - (*ProtoSDP)(nil), // 17: proto.ProtoSDP - (*ProtoRaw)(nil), // 18: proto.ProtoRaw - (*ProtoClientRequestRoomStream)(nil), // 19: proto.ProtoClientRequestRoomStream - (*ProtoClientDisconnected)(nil), // 20: proto.ProtoClientDisconnected - (*ProtoServerPushStream)(nil), // 21: proto.ProtoServerPushStream + (ProtoControllerStateBatch_UpdateType)(0), // 0: proto.ProtoControllerStateBatch.UpdateType + (*ProtoMouseMove)(nil), // 1: proto.ProtoMouseMove + (*ProtoMouseMoveAbs)(nil), // 2: proto.ProtoMouseMoveAbs + (*ProtoMouseWheel)(nil), // 3: proto.ProtoMouseWheel + (*ProtoMouseKeyDown)(nil), // 4: proto.ProtoMouseKeyDown + (*ProtoMouseKeyUp)(nil), // 5: proto.ProtoMouseKeyUp + (*ProtoKeyDown)(nil), // 6: proto.ProtoKeyDown + (*ProtoKeyUp)(nil), // 7: proto.ProtoKeyUp + (*ProtoControllerAttach)(nil), // 8: proto.ProtoControllerAttach + (*ProtoControllerDetach)(nil), // 9: proto.ProtoControllerDetach + (*ProtoControllerRumble)(nil), // 10: proto.ProtoControllerRumble + (*ProtoControllerStateBatch)(nil), // 11: proto.ProtoControllerStateBatch + (*RTCIceCandidateInit)(nil), // 12: proto.RTCIceCandidateInit + (*RTCSessionDescriptionInit)(nil), // 13: proto.RTCSessionDescriptionInit + (*ProtoICE)(nil), // 14: proto.ProtoICE + (*ProtoSDP)(nil), // 15: proto.ProtoSDP + (*ProtoRaw)(nil), // 16: proto.ProtoRaw + (*ProtoClientRequestRoomStream)(nil), // 17: proto.ProtoClientRequestRoomStream + (*ProtoClientDisconnected)(nil), // 18: proto.ProtoClientDisconnected + (*ProtoServerPushStream)(nil), // 19: proto.ProtoServerPushStream + nil, // 20: proto.ProtoControllerStateBatch.ButtonChangedMaskEntry } var file_types_proto_depIdxs = []int32{ - 14, // 0: proto.ProtoICE.candidate:type_name -> proto.RTCIceCandidateInit - 15, // 1: proto.ProtoSDP.sdp:type_name -> proto.RTCSessionDescriptionInit - 2, // [2:2] is the sub-list for method output_type - 2, // [2:2] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 0, // 0: proto.ProtoControllerStateBatch.update_type:type_name -> proto.ProtoControllerStateBatch.UpdateType + 20, // 1: proto.ProtoControllerStateBatch.button_changed_mask:type_name -> proto.ProtoControllerStateBatch.ButtonChangedMaskEntry + 12, // 2: proto.ProtoICE.candidate:type_name -> proto.RTCIceCandidateInit + 13, // 3: proto.ProtoSDP.sdp:type_name -> proto.RTCSessionDescriptionInit + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_types_proto_init() } @@ -1385,19 +1315,21 @@ func file_types_proto_init() { if File_types_proto != nil { return } - file_types_proto_msgTypes[14].OneofWrappers = []any{} + file_types_proto_msgTypes[10].OneofWrappers = []any{} + file_types_proto_msgTypes[11].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_proto_rawDesc), len(file_types_proto_rawDesc)), - NumEnums: 0, - NumMessages: 22, + NumEnums: 1, + NumMessages: 20, NumExtensions: 0, NumServices: 0, }, GoTypes: file_types_proto_goTypes, DependencyIndexes: file_types_proto_depIdxs, + EnumInfos: file_types_proto_enumTypes, MessageInfos: file_types_proto_msgTypes, }.Build() File_types_proto = out.File diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index ef4f4763..996a53fe 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -5079,9 +5079,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vimputti" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb370ee43e3ee4ca5329886e64dc5b27c83dc8cced5a63c2418777dac9a41a8" +checksum = "6440b3684270f355fb89193bfb0de957686119626b8b207f21d91024a892d05c" dependencies = [ "anyhow", "libc", diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 9da600f4..7516d1fe 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -22,7 +22,7 @@ rand = "0.9" rustls = { version = "0.23", features = ["ring"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -vimputti = "0.1.4" +vimputti = "0.1.7" chrono = "0.4" prost = "0.14" prost-types = "0.14" diff --git a/packages/server/src/input/controller.rs b/packages/server/src/input/controller.rs index d34e8600..284c2e07 100644 --- a/packages/server/src/input/controller.rs +++ b/packages/server/src/input/controller.rs @@ -29,10 +29,8 @@ impl ControllerInput { client: &vimputti::client::VimputtiClient, ) -> Result { let config = controller_string_to_type(&controller_type)?; - Ok(Self { - config: config.clone(), - device: client.create_device(config).await?, - }) + let device = client.create_device(config.clone()).await?; + Ok(Self { config, device }) } pub fn device_mut(&mut self) -> &mut vimputti::client::VirtualController { @@ -121,9 +119,60 @@ async fn command_loop( slot.session_id == session_id && slot.session_slot == session_slot as u32 }) .map(|(slot_num, _)| *slot_num); - let slot = existing_slot.or_else(|| get_free_slot(&controllers)); - if let Some(slot) = slot { + if let Some(existing_slot) = existing_slot { + if let Some(controller_slot) = controllers.get_mut(&existing_slot) { + let rumble_tx = rumble_tx.clone(); + let attach_tx = attach_tx.clone(); + + controller_slot + .controller + .device_mut() + .on_rumble(move |strong, weak, duration_ms| { + let _ = rumble_tx.try_send(( + existing_slot, + strong, + weak, + duration_ms, + data.session_id.clone(), + )); + }) + .await + .map_err(|e| { + tracing::warn!( + "Failed to register rumble callback for slot {}: {}", + existing_slot, + e + ); + }) + .ok(); + + // Return to attach_tx what slot was assigned + let attach_info = ProtoControllerAttach { + id: data.id.clone(), + session_slot: existing_slot as i32, + session_id: session_id.clone(), + }; + + match attach_tx.send(attach_info).await { + Ok(_) => { + tracing::info!( + "Controller {} re-attached to slot {} (session: {})", + data.id, + existing_slot, + session_id + ); + } + Err(e) => { + tracing::error!( + "Failed to send re-attach info for slot {}: {}", + existing_slot, + e + ); + } + } + } + } else if let Some(slot) = get_free_slot(&controllers) { if let Ok(mut controller) = ControllerInput::new(data.id.clone(), &vimputti_client).await { @@ -133,7 +182,13 @@ async fn command_loop( controller .device_mut() .on_rumble(move |strong, weak, duration_ms| { - let _ = rumble_tx.try_send((slot, strong, weak, duration_ms, data.session_id.clone())); + let _ = rumble_tx.try_send(( + slot, + strong, + weak, + duration_ms, + data.session_id.clone(), + )); }) .await .map_err(|e| { @@ -190,65 +245,10 @@ async fn command_loop( if controllers.remove(&(data.session_slot as u32)).is_some() { tracing::info!("Controller detached from slot {}", data.session_slot); } else { - tracing::warn!("No controller found in slot {} to detach", data.session_slot); - } - } - Payload::ControllerButton(data) => { - if let Some(controller) = controllers.get(&(data.session_slot as u32)) { - if let Some(button) = vimputti::Button::from_ev_code(data.button as u16) { - let device = controller.controller.device(); - device.button(button, data.pressed); - device.sync(); - } - } else { - tracing::warn!("Controller slot {} not found for button event", data.session_slot); - } - } - Payload::ControllerStick(data) => { - if let Some(controller) = controllers.get(&(data.session_slot as u32)) { - let device = controller.controller.device(); - if data.stick == 0 { - // Left stick - device.axis(vimputti::Axis::LeftStickX, data.x); - device.sync(); - device.axis(vimputti::Axis::LeftStickY, data.y); - } else if data.stick == 1 { - // Right stick - device.axis(vimputti::Axis::RightStickX, data.x); - device.sync(); - device.axis(vimputti::Axis::RightStickY, data.y); - } - device.sync(); - } else { - tracing::warn!("Controller slot {} not found for stick event", data.session_slot); - } - } - Payload::ControllerTrigger(data) => { - if let Some(controller) = controllers.get(&(data.session_slot as u32)) { - let device = controller.controller.device(); - if data.trigger == 0 { - // Left trigger - device.axis(vimputti::Axis::LowerLeftTrigger, data.value); - } else if data.trigger == 1 { - // Right trigger - device.axis(vimputti::Axis::LowerRightTrigger, data.value); - } - device.sync(); - } else { - tracing::warn!("Controller slot {} not found for trigger event", data.session_slot); - } - } - Payload::ControllerAxis(data) => { - if let Some(controller) = controllers.get(&(data.session_slot as u32)) { - let device = controller.controller.device(); - if data.axis == 0 { - // dpad x - device.axis(vimputti::Axis::DPadX, data.value); - } else if data.axis == 1 { - // dpad y - device.axis(vimputti::Axis::DPadY, data.value); - } - device.sync(); + tracing::warn!( + "No controller found in slot {} to detach", + data.session_slot + ); } } Payload::ClientDisconnected(data) => { @@ -274,6 +274,125 @@ async fn command_loop( } } } + Payload::ControllerStateBatch(data) => { + if let Some(controller) = controllers.get(&(data.session_slot as u32)) { + let device = controller.controller.device(); + + // Handle inputs based on update type + if data.update_type == 0 { + // FULL_STATE: Update all values + let _ = device.sync().await; + for (btn_code, pressed) in data.button_changed_mask { + if let Some(button) = vimputti::Button::from_ev_code(btn_code as u16) { + let _ = device.button(button, pressed).await; + let _ = device.sync().await; + } + } + if let Some(x) = data.left_stick_x { + let _ = device.axis(vimputti::Axis::LeftStickX, x).await; + let _ = device.sync().await; + } + if let Some(y) = data.left_stick_y { + let _ = device.axis(vimputti::Axis::LeftStickY, y).await; + let _ = device.sync().await; + } + if let Some(x) = data.right_stick_x { + let _ = device.axis(vimputti::Axis::RightStickX, x).await; + let _ = device.sync().await; + } + if let Some(y) = data.right_stick_y { + let _ = device.axis(vimputti::Axis::RightStickY, y).await; + let _ = device.sync().await; + } + if let Some(value) = data.left_trigger { + let _ = device.axis(vimputti::Axis::LowerLeftTrigger, value).await; + let _ = device.sync().await; + } + if let Some(value) = data.right_trigger { + let _ = device.axis(vimputti::Axis::LowerRightTrigger, value).await; + let _ = device.sync().await; + } + if let Some(x) = data.dpad_x { + let _ = device.axis(vimputti::Axis::DPadX, x).await; + let _ = device.sync().await; + } + if let Some(y) = data.dpad_y { + let _ = device.axis(vimputti::Axis::DPadY, y).await; + let _ = device.sync().await; + } + } else { + // DELTA: Only update changed values + if let Some(changed_fields) = data.changed_fields { + let _ = device.sync().await; + if (changed_fields & (1 << 0)) != 0 { + for (btn_code, pressed) in data.button_changed_mask { + if let Some(button) = + vimputti::Button::from_ev_code(btn_code as u16) + { + let _ = device.button(button, pressed).await; + let _ = device.sync().await; + } + } + } + if (changed_fields & (1 << 1)) != 0 { + if let Some(x) = data.left_stick_x { + let _ = device.axis(vimputti::Axis::LeftStickX, x).await; + let _ = device.sync().await; + } + } + if (changed_fields & (1 << 2)) != 0 { + if let Some(y) = data.left_stick_y { + let _ = device.axis(vimputti::Axis::LeftStickY, y).await; + let _ = device.sync().await; + } + } + if (changed_fields & (1 << 3)) != 0 { + if let Some(x) = data.right_stick_x { + let _ = device.axis(vimputti::Axis::RightStickX, x).await; + let _ = device.sync().await; + } + } + if (changed_fields & (1 << 4)) != 0 { + if let Some(y) = data.right_stick_y { + let _ = device.axis(vimputti::Axis::RightStickY, y).await; + let _ = device.sync().await; + } + } + if (changed_fields & (1 << 5)) != 0 { + if let Some(value) = data.left_trigger { + let _ = + device.axis(vimputti::Axis::LowerLeftTrigger, value).await; + let _ = device.sync().await; + } + } + if (changed_fields & (1 << 6)) != 0 { + if let Some(value) = data.right_trigger { + let _ = + device.axis(vimputti::Axis::LowerRightTrigger, value).await; + let _ = device.sync().await; + } + } + if (changed_fields & (1 << 7)) != 0 { + if let Some(x) = data.dpad_x { + let _ = device.axis(vimputti::Axis::DPadX, x).await; + let _ = device.sync().await; + } + } + if (changed_fields & (1 << 8)) != 0 { + if let Some(y) = data.dpad_y { + let _ = device.axis(vimputti::Axis::DPadY, y).await; + let _ = device.sync().await; + } + } + } + } + } else { + tracing::warn!( + "Controller slot {} not found for state batch event", + data.session_slot + ); + } + } _ => { //no-op } diff --git a/packages/server/src/proto/proto.rs b/packages/server/src/proto/proto.rs index 635b8f3e..a9d68c76 100644 --- a/packages/server/src/proto/proto.rs +++ b/packages/server/src/proto/proto.rs @@ -102,77 +102,6 @@ pub struct ProtoControllerDetach { #[prost(string, tag="2")] pub session_id: ::prost::alloc::string::String, } -/// ControllerButton message -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ProtoControllerButton { - /// Session specific slot number (0-3) - #[prost(int32, tag="1")] - pub session_slot: i32, - /// Session ID of the client - #[prost(string, tag="2")] - pub session_id: ::prost::alloc::string::String, - /// Button code (linux input event code) - #[prost(int32, tag="3")] - pub button: i32, - /// true if pressed, false if released - #[prost(bool, tag="4")] - pub pressed: bool, -} -/// ControllerTriggers message -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ProtoControllerTrigger { - /// Session specific slot number (0-3) - #[prost(int32, tag="1")] - pub session_slot: i32, - /// Session ID of the client - #[prost(string, tag="2")] - pub session_id: ::prost::alloc::string::String, - /// Trigger number (0 for left, 1 for right) - #[prost(int32, tag="3")] - pub trigger: i32, - /// trigger value (-32768 to 32767) - #[prost(int32, tag="4")] - pub value: i32, -} -/// ControllerSticks message -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ProtoControllerStick { - /// Session specific slot number (0-3) - #[prost(int32, tag="1")] - pub session_slot: i32, - /// Session ID of the client - #[prost(string, tag="2")] - pub session_id: ::prost::alloc::string::String, - /// Stick number (0 for left, 1 for right) - #[prost(int32, tag="3")] - pub stick: i32, - /// X axis value (-32768 to 32767) - #[prost(int32, tag="4")] - pub x: i32, - /// Y axis value (-32768 to 32767) - #[prost(int32, tag="5")] - pub y: i32, -} -/// ControllerAxis message -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ProtoControllerAxis { - /// Session specific slot number (0-3) - #[prost(int32, tag="1")] - pub session_slot: i32, - /// Session ID of the client - #[prost(string, tag="2")] - pub session_id: ::prost::alloc::string::String, - /// Axis number (0 for d-pad horizontal, 1 for d-pad vertical) - #[prost(int32, tag="3")] - pub axis: i32, - /// axis value (-1 to 1) - #[prost(int32, tag="4")] - pub value: i32, -} /// ControllerRumble message #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -193,6 +122,86 @@ pub struct ProtoControllerRumble { #[prost(int32, tag="5")] pub duration: i32, } +/// ControllerStateBatch - single message containing full or partial controller state +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ProtoControllerStateBatch { + /// Session specific slot number (0-3) + #[prost(int32, tag="1")] + pub session_slot: i32, + /// Session ID of the client + #[prost(string, tag="2")] + pub session_id: ::prost::alloc::string::String, + #[prost(enumeration="proto_controller_state_batch::UpdateType", tag="3")] + pub update_type: i32, + /// Sequence number for packet loss detection + #[prost(uint32, tag="4")] + pub sequence: u32, + /// Button state map (Linux event codes) + #[prost(map="int32, bool", tag="5")] + pub button_changed_mask: ::std::collections::HashMap, + /// Analog inputs + /// + /// -32768 to 32767 + #[prost(int32, optional, tag="6")] + pub left_stick_x: ::core::option::Option, + /// -32768 to 32767 + #[prost(int32, optional, tag="7")] + pub left_stick_y: ::core::option::Option, + /// -32768 to 32767 + #[prost(int32, optional, tag="8")] + pub right_stick_x: ::core::option::Option, + /// -32768 to 32767 + #[prost(int32, optional, tag="9")] + pub right_stick_y: ::core::option::Option, + /// -32768 to 32767 + #[prost(int32, optional, tag="10")] + pub left_trigger: ::core::option::Option, + /// -32768 to 32767 + #[prost(int32, optional, tag="11")] + pub right_trigger: ::core::option::Option, + /// -1, 0, or 1 + #[prost(int32, optional, tag="12")] + pub dpad_x: ::core::option::Option, + /// -1, 0, or 1 + #[prost(int32, optional, tag="13")] + pub dpad_y: ::core::option::Option, + /// Bitmask indicating which fields have changed + /// Bit 0: button_changed_mask, Bit 1: left_stick_x, Bit 2: left_stick_y, etc. + #[prost(uint32, optional, tag="14")] + pub changed_fields: ::core::option::Option, +} +/// Nested message and enum types in `ProtoControllerStateBatch`. +pub mod proto_controller_state_batch { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum UpdateType { + /// Complete controller state + FullState = 0, + /// Only changed fields + Delta = 1, + } + impl UpdateType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + UpdateType::FullState => "FULL_STATE", + UpdateType::Delta => "DELTA", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "FULL_STATE" => Some(Self::FullState), + "DELTA" => Some(Self::Delta), + _ => None, + } + } + } +} // WebRTC + signaling #[allow(clippy::derive_partial_eq_without_eq)] @@ -274,7 +283,7 @@ pub struct ProtoMessageBase { pub struct ProtoMessage { #[prost(message, optional, tag="1")] pub message_base: ::core::option::Option, - #[prost(oneof="proto_message::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 20, 21, 22, 23, 24, 25")] + #[prost(oneof="proto_message::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20, 21, 22, 23, 24, 25")] pub payload: ::core::option::Option, } /// Nested message and enum types in `ProtoMessage`. @@ -297,20 +306,15 @@ pub mod proto_message { KeyDown(super::ProtoKeyDown), #[prost(message, tag="8")] KeyUp(super::ProtoKeyUp), + /// Controller input types #[prost(message, tag="9")] ControllerAttach(super::ProtoControllerAttach), #[prost(message, tag="10")] ControllerDetach(super::ProtoControllerDetach), #[prost(message, tag="11")] - ControllerButton(super::ProtoControllerButton), - #[prost(message, tag="12")] - ControllerTrigger(super::ProtoControllerTrigger), - #[prost(message, tag="13")] - ControllerStick(super::ProtoControllerStick), - #[prost(message, tag="14")] - ControllerAxis(super::ProtoControllerAxis), - #[prost(message, tag="15")] ControllerRumble(super::ProtoControllerRumble), + #[prost(message, tag="12")] + ControllerStateBatch(super::ProtoControllerStateBatch), /// Signaling types #[prost(message, tag="20")] Ice(super::ProtoIce), diff --git a/protobufs/messages.proto b/protobufs/messages.proto index bc14fd1b..82d147ce 100644 --- a/protobufs/messages.proto +++ b/protobufs/messages.proto @@ -23,13 +23,12 @@ message ProtoMessage { ProtoMouseKeyUp mouse_key_up = 6; ProtoKeyDown key_down = 7; ProtoKeyUp key_up = 8; + + // Controller input types ProtoControllerAttach controller_attach = 9; ProtoControllerDetach controller_detach = 10; - ProtoControllerButton controller_button = 11; - ProtoControllerTrigger controller_trigger = 12; - ProtoControllerStick controller_stick = 13; - ProtoControllerAxis controller_axis = 14; - ProtoControllerRumble controller_rumble = 15; + ProtoControllerRumble controller_rumble = 11; + ProtoControllerStateBatch controller_state_batch = 12; // Signaling types ProtoICE ice = 20; diff --git a/protobufs/types.proto b/protobufs/types.proto index 1f951f16..deeed486 100644 --- a/protobufs/types.proto +++ b/protobufs/types.proto @@ -61,39 +61,6 @@ message ProtoControllerDetach { string session_id = 2; // Session ID of the client } -// ControllerButton message -message ProtoControllerButton { - int32 session_slot = 1; // Session specific slot number (0-3) - string session_id = 2; // Session ID of the client - int32 button = 3; // Button code (linux input event code) - bool pressed = 4; // true if pressed, false if released -} - -// ControllerTriggers message -message ProtoControllerTrigger { - int32 session_slot = 1; // Session specific slot number (0-3) - string session_id = 2; // Session ID of the client - int32 trigger = 3; // Trigger number (0 for left, 1 for right) - int32 value = 4; // trigger value (-32768 to 32767) -} - -// ControllerSticks message -message ProtoControllerStick { - int32 session_slot = 1; // Session specific slot number (0-3) - string session_id = 2; // Session ID of the client - int32 stick = 3; // Stick number (0 for left, 1 for right) - int32 x = 4; // X axis value (-32768 to 32767) - int32 y = 5; // Y axis value (-32768 to 32767) -} - -// ControllerAxis message -message ProtoControllerAxis { - int32 session_slot = 1; // Session specific slot number (0-3) - string session_id = 2; // Session ID of the client - int32 axis = 3; // Axis number (0 for d-pad horizontal, 1 for d-pad vertical) - int32 value = 4; // axis value (-1 to 1) -} - // ControllerRumble message message ProtoControllerRumble { int32 session_slot = 1; // Session specific slot number (0-3) @@ -103,6 +70,38 @@ message ProtoControllerRumble { int32 duration = 5; // Duration in milliseconds } +// ControllerStateBatch - single message containing full or partial controller state +message ProtoControllerStateBatch { + int32 session_slot = 1; // Session specific slot number (0-3) + string session_id = 2; // Session ID of the client + + enum UpdateType { + FULL_STATE = 0; // Complete controller state + DELTA = 1; // Only changed fields + } + UpdateType update_type = 3; + + // Sequence number for packet loss detection + uint32 sequence = 4; + + // Button state map (Linux event codes) + map button_changed_mask = 5; + + // Analog inputs + optional int32 left_stick_x = 6; // -32768 to 32767 + optional int32 left_stick_y = 7; // -32768 to 32767 + optional int32 right_stick_x = 8; // -32768 to 32767 + optional int32 right_stick_y = 9; // -32768 to 32767 + optional int32 left_trigger = 10; // -32768 to 32767 + optional int32 right_trigger = 11; // -32768 to 32767 + optional int32 dpad_x = 12; // -1, 0, or 1 + optional int32 dpad_y = 13; // -1, 0, or 1 + + // Bitmask indicating which fields have changed + // Bit 0: button_changed_mask, Bit 1: left_stick_x, Bit 2: left_stick_y, etc. + optional uint32 changed_fields = 14; +} + /* WebRTC + signaling */ message RTCIceCandidateInit {