feat(runner): Fixes and improvements (#259)

## Description

- Improves latency for runner
- Fixes bugs in entrypoint bash scripts
- Package updates, gstreamer 1.26 and workaround for it

Modified runner workflow to hopefully pull latest cachyos base image on
nightlies. This will cause a full build but for nightlies should be
fine?

Also removed the duplicate key-down workaround as we've enabled ordered
datachannels now. Increased retransmit to 2 from 0 to see if it'll help
with some network issues.

Marked as draft as I need to do bug testing still, I'll do it after
fever calms down 😅



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

## Summary by CodeRabbit

- **New Features**
- Enhanced deployment workflows with optimized container image
management.
- Improved audio and video processing for lower latency and better
synchronization.
  - Consolidated debugging options to ease command-line monitoring.

- **Refactor**
- Streamlined internal script flow and process handling for smoother
performance.
- Updated dependency management and communication protocols to boost
overall stability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
This commit is contained in:
Kristian Ollikainen
2025-04-13 23:13:09 +03:00
committed by GitHub
parent f408ec56cb
commit 9a6826b069
15 changed files with 1257 additions and 672 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -8,22 +8,24 @@ name = "nestri-server"
path = "src/main.rs"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main", features = ["v1_24"] }
gst-webrtc = { package = "gstreamer-webrtc", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main", features = ["v1_24"] }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main", features = ["v1_26"] }
gst-webrtc = { package = "gstreamer-webrtc", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main", features = ["v1_26"] }
gstrswebrtc = { package = "gst-plugin-webrtc", git = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs", branch = "main", features = ["v1_22"] }
serde = {version = "1.0.214", features = ["derive"] }
tokio = { version = "1.41.0", features = ["full"] }
clap = { version = "4.5.20", features = ["env"] }
serde_json = "1.0.132"
webrtc = "0.12.0"
regex = "1.11.1"
rand = "0.9.0"
rustls = { version = "0.23.17", features = ["ring"] }
tokio-tungstenite = { version = "0.26.1", features = ["native-tls"] }
log = { version = "0.4.22", features = ["std"] }
chrono = "0.4.38"
futures-util = "0.3.31"
num-derive = "0.4.2"
num-traits = "0.2.19"
prost = "0.13.4"
prost-types = "0.13.4"
serde = {version = "1.0", features = ["derive"] }
tokio = { version = "1.44", features = ["full"] }
clap = { version = "4.5", features = ["env"] }
serde_json = "1.0"
webrtc = "0.12"
regex = "1.11"
rand = "0.9"
rustls = { version = "0.23", features = ["ring"] }
tokio-tungstenite = { version = "0.26", features = ["native-tls"] }
log = { version = "0.4", features = ["std"] }
chrono = "0.4"
futures-util = "0.3"
num-derive = "0.4"
num-traits = "0.2"
prost = "0.13"
prost-types = "0.13"
parking_lot = "0.12"
atomic_refcell = "0.1"

View File

@@ -22,19 +22,11 @@ impl Args {
.default_value("false"),
)
.arg(
Arg::new("debug-feed")
Arg::new("debug")
.short('d')
.long("debug-feed")
.env("DEBUG_FEED")
.help("Debug by showing a window on host")
.default_value("false"),
)
.arg(
Arg::new("debug-latency")
.short('l')
.long("debug-latency")
.env("DEBUG_LATENCY")
.help("Debug latency by showing time on feed")
.long("debug")
.env("DEBUG")
.help("Enable additional debugging information and features")
.default_value("false"),
)
.arg(

View File

@@ -1,10 +1,8 @@
pub struct AppArgs {
/// Verbose output mode
pub verbose: bool,
/// Debug the pipeline by showing a window on host
pub debug_feed: bool,
/// Debug the latency by showing time in stream
pub debug_latency: bool,
/// Enable additional debug information and features, may affect performance
pub debug: bool,
/// Virtual display resolution
pub resolution: (u32, u32),
@@ -24,10 +22,8 @@ impl AppArgs {
Self {
verbose: matches.get_one::<String>("verbose").unwrap() == "true"
|| matches.get_one::<String>("verbose").unwrap() == "1",
debug_feed: matches.get_one::<String>("debug-feed").unwrap() == "true"
|| matches.get_one::<String>("debug-feed").unwrap() == "1",
debug_latency: matches.get_one::<String>("debug-latency").unwrap() == "true"
|| matches.get_one::<String>("debug-latency").unwrap() == "1",
debug: matches.get_one::<String>("debug").unwrap() == "true"
|| matches.get_one::<String>("debug").unwrap() == "1",
resolution: {
let res = matches
.get_one::<String>("resolution")
@@ -65,8 +61,7 @@ impl AppArgs {
pub fn debug_print(&self) {
println!("AppArgs:");
println!("> verbose: {}", self.verbose);
println!("> debug_feed: {}", self.debug_feed);
println!("> debug_latency: {}", self.debug_latency);
println!("> debug: {}", self.debug);
println!("> resolution: {}x{}", self.resolution.0, self.resolution.1);
println!("> framerate: {}", self.framerate);
println!("> relay_url: {}", self.relay_url);

View File

@@ -249,8 +249,10 @@ pub fn encoder_gop_params(encoder: &VideoEncoderInfo, gop_size: u32) -> VideoEnc
pub fn encoder_low_latency_params(
encoder: &VideoEncoderInfo,
rate_control: &RateControl,
framerate: u32,
) -> VideoEncoderInfo {
let mut encoder_optz = encoder_gop_params(encoder, 30);
// 2 second GOP size, maybe lower to 1 second for fast recovery, if needed?
let mut encoder_optz = encoder_gop_params(encoder, framerate * 2);
match encoder_optz.encoder_api {
EncoderAPI::QSV => {

View File

@@ -128,8 +128,11 @@ fn handle_encoder_video_settings(
args: &args::Args,
video_encoder: &enc_helper::VideoEncoderInfo,
) -> enc_helper::VideoEncoderInfo {
let mut optimized_encoder =
enc_helper::encoder_low_latency_params(&video_encoder, &args.encoding.video.rate_control);
let mut optimized_encoder = enc_helper::encoder_low_latency_params(
&video_encoder,
&args.encoding.video.rate_control,
args.app.framerate,
);
// Handle rate-control method
match &args.encoding.video.rate_control {
encoding_args::RateControl::CQP(cqp) => {
@@ -235,7 +238,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
encoding_args::AudioCaptureMethod::PipeWire => {
gst::ElementFactory::make("pipewiresrc").build()?
}
_ => gst::ElementFactory::make("alsasrc").build()?,
encoding_args::AudioCaptureMethod::ALSA => gst::ElementFactory::make("alsasrc").build()?,
};
// Audio Converter Element
@@ -259,6 +262,10 @@ async fn main() -> Result<(), Box<dyn Error>> {
_ => 128000i32,
},
);
// If has "frame-size" (opus), set to 10 for lower latency (below 10 seems to be too low?)
if audio_encoder.has_property("frame-size") {
audio_encoder.set_property_from_str("frame-size", "10");
}
/* Video */
// Video Source Element
@@ -299,6 +306,18 @@ async fn main() -> Result<(), Box<dyn Error>> {
let video_encoder = gst::ElementFactory::make(video_encoder_info.name.as_str()).build()?;
video_encoder_info.apply_parameters(&video_encoder, args.app.verbose);
// Video parser Element, required for GStreamer 1.26 as it broke some things..
let video_parser;
if video_encoder_info.codec == enc_helper::VideoCodec::H264 {
video_parser = Some(
gst::ElementFactory::make("h264parse")
.property("config-interval", -1i32)
.build()?,
);
} else {
video_parser = None;
}
/* Output */
// WebRTC sink Element
let signaller = NestriSignaller::new(nestri_ws.clone(), pipeline.clone());
@@ -307,20 +326,50 @@ async fn main() -> Result<(), Box<dyn Error>> {
webrtcsink.set_property_from_str("congestion-control", "disabled");
webrtcsink.set_property("do-retransmission", false);
/* Queues */
let video_queue = gst::ElementFactory::make("queue2")
.property("max-size-buffers", 3u32)
.property("max-size-time", 0u64)
.property("max-size-bytes", 0u32)
.build()?;
let audio_queue = gst::ElementFactory::make("queue2")
.property("max-size-buffers", 3u32)
.property("max-size-time", 0u64)
.property("max-size-bytes", 0u32)
.build()?;
/* Clock Sync */
let video_clocksync = gst::ElementFactory::make("clocksync")
.property("sync-to-first", true)
.build()?;
let audio_clocksync = gst::ElementFactory::make("clocksync")
.property("sync-to-first", true)
.build()?;
// Add elements to the pipeline
pipeline.add_many(&[
webrtcsink.upcast_ref(),
&video_encoder,
&video_converter,
&caps_filter,
&video_queue,
&video_clocksync,
&video_source,
&audio_encoder,
&audio_capsfilter,
&audio_queue,
&audio_clocksync,
&audio_rate,
&audio_converter,
&audio_source,
])?;
if let Some(parser) = &video_parser {
pipeline.add(parser)?;
}
// If DMA-BUF is enabled, add glupload, color conversion and caps filter
if args.app.dma_buf {
pipeline.add_many(&[&glupload, &glcolorconvert, &gl_caps_filter])?;
@@ -332,6 +381,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
&audio_converter,
&audio_rate,
&audio_capsfilter,
&audio_queue,
&audio_clocksync,
&audio_encoder,
webrtcsink.upcast_ref(),
])?;
@@ -342,30 +393,45 @@ async fn main() -> Result<(), Box<dyn Error>> {
gst::Element::link_many(&[
&video_source,
&caps_filter,
&video_queue,
&video_clocksync,
&glupload,
&glcolorconvert,
&gl_caps_filter,
&video_encoder,
webrtcsink.upcast_ref(),
])?;
} else {
// Link video source to caps_filter, video_converter, video_encoder, webrtcsink
gst::Element::link_many(&[
&video_source,
&caps_filter,
&video_queue,
&video_clocksync,
&video_converter,
&video_encoder,
webrtcsink.upcast_ref(),
])?;
}
// Link video parser if present with webrtcsink, otherwise just link webrtc sink
if let Some(parser) = &video_parser {
gst::Element::link_many(&[&video_encoder, parser, webrtcsink.upcast_ref()])?;
} else {
gst::Element::link_many(&[&video_encoder, webrtcsink.upcast_ref()])?;
}
// Set QOS
video_encoder.set_property("qos", true);
// Optimize latency of pipeline
video_source
.sync_state_with_parent()
.expect("failed to sync with parent");
video_source.set_property("do-timestamp", &true);
audio_source.set_property("do-timestamp", &true);
pipeline.set_property("latency", &0u64);
pipeline.set_property("async-handling", true);
pipeline.set_property("message-forward", true);
// Run both pipeline and websocket tasks concurrently
let result = run_pipeline(pipeline.clone()).await;

View File

@@ -7,47 +7,53 @@ use crate::proto::proto::proto_input::InputType::{
};
use crate::proto::proto::{ProtoInput, ProtoMessageInput};
use crate::websocket::NestriWebSocket;
use atomic_refcell::AtomicRefCell;
use glib::subclass::prelude::*;
use gst::glib;
use gst::prelude::*;
use gst_webrtc::{WebRTCSDPType, WebRTCSessionDescription, gst_sdp};
use gstrswebrtc::signaller::{Signallable, SignallableImpl};
use parking_lot::RwLock as PLRwLock;
use prost::Message;
use std::collections::HashSet;
use std::sync::{Arc, LazyLock};
use std::sync::{Mutex, RwLock};
use webrtc::ice_transport::ice_candidate::RTCIceCandidateInit;
use webrtc::peer_connection::sdp::session_description::RTCSessionDescription;
pub struct Signaller {
nestri_ws: RwLock<Option<Arc<NestriWebSocket>>>,
pipeline: RwLock<Option<Arc<gst::Pipeline>>>,
data_channel: RwLock<Option<gst_webrtc::WebRTCDataChannel>>,
nestri_ws: PLRwLock<Option<Arc<NestriWebSocket>>>,
pipeline: PLRwLock<Option<Arc<gst::Pipeline>>>,
data_channel: AtomicRefCell<Option<gst_webrtc::WebRTCDataChannel>>,
}
impl Default for Signaller {
fn default() -> Self {
Self {
nestri_ws: RwLock::new(None),
pipeline: RwLock::new(None),
data_channel: RwLock::new(None),
nestri_ws: PLRwLock::new(None),
pipeline: PLRwLock::new(None),
data_channel: AtomicRefCell::new(None),
}
}
}
impl Signaller {
pub fn set_nestri_ws(&self, nestri_ws: Arc<NestriWebSocket>) {
*self.nestri_ws.write().unwrap() = Some(nestri_ws);
*self.nestri_ws.write() = Some(nestri_ws);
}
pub fn set_pipeline(&self, pipeline: Arc<gst::Pipeline>) {
*self.pipeline.write().unwrap() = Some(pipeline);
*self.pipeline.write() = Some(pipeline);
}
pub fn get_pipeline(&self) -> Option<Arc<gst::Pipeline>> {
self.pipeline.read().unwrap().clone()
self.pipeline.read().clone()
}
pub fn set_data_channel(&self, data_channel: gst_webrtc::WebRTCDataChannel) {
*self.data_channel.write().unwrap() = Some(data_channel);
match self.data_channel.try_borrow_mut() {
Ok(mut dc) => *dc = Some(data_channel),
Err(_) => gst::warning!(
gst::CAT_DEFAULT,
"Failed to set data channel - already borrowed"
),
}
}
/// Helper method to clean things up
@@ -55,7 +61,6 @@ impl Signaller {
let nestri_ws = {
self.nestri_ws
.read()
.unwrap()
.clone()
.expect("NestriWebSocket not set")
};
@@ -145,8 +150,9 @@ impl Signaller {
&"nestri-data-channel",
&gst::Structure::builder("config")
.field("ordered", &true)
.field("max-retransmits", &0u32)
.field("max-retransmits", &2u32)
.field("priority", "high")
.field("protocol", "raw")
.build(),
],
),
@@ -175,7 +181,6 @@ impl SignallableImpl for Signaller {
let nestri_ws = {
self.nestri_ws
.read()
.unwrap()
.clone()
.expect("NestriWebSocket not set")
};
@@ -266,7 +271,6 @@ impl SignallableImpl for Signaller {
let nestri_ws = {
self.nestri_ws
.read()
.unwrap()
.clone()
.expect("NestriWebSocket not set")
};
@@ -297,7 +301,6 @@ impl SignallableImpl for Signaller {
let nestri_ws = {
self.nestri_ws
.read()
.unwrap()
.clone()
.expect("NestriWebSocket not set")
};
@@ -360,9 +363,6 @@ impl ObjectImpl for Signaller {
fn setup_data_channel(data_channel: &gst_webrtc::WebRTCDataChannel, pipeline: &gst::Pipeline) {
let pipeline = pipeline.clone();
// A shared state to track currently pressed keys
let pressed_keys = Arc::new(Mutex::new(HashSet::new()));
let pressed_buttons = Arc::new(Mutex::new(HashSet::new()));
data_channel.connect_on_message_data(move |_data_channel, data| {
if let Some(data) = data {
@@ -370,9 +370,7 @@ fn setup_data_channel(data_channel: &gst_webrtc::WebRTCDataChannel, pipeline: &g
Ok(message_input) => {
if let Some(input_msg) = message_input.data {
// Process the input message and create an event
if let Some(event) =
handle_input_message(input_msg, &pressed_keys, &pressed_buttons)
{
if let Some(event) = handle_input_message(input_msg) {
// Send the event to pipeline, result bool is ignored
let _ = pipeline.send_event(event);
}
@@ -388,11 +386,7 @@ fn setup_data_channel(data_channel: &gst_webrtc::WebRTCDataChannel, pipeline: &g
});
}
fn handle_input_message(
input_msg: ProtoInput,
pressed_keys: &Arc<Mutex<HashSet<i32>>>,
pressed_buttons: &Arc<Mutex<HashSet<i32>>>,
) -> Option<gst::Event> {
fn handle_input_message(input_msg: ProtoInput) -> Option<gst::Event> {
if let Some(input_type) = input_msg.input_type {
match input_type {
MouseMove(data) => {
@@ -412,13 +406,6 @@ fn handle_input_message(
Some(gst::event::CustomUpstream::new(structure))
}
KeyDown(data) => {
let mut keys = pressed_keys.lock().unwrap();
// If the key is already pressed, return to prevent key lockup
if keys.contains(&data.key) {
return None;
}
keys.insert(data.key);
let structure = gst::Structure::builder("KeyboardKey")
.field("key", data.key as u32)
.field("pressed", true)
@@ -427,10 +414,6 @@ fn handle_input_message(
Some(gst::event::CustomUpstream::new(structure))
}
KeyUp(data) => {
let mut keys = pressed_keys.lock().unwrap();
// Remove the key from the pressed state when released
keys.remove(&data.key);
let structure = gst::Structure::builder("KeyboardKey")
.field("key", data.key as u32)
.field("pressed", false)
@@ -447,13 +430,6 @@ fn handle_input_message(
Some(gst::event::CustomUpstream::new(structure))
}
MouseKeyDown(data) => {
let mut buttons = pressed_buttons.lock().unwrap();
// If the button is already pressed, return to prevent button lockup
if buttons.contains(&data.key) {
return None;
}
buttons.insert(data.key);
let structure = gst::Structure::builder("MouseButton")
.field("button", data.key as u32)
.field("pressed", true)
@@ -462,10 +438,6 @@ fn handle_input_message(
Some(gst::event::CustomUpstream::new(structure))
}
MouseKeyUp(data) => {
let mut buttons = pressed_buttons.lock().unwrap();
// Remove the button from the pressed state when released
buttons.remove(&data.key);
let structure = gst::Structure::builder("MouseButton")
.field("button", data.key as u32)
.field("pressed", false)

View File

@@ -0,0 +1,202 @@
// @generated
// This file is @generated by prost-build.
/// EntityState represents the state of an entity in the mesh (e.g., a room).
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EntityState {
/// Type of entity (e.g., "room")
#[prost(string, tag="1")]
pub entity_type: ::prost::alloc::string::String,
/// Unique identifier (e.g., room name)
#[prost(string, tag="2")]
pub entity_id: ::prost::alloc::string::String,
/// Whether the entity is active
#[prost(bool, tag="3")]
pub active: bool,
/// Relay ID that owns this entity
#[prost(string, tag="4")]
pub owner_relay_id: ::prost::alloc::string::String,
}
/// MeshMessage is the top-level message for all relay-to-relay communication.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct MeshMessage {
#[prost(oneof="mesh_message::Type", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13")]
pub r#type: ::core::option::Option<mesh_message::Type>,
}
/// Nested message and enum types in `MeshMessage`.
pub mod mesh_message {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Type {
/// Level 0
#[prost(message, tag="1")]
StateUpdate(super::StateUpdate),
#[prost(message, tag="2")]
Ack(super::Ack),
#[prost(message, tag="3")]
RetransmissionRequest(super::RetransmissionRequest),
#[prost(message, tag="4")]
Retransmission(super::Retransmission),
#[prost(message, tag="5")]
Heartbeat(super::Heartbeat),
#[prost(message, tag="6")]
SuspectRelay(super::SuspectRelay),
#[prost(message, tag="7")]
Disconnect(super::Disconnect),
/// Level 1
#[prost(message, tag="8")]
ForwardSdp(super::ForwardSdp),
#[prost(message, tag="9")]
ForwardIce(super::ForwardIce),
#[prost(message, tag="10")]
ForwardIngest(super::ForwardIngest),
#[prost(message, tag="11")]
StreamRequest(super::StreamRequest),
/// Level 2
#[prost(message, tag="12")]
Handshake(super::Handshake),
#[prost(message, tag="13")]
HandshakeResponse(super::HandshakeResponse),
}
}
/// Handshake to inititiate new connection to mesh.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Handshake {
/// UUID of the relay
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// base64 encoded Diffie-Hellman public key
#[prost(string, tag="2")]
pub dh_public_key: ::prost::alloc::string::String,
}
/// HandshakeResponse to respond to a mesh joiner.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct HandshakeResponse {
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub dh_public_key: ::prost::alloc::string::String,
/// relay id to signature
#[prost(map="string, string", tag="3")]
pub approvals: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>,
}
/// Forwarded SDP from another relay.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ForwardSdp {
#[prost(string, tag="1")]
pub room_name: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub participant_id: ::prost::alloc::string::String,
#[prost(string, tag="3")]
pub sdp: ::prost::alloc::string::String,
/// "offer" or "answer"
#[prost(string, tag="4")]
pub r#type: ::prost::alloc::string::String,
}
/// Forwarded ICE candidate from another relay.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ForwardIce {
#[prost(string, tag="1")]
pub room_name: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub participant_id: ::prost::alloc::string::String,
#[prost(string, tag="3")]
pub candidate: ::prost::alloc::string::String,
}
/// Forwarded ingest room from another relay.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ForwardIngest {
#[prost(string, tag="1")]
pub room_name: ::prost::alloc::string::String,
}
/// Stream request from mesh.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct StreamRequest {
#[prost(string, tag="1")]
pub room_name: ::prost::alloc::string::String,
}
/// StateUpdate propagates entity state changes across the mesh.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct StateUpdate {
/// Unique sequence number for this update
#[prost(uint64, tag="1")]
pub sequence_number: u64,
/// Key: entity_id (e.g., room name), Value: EntityState
#[prost(map="string, message", tag="2")]
pub entities: ::std::collections::HashMap<::prost::alloc::string::String, EntityState>,
}
/// Ack acknowledges receipt of a StateUpdate.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Ack {
/// UUID of the acknowledging relay
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// Sequence number being acknowledged
#[prost(uint64, tag="2")]
pub sequence_number: u64,
}
/// RetransmissionRequest requests a missed StateUpdate.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RetransmissionRequest {
/// UUID of the requesting relay
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// Sequence number of the missed update
#[prost(uint64, tag="2")]
pub sequence_number: u64,
}
/// Retransmission resends a StateUpdate.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Retransmission {
/// UUID of the sending relay
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// The retransmitted update
#[prost(message, optional, tag="2")]
pub state_update: ::core::option::Option<StateUpdate>,
}
/// Heartbeat signals relay liveness.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Heartbeat {
/// UUID of the sending relay
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// Time of the heartbeat
#[prost(message, optional, tag="2")]
pub timestamp: ::core::option::Option<::prost_types::Timestamp>,
}
/// SuspectRelay marks a relay as potentially unresponsive.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SuspectRelay {
/// UUID of the suspected relay
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// Reason for suspicion (e.g., "no heartbeat")
#[prost(string, tag="2")]
pub reason: ::prost::alloc::string::String,
}
/// Disconnect signals to remove a relay from the mesh.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Disconnect {
/// UUID of the relay to disconnect
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// Reason for disconnection (e.g., "unresponsive")
#[prost(string, tag="2")]
pub reason: ::prost::alloc::string::String,
}
// @@protoc_insertion_point(module)

View File

@@ -3,17 +3,17 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoTimestampEntry {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub stage: ::prost::alloc::string::String,
#[prost(message, optional, tag = "2")]
#[prost(message, optional, tag="2")]
pub time: ::core::option::Option<::prost_types::Timestamp>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoLatencyTracker {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub sequence_id: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "2")]
#[prost(message, repeated, tag="2")]
pub timestamps: ::prost::alloc::vec::Vec<ProtoTimestampEntry>,
}
/// MouseMove message
@@ -21,11 +21,11 @@ pub struct ProtoLatencyTracker {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMouseMove {
/// Fixed value "MouseMove"
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag = "2")]
#[prost(int32, tag="2")]
pub x: i32,
#[prost(int32, tag = "3")]
#[prost(int32, tag="3")]
pub y: i32,
}
/// MouseMoveAbs message
@@ -33,11 +33,11 @@ pub struct ProtoMouseMove {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMouseMoveAbs {
/// Fixed value "MouseMoveAbs"
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag = "2")]
#[prost(int32, tag="2")]
pub x: i32,
#[prost(int32, tag = "3")]
#[prost(int32, tag="3")]
pub y: i32,
}
/// MouseWheel message
@@ -45,11 +45,11 @@ pub struct ProtoMouseMoveAbs {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMouseWheel {
/// Fixed value "MouseWheel"
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag = "2")]
#[prost(int32, tag="2")]
pub x: i32,
#[prost(int32, tag = "3")]
#[prost(int32, tag="3")]
pub y: i32,
}
/// MouseKeyDown message
@@ -57,9 +57,9 @@ pub struct ProtoMouseWheel {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMouseKeyDown {
/// Fixed value "MouseKeyDown"
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag = "2")]
#[prost(int32, tag="2")]
pub key: i32,
}
/// MouseKeyUp message
@@ -67,9 +67,9 @@ pub struct ProtoMouseKeyDown {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMouseKeyUp {
/// Fixed value "MouseKeyUp"
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag = "2")]
#[prost(int32, tag="2")]
pub key: i32,
}
/// KeyDown message
@@ -77,9 +77,9 @@ pub struct ProtoMouseKeyUp {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoKeyDown {
/// Fixed value "KeyDown"
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag = "2")]
#[prost(int32, tag="2")]
pub key: i32,
}
/// KeyUp message
@@ -87,53 +87,53 @@ pub struct ProtoKeyDown {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoKeyUp {
/// Fixed value "KeyUp"
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag = "2")]
#[prost(int32, tag="2")]
pub key: i32,
}
/// Union of all Input types
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoInput {
#[prost(oneof = "proto_input::InputType", tags = "1, 2, 3, 4, 5, 6, 7")]
#[prost(oneof="proto_input::InputType", tags="1, 2, 3, 4, 5, 6, 7")]
pub input_type: ::core::option::Option<proto_input::InputType>,
}
/// Nested message and enum types in `ProtoInput`.
pub mod proto_input {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum InputType {
#[prost(message, tag = "1")]
#[prost(message, tag="1")]
MouseMove(super::ProtoMouseMove),
#[prost(message, tag = "2")]
#[prost(message, tag="2")]
MouseMoveAbs(super::ProtoMouseMoveAbs),
#[prost(message, tag = "3")]
#[prost(message, tag="3")]
MouseWheel(super::ProtoMouseWheel),
#[prost(message, tag = "4")]
#[prost(message, tag="4")]
MouseKeyDown(super::ProtoMouseKeyDown),
#[prost(message, tag = "5")]
#[prost(message, tag="5")]
MouseKeyUp(super::ProtoMouseKeyUp),
#[prost(message, tag = "6")]
#[prost(message, tag="6")]
KeyDown(super::ProtoKeyDown),
#[prost(message, tag = "7")]
#[prost(message, tag="7")]
KeyUp(super::ProtoKeyUp),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMessageBase {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub payload_type: ::prost::alloc::string::String,
#[prost(message, optional, tag = "2")]
#[prost(message, optional, tag="2")]
pub latency: ::core::option::Option<ProtoLatencyTracker>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMessageInput {
#[prost(message, optional, tag = "1")]
#[prost(message, optional, tag="1")]
pub message_base: ::core::option::Option<ProtoMessageBase>,
#[prost(message, optional, tag = "2")]
#[prost(message, optional, tag="2")]
pub data: ::core::option::Option<ProtoInput>,
}
// @@protoc_insertion_point(module)