use std::io;
use std::sync::Arc;
use std::time::Duration;
use crate::core::clock::GlobalTime;
use crate::core::record::Channel;
use crate::core::sched::{Pace, RateControl};
use crate::host::audio::AudioStream;
use crate::host::clock::MonotonicClock;
use crate::host::display::{PixelFormat, Scanout, Surface};
use crate::host::input::{self, Feed, InputSink};
use crate::machine::Machine;
use super::VncServer;
pub const SLICE: GlobalTime = GlobalTime::from_nanos(16_666_667);
const MAX_WAIT: Duration = Duration::from_millis(50);
const MAX_CATCHUP_NANOS: u64 = 250_000_000;
pub struct VncSession {
server: VncServer,
scanout: Box<dyn Scanout>,
surface: Surface,
feed: Arc<Feed>,
channel: Channel,
audio: Option<AudioStream>,
captured: u64,
}
impl core::fmt::Debug for VncSession {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("VncSession")
.field("server", &self.server)
.field("scanout", &self.scanout)
.field("feed", &self.feed)
.field("channel", &self.channel.to_string())
.field("audio", &self.audio.is_some())
.finish_non_exhaustive()
}
}
impl VncSession {
#[must_use]
pub fn new(server: VncServer, scanout: Box<dyn Scanout>) -> VncSession {
let info = scanout.info();
let mut server = server;
server.set_geometry(info.width, info.height);
VncSession {
surface: Surface::new(PixelFormat::BGRA8888, info.width, info.height),
server,
scanout,
feed: Arc::new(Feed::new()),
channel: input::channel(input::DEFAULT_STREAM),
audio: None,
captured: u64::MAX,
}
}
#[must_use]
pub fn on_stream(mut self, name: &str) -> VncSession {
self.channel = input::channel(name);
self
}
#[must_use]
pub fn channel(&self) -> &Channel {
&self.channel
}
#[must_use]
pub fn feed(&self) -> &Arc<Feed> {
&self.feed
}
pub fn attach(&self, recorder: &crate::core::record::Recorder) -> crate::Result<()> {
recorder.register(self.channel.clone(), input::sink(&self.feed))
}
#[must_use]
pub fn with_audio(mut self, stream: AudioStream) -> VncSession {
self.audio = Some(stream);
self
}
#[must_use]
pub fn audio(&self) -> Option<&AudioStream> {
self.audio.as_ref()
}
pub fn audio_mut(&mut self) -> Option<&mut AudioStream> {
self.audio.as_mut()
}
#[must_use]
pub fn with_sink(self, sink: Arc<dyn InputSink>) -> VncSession {
self.feed.attach(sink);
self
}
#[must_use]
pub fn server(&self) -> &VncServer {
&self.server
}
pub fn install(&self, machine: &mut Machine) {
let clock = MonotonicClock::new();
let now_host = {
use crate::core::sched::HostClock;
clock.monotonic_nanos()
};
let now = machine.now();
machine.set_host_clock(Box::new(clock));
machine.scheduler_mut().rate_controller_mut().set_control(
RateControl::Realtime {
max_catchup_nanos: MAX_CATCHUP_NANOS,
},
now_host,
now,
);
}
pub fn poll(&mut self, machine: &mut Machine) -> io::Result<usize> {
self.capture();
if let Some(audio) = self.audio.as_mut() {
audio.pull();
}
let live = self.server.poll(&self.surface)?;
let mut crossed = 0;
match machine.recorder() {
Some(recorder) => {
for event in live {
recorder
.post(&self.channel, &event.encode())
.map_err(|e| io::Error::other(e.to_string()))?;
crossed += 1;
}
}
None => {
for event in live {
self.feed.deliver(event);
crossed += 1;
}
}
}
Ok(crossed)
}
#[must_use]
pub fn deadline(&self, machine: &Machine) -> GlobalTime {
machine.now().saturating_add(SLICE)
}
pub fn run(
&mut self,
machine: &mut Machine,
mut keep_going: impl FnMut(&mut Machine) -> bool,
) -> io::Result<()> {
self.install(machine);
loop {
self.poll(machine)?;
let deadline = self.deadline(machine);
machine
.run_until(deadline)
.map_err(|e| io::Error::other(e.to_string()))?;
self.wait(machine)?;
if !keep_going(machine) {
return Ok(());
}
}
}
fn wait(&self, machine: &mut Machine) -> io::Result<()> {
let pace = machine
.scheduler_mut()
.pace()
.map_err(|e| io::Error::other(e.to_string()))?;
if let Pace::Wait { nanos } = pace {
std::thread::sleep(Duration::from_nanos(nanos).min(MAX_WAIT));
}
Ok(())
}
fn capture(&mut self) {
let info = self.scanout.info();
if info.width != self.surface.width() || info.height != self.surface.height() {
self.surface
.reshape(PixelFormat::BGRA8888, info.width, info.height);
self.server.set_geometry(info.width, info.height);
self.captured = u64::MAX;
}
let counter = self.scanout.frame_counter();
if counter == self.captured {
return;
}
self.captured = self.scanout.capture(&mut self.surface);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::host::display::SurfaceInfo;
use crate::host::input::{InputEvent, Keysym};
use std::sync::Mutex;
#[derive(Debug)]
struct Blank;
impl Scanout for Blank {
fn info(&self) -> SurfaceInfo {
SurfaceInfo::new(4, 2, PixelFormat::BGRA8888)
}
fn frame_counter(&self) -> u64 {
0
}
fn capture(&self, dst: &mut Surface) -> u64 {
dst.fill([0, 0, 0]);
0
}
}
#[derive(Debug, Default)]
struct Seen(Mutex<Vec<InputEvent>>);
impl InputSink for Seen {
fn deliver(&self, event: InputEvent) {
self.0.lock().expect("not poisoned").push(event);
}
}
fn a_machine() -> Machine {
let registry = crate::machine::catalog::registry().expect("this build's registry");
let options = crate::machine::BuildOptions::new()
.with_classes(crate::machine::catalog::classes())
.with_bindings(crate::machine::catalog::bindings().expect("this build's bindings"));
crate::machine::build(
"vnc-session.machine",
r#"
machine "vnc-session" {
osc x = 1000000 Hz
space mem { width = 16, unassigned = open-bus }
object dram "ram" { size = 256 }
map mem 0x0000 size 0x100 = dram
}
"#,
®istry,
&options,
)
.expect("a machine with nothing but memory")
}
#[test]
fn a_replay_delivers_at_the_instant_it_was_recorded_at() {
use crate::core::record::{InputEvent as Record, InputLog, Recorder};
let at = GlobalTime::from_nanos(5_000_000);
let mut log = InputLog::new();
let channel = crate::host::input::channel(crate::host::input::DEFAULT_STREAM);
log.push(Record {
at,
channel: channel.clone(),
payload: InputEvent::Key {
keysym: Keysym::from_ascii(b'a'),
down: true,
}
.encode()
.to_vec(),
})
.expect("an empty log takes anything");
let seen = Arc::new(Seen::default());
let server = VncServer::bind(":0").expect("an ephemeral port");
let session = VncSession::new(server, Box::new(Blank))
.with_sink(Arc::clone(&seen) as Arc<dyn InputSink>);
let replay = Arc::new(Recorder::replaying(log));
session.attach(&replay).expect("a fresh recorder");
let mut machine = a_machine();
machine
.set_recorder(Arc::clone(&replay))
.expect("a deterministic machine");
let mut session = session;
session.poll(&mut machine).expect("poll");
machine.run_until(at).expect("run");
assert!(seen.0.lock().expect("not poisoned").is_empty());
session.poll(&mut machine).expect("poll");
let deadline = session.deadline(&machine);
assert_eq!(deadline, machine.now().saturating_add(SLICE));
machine.run_until(deadline).expect("run");
assert_eq!(seen.0.lock().expect("not poisoned").len(), 1);
assert_eq!(replay.cursor(), 1);
}
#[test]
fn a_session_with_no_recorder_delivers_straight_to_its_sinks() {
let seen = Arc::new(Seen::default());
let server = VncServer::bind(":0").expect("an ephemeral port");
let mut session = VncSession::new(server, Box::new(Blank))
.with_sink(Arc::clone(&seen) as Arc<dyn InputSink>);
let mut machine = a_machine();
assert_eq!(session.poll(&mut machine).expect("poll"), 0);
assert_eq!(session.channel().to_string(), "input:vnc");
assert_eq!(session.feed().len(), 1);
crate::core::record::InputSink::deliver(
&**session.feed(),
&InputEvent::Key {
keysym: Keysym::RETURN,
down: true,
}
.encode(),
);
assert_eq!(seen.0.lock().expect("not poisoned").len(), 1);
}
#[test]
fn a_stream_can_be_named() {
let server = VncServer::bind(":0").expect("an ephemeral port");
let session = VncSession::new(server, Box::new(Blank)).on_stream("second");
assert_eq!(session.channel().to_string(), "input:second");
}
#[derive(Debug, Default)]
struct Ticker(std::sync::atomic::AtomicU64);
impl crate::host::audio::AudioSource for Ticker {
fn info(&self) -> crate::host::audio::StreamInfo {
crate::host::audio::StreamInfo::new(48_000, 1, 1, crate::host::audio::SampleFormat::S16)
}
fn drain(&self, out: &mut Vec<i16>) -> u64 {
self.0.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
out.push(0);
1
}
}
#[test]
fn a_session_drains_the_sound_every_slice() {
let server = VncServer::bind(":0").expect("an ephemeral port");
let stream = AudioStream::new(
Box::new(Ticker::default()),
48_000,
crate::host::audio::SampleFormat::S16,
);
let mut session = VncSession::new(server, Box::new(Blank)).with_audio(stream);
let mut machine = a_machine();
assert_eq!(session.audio().map(AudioStream::dropped), Some(0));
for _ in 0..4 {
session.poll(&mut machine).expect("poll");
}
assert_eq!(
session.audio().expect("a stream").buffer().frames(),
4,
"one frame per slice reached the queue"
);
assert!(session.audio_mut().is_some());
}
#[test]
fn installing_the_clock_makes_pacing_answerable() {
let server = VncServer::bind(":0").expect("an ephemeral port");
let session = VncSession::new(server, Box::new(Blank));
let mut machine = a_machine();
session.install(&mut machine);
assert!(machine.scheduler_mut().pace().is_ok());
}
}