use super::{
super::types::{
ConnectionCommand, CurrentRequest, RemoteConnectionHandle, RemoteError, RemoteStream,
RemoteUpdate, RenderCommand,
},
handshake::connect_and_handshake,
stream_io::try_read_stream_event,
};
use crate::settings::WorkerSettings;
use std::{
sync::mpsc::{self, TryRecvError},
thread,
time::{Duration, Instant},
};
#[must_use]
pub fn spawn_remote_connection(worker: WorkerSettings) -> RemoteConnectionHandle {
let (tx, rx) = mpsc::channel();
let worker_for_thread = worker.clone();
thread::spawn(move || run_connection(&worker_for_thread, &rx, super::LIVENESS_TIMEOUT));
RemoteConnectionHandle {
worker,
commands: tx,
}
}
fn run_connection(
worker: &WorkerSettings,
commands: &mpsc::Receiver<ConnectionCommand>,
liveness_timeout: Duration,
) {
let mut stream: Option<RemoteStream> = None;
let mut welcome: Option<indicatrix_net::messages::Welcome> = None;
let mut current: Option<CurrentRequest> = None;
let mut last_event = Instant::now();
let mut seen_first_event = false;
loop {
match commands.try_recv() {
Ok(ConnectionCommand::Render(cmd)) => {
dispatch_render(worker, &mut stream, &mut welcome, &mut current, *cmd);
last_event = Instant::now();
seen_first_event = false;
}
Ok(ConnectionCommand::Cancel { request_id }) => {
if current.as_ref().is_some_and(|c| c.request_id == request_id)
&& let Some(s) = stream.as_mut()
&& indicatrix_net::client::send_cancel(s, request_id).is_err()
{
stream = None;
welcome = None;
}
}
Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => return,
}
let Some(s) = stream.as_mut() else {
thread::sleep(super::POLL_INTERVAL);
continue;
};
match try_read_stream_event(s, super::POLL_INTERVAL) {
Ok(Some((event, payload))) => {
last_event = Instant::now();
seen_first_event = true;
if let indicatrix_net::messages::StreamEvent::NeedAsset { content_hash } = &event {
if !answer_need_asset(s, content_hash, &mut current) {
stream = None;
welcome = None;
}
last_event = Instant::now();
continue;
}
if let indicatrix_net::messages::StreamEvent::CapabilityChanged { render } = &event
&& let Some(w) = welcome.as_mut()
{
w.render.clone_from(render);
}
super::route_event(&mut current, &event, payload.as_deref());
}
Ok(None) => {
let deadline = super::liveness_deadline(seen_first_event, liveness_timeout);
if super::check_liveness(&mut current, last_event, deadline) {
stream = None;
welcome = None;
}
}
Err(e) => {
if let Some(mut cur) = current.take() {
(cur.on_update)(RemoteUpdate::Failed {
request_id: cur.request_id,
message: RemoteError::from(e).to_string(),
});
}
stream = None;
welcome = None;
}
}
}
}
fn answer_need_asset(
stream: &mut RemoteStream,
hash: &indicatrix_net::messages::ContentHash,
current: &mut Option<CurrentRequest>,
) -> bool {
let Err(reason) = super::asset_upload::send_requested_asset(stream, hash) else {
return true;
};
if let Some(mut cur) = current.take() {
(cur.on_update)(RemoteUpdate::Failed {
request_id: cur.request_id,
message: RemoteError::Asset(reason).to_string(),
});
}
false
}
pub(super) fn live_stream_config(
worker: &WorkerSettings,
min_cadence_ms: u32,
width: u32,
height: u32,
display_only: bool,
) -> indicatrix_net::messages::StreamConfig {
let mut config = worker.stream_config(min_cadence_ms, width, height);
if display_only {
config.transfer_mode = indicatrix_net::messages::TransferMode::DisplayOnly;
config.preview = None;
}
config
}
fn dispatch_render(
worker: &WorkerSettings,
stream: &mut Option<RemoteStream>,
welcome: &mut Option<indicatrix_net::messages::Welcome>,
current: &mut Option<CurrentRequest>,
cmd: RenderCommand,
) {
let RenderCommand {
request,
accumulator,
mut on_update,
} = cmd;
let request_id = request.request_id;
if let Some(previous) = current.take()
&& let Some(s) = stream.as_mut()
{
let _ = indicatrix_net::client::send_cancel(s, previous.request_id);
}
if stream.is_none() {
match connect_and_handshake(worker) {
Ok((s, w)) => {
*stream = Some(s);
*welcome = Some(w);
}
Err(e) => {
on_update(RemoteUpdate::Failed {
request_id,
message: e.to_string(),
});
return; }
}
}
let w = welcome
.as_ref()
.expect("stream is Some at this point, and the two are always set together");
on_update(RemoteUpdate::Connected {
request_id,
info: w.clone().into(),
});
let Some(capability) = w.render.as_ref() else {
on_update(RemoteUpdate::Failed {
request_id,
message: RemoteError::NoRenderCapacity.to_string(),
});
return;
};
if request.scene.hdr().is_some() && !capability.hdr {
on_update(RemoteUpdate::Failed {
request_id,
message: RemoteError::HdrUnsupported.to_string(),
});
return;
}
let render_request = indicatrix_net::messages::RenderRequest {
request_id,
scene: request.scene,
first_sample: request.first_sample,
samples: request.samples,
stream: live_stream_config(
worker,
capability.min_cadence_ms,
request.width,
request.height,
request.display_only,
),
intent: request.intent,
};
{
let mut acc = accumulator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
acc.begin_request_for_range(request_id, request.first_sample, request.samples);
}
let s = stream
.as_mut()
.expect("just connected above if this was None");
match indicatrix_net::client::send_render_request(s, &render_request) {
Ok(()) => {
*current = Some(CurrentRequest {
request_id,
accumulator,
on_update,
});
}
Err(e) => {
*stream = None;
*welcome = None;
on_update(RemoteUpdate::Failed {
request_id,
message: RemoteError::from(e).to_string(),
});
}
}
}