mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-11 00:05:36 +02:00
## Description Whew.. - Steam can now run without namespaces using live-patcher (because Docker..) - Improved NVIDIA GPU selection and handling - Pipeline tests for GPU picking logic - Optimizations and cleanup all around - SSH (by default disabled) for easier instance debugging. - CachyOS' Proton because that works without namespaces (couldn't figure out how to enable automatically in Steam yet..) - Package updates and partial removal of futures (libp2p is going to switch to Tokio in next release hopefully) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - SSH server can now be enabled within the container for remote access when configured. - Added persistent live patching for Steam runtime entrypoints to improve compatibility with namespace-less applications. - Enhanced GPU selection with multi-GPU support and PCI bus ID matching for improved hardware compatibility. - Improved encoder selection by runtime testing of video encoders for better reliability. - Added WebSocket transport support in peer-to-peer networking. - Added flexible compositor and application launching with configurable commands and improved socket handling. - **Bug Fixes** - Addressed NVIDIA-specific GStreamer issues by setting new environment variables. - Improved error handling and logging for GPU and encoder selection. - Fixed process monitoring to handle patcher restarts and added cleanup logic. - Added GStreamer cache clearing workaround for Wayland socket failures. - **Improvements** - Real-time logging of container processes to standard output and error for easier monitoring. - Enhanced process management and reduced CPU usage in protocol handling loops. - Updated dependency versions for greater stability and feature support. - Improved audio capture defaults and expanded audio pipeline support. - Enhanced video pipeline setup with conditional handling for different encoder APIs and DMA-BUF support. - Refined concurrency and lifecycle management in protocol messaging for increased robustness. - Consistent namespace usage and updated crate references across the codebase. - Enhanced SSH configuration with key management, port customization, and startup verification. - Improved GPU and video encoder integration in pipeline construction. - Simplified error handling and consolidated write operations in protocol streams. - Removed Ludusavi installation from container image and updated package installations. - **Other** - Minor formatting and style changes for better code readability and maintainability. - Docker build context now ignores `.idea` directory to streamline builds. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
193 lines
6.5 KiB
Rust
193 lines
6.5 KiB
Rust
use crate::p2p::p2p::NestriConnection;
|
|
use crate::p2p::p2p_safestream::SafeStream;
|
|
use dashmap::DashMap;
|
|
use libp2p::StreamProtocol;
|
|
use std::sync::Arc;
|
|
use tokio::sync::mpsc;
|
|
use tokio::time::{self, Duration};
|
|
|
|
// Cloneable callback type
|
|
pub type CallbackInner = dyn Fn(Vec<u8>) + Send + Sync + 'static;
|
|
pub struct Callback(Arc<CallbackInner>);
|
|
impl Callback {
|
|
pub fn new<F>(f: F) -> Self
|
|
where
|
|
F: Fn(Vec<u8>) + Send + Sync + 'static,
|
|
{
|
|
Callback(Arc::new(f))
|
|
}
|
|
|
|
pub fn call(&self, data: Vec<u8>) {
|
|
self.0(data)
|
|
}
|
|
}
|
|
impl Clone for Callback {
|
|
fn clone(&self) -> Self {
|
|
Callback(Arc::clone(&self.0))
|
|
}
|
|
}
|
|
impl From<Box<CallbackInner>> for Callback {
|
|
fn from(boxed: Box<CallbackInner>) -> Self {
|
|
Callback(Arc::from(boxed))
|
|
}
|
|
}
|
|
|
|
/// NestriStreamProtocol manages the stream protocol for Nestri connections.
|
|
pub struct NestriStreamProtocol {
|
|
tx: Option<mpsc::Sender<Vec<u8>>>,
|
|
safe_stream: Arc<SafeStream>,
|
|
callbacks: Arc<DashMap<String, Callback>>,
|
|
read_handle: Option<tokio::task::JoinHandle<()>>,
|
|
write_handle: Option<tokio::task::JoinHandle<()>>,
|
|
}
|
|
impl NestriStreamProtocol {
|
|
const NESTRI_PROTOCOL_STREAM_PUSH: StreamProtocol =
|
|
StreamProtocol::new("/nestri-relay/stream-push/1.0.0");
|
|
|
|
pub async fn new(
|
|
nestri_connection: NestriConnection,
|
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
|
let mut nestri_connection = nestri_connection.clone();
|
|
let push_stream = match nestri_connection
|
|
.control
|
|
.open_stream(nestri_connection.peer_id, Self::NESTRI_PROTOCOL_STREAM_PUSH)
|
|
.await
|
|
{
|
|
Ok(stream) => stream,
|
|
Err(e) => {
|
|
return Err(Box::new(e));
|
|
}
|
|
};
|
|
|
|
let mut sp = NestriStreamProtocol {
|
|
tx: None,
|
|
safe_stream: Arc::new(SafeStream::new(push_stream)),
|
|
callbacks: Arc::new(DashMap::new()),
|
|
read_handle: None,
|
|
write_handle: None,
|
|
};
|
|
|
|
// Use restart method to initialize the read and write loops
|
|
sp.restart()?;
|
|
|
|
Ok(sp)
|
|
}
|
|
|
|
pub fn restart(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
|
// Return if tx and handles are already initialized
|
|
if self.tx.is_some() && self.read_handle.is_some() && self.write_handle.is_some() {
|
|
tracing::warn!("NestriStreamProtocol is already running, restart skipped");
|
|
return Ok(());
|
|
}
|
|
|
|
let (tx, rx) = mpsc::channel(1000);
|
|
self.tx = Some(tx);
|
|
self.read_handle = Some(self.spawn_read_loop());
|
|
self.write_handle = Some(self.spawn_write_loop(rx));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn spawn_read_loop(&self) -> tokio::task::JoinHandle<()> {
|
|
let safe_stream = self.safe_stream.clone();
|
|
let callbacks = self.callbacks.clone();
|
|
tokio::spawn(async move {
|
|
loop {
|
|
let data = {
|
|
match safe_stream.receive_raw().await {
|
|
Ok(data) => data,
|
|
Err(e) => {
|
|
tracing::error!("Error receiving data: {}", e);
|
|
break; // Exit the loop on error
|
|
}
|
|
}
|
|
};
|
|
|
|
match serde_json::from_slice::<crate::messages::MessageBase>(&data) {
|
|
Ok(base_message) => {
|
|
let response_type = base_message.payload_type;
|
|
|
|
// With DashMap, we don't need explicit locking
|
|
// we just get the callback directly if it exists
|
|
if let Some(callback) = callbacks.get(&response_type) {
|
|
// Execute the callback
|
|
if let Err(e) =
|
|
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
|
callback.call(data.clone())
|
|
}))
|
|
{
|
|
tracing::error!(
|
|
"Callback for response type '{}' panicked: {:?}",
|
|
response_type,
|
|
e
|
|
);
|
|
}
|
|
} else {
|
|
tracing::warn!(
|
|
"No callback registered for response type: {}",
|
|
response_type
|
|
);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
tracing::error!("Failed to decode message: {}", e);
|
|
}
|
|
}
|
|
|
|
// Add a small sleep to reduce CPU usage
|
|
time::sleep(Duration::from_micros(100)).await;
|
|
}
|
|
})
|
|
}
|
|
|
|
fn spawn_write_loop(&self, mut rx: mpsc::Receiver<Vec<u8>>) -> tokio::task::JoinHandle<()> {
|
|
let safe_stream = self.safe_stream.clone();
|
|
tokio::spawn(async move {
|
|
loop {
|
|
// Wait for a message from the channel
|
|
match rx.recv().await {
|
|
Some(tx_data) => {
|
|
if let Err(e) = safe_stream.send_raw(&tx_data).await {
|
|
tracing::error!("Error sending data: {:?}", e);
|
|
}
|
|
}
|
|
None => {
|
|
tracing::info!("Receiver closed, exiting write loop");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Add a small sleep to reduce CPU usage
|
|
time::sleep(Duration::from_micros(100)).await;
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn send_message<M: serde::Serialize>(
|
|
&self,
|
|
message: &M,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
let json_data = serde_json::to_vec(message)?;
|
|
let Some(tx) = &self.tx else {
|
|
return Err(Box::new(std::io::Error::new(
|
|
std::io::ErrorKind::NotConnected,
|
|
if self.read_handle.is_none() && self.write_handle.is_none() {
|
|
"NestriStreamProtocol has been shutdown"
|
|
} else {
|
|
"NestriStreamProtocol is not properly initialized"
|
|
},
|
|
)));
|
|
};
|
|
tx.try_send(json_data)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn register_callback<F>(&self, response_type: &str, callback: F)
|
|
where
|
|
F: Fn(Vec<u8>) + Send + Sync + 'static,
|
|
{
|
|
self.callbacks
|
|
.insert(response_type.to_string(), Callback::new(callback));
|
|
}
|
|
}
|