Skip to main content

gled_ui/
link.rs

1//! The interface's connection to the engine, whatever carries it: the
2//! desktop app talks to its engine in the same process, the browser and a
3//! remote desktop over a WebSocket. Actions and other input enqueued on the
4//! interface's thread go through it, numbered, so the interface knows when
5//! the engine applied its edits.
6
7use std::{
8    cell::{Cell, RefCell},
9    rc::Rc,
10};
11
12use gled_protocol::{
13    action::route_actions_on_this_thread,
14    message::{ClientMessage, ServerMessage, Subscriptions},
15    state::{ClientInput, route_inputs_on_this_thread},
16};
17use web_time::{Duration, Instant};
18
19/// How long without any message from a remote engine counts as an
20/// interrupted connection. The engine sends the beat timing about 30 times
21/// a second.
22const SILENCE: Duration = Duration::from_secs(3);
23
24/// How messages reach the engine and come back.
25pub trait EngineLink {
26    fn send(&mut self, message: ClientMessage);
27    /// The messages that arrived since the last call.
28    fn receive(&mut self) -> Vec<ServerMessage>;
29
30    /// Whether the messages travel over a network, where the connection can
31    /// be interrupted.
32    fn is_remote(&self) -> bool {
33        false
34    }
35
36    /// Whether the network connection is open.
37    fn is_open(&self) -> bool {
38        true
39    }
40
41    /// Where the engine runs, if the interface should say so while it
42    /// connects.
43    fn target(&self) -> Option<String> {
44        None
45    }
46}
47
48/// A connection to a remote engine that is interrupted.
49#[derive(Debug, Clone, Copy)]
50pub(crate) struct Interruption {
51    /// How long ago the last message arrived.
52    pub(crate) last_contact: Duration,
53}
54
55/// The interface's end of its link. Cheap to clone; all clones share the
56/// link and the numbering.
57#[derive(Clone)]
58pub struct Connection {
59    link: Rc<RefCell<Box<dyn EngineLink>>>,
60    /// Number of the last action sent.
61    sequence: Rc<Cell<u64>>,
62    /// When the last message from the engine arrived.
63    last_contact: Rc<Cell<Option<Instant>>>,
64}
65
66impl std::fmt::Debug for Connection {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        f.debug_struct("Connection")
69            .field("sequence", &self.sequence.get())
70            .finish_non_exhaustive()
71    }
72}
73
74impl Connection {
75    /// Wraps `link` and makes actions and client inputs enqueued on the
76    /// calling thread go through it. Call on the interface's thread.
77    pub fn new(link: Box<dyn EngineLink>, subscriptions: Subscriptions) -> Self {
78        let connection = Self {
79            link: Rc::new(RefCell::new(link)),
80            sequence: Rc::default(),
81            last_contact: Rc::default(),
82        };
83        connection.send(ClientMessage::Subscribe(subscriptions));
84        let actions = connection.clone();
85        route_actions_on_this_thread(move |action| {
86            let sequence = actions.sequence.get() + 1;
87            actions.sequence.set(sequence);
88            actions.send(ClientMessage::Input(ClientInput::Action {
89                sequence,
90                action,
91            }));
92        });
93        let inputs = connection.clone();
94        route_inputs_on_this_thread(move |input| inputs.send(ClientMessage::Input(input)));
95        connection
96    }
97
98    pub(crate) fn send(&self, message: ClientMessage) {
99        match self.link.try_borrow_mut() {
100            Ok(mut link) => link.send(message),
101            Err(_) => tracing::warn!("Dropped {message:?}: the link is busy"),
102        }
103    }
104
105    pub(crate) fn receive(&self) -> Vec<ServerMessage> {
106        let mut link = self.link.borrow_mut();
107        let messages = link.receive();
108        // A link that is not open only reports its own errors.
109        if !messages.is_empty() && link.is_open() {
110            self.last_contact.set(Some(Instant::now()));
111        }
112        messages
113    }
114
115    /// Whether the engine runs on another machine.
116    pub(crate) fn is_remote(&self) -> bool {
117        self.link.borrow().is_remote()
118    }
119
120    pub(crate) fn target(&self) -> Option<String> {
121        self.link.borrow().target()
122    }
123
124    /// Whether the connection to a remote engine is interrupted: closed, or
125    /// silent for a while. `None` while all is well and before the first
126    /// contact, which the interface waits for anyway.
127    pub(crate) fn interruption(&self) -> Option<Interruption> {
128        let link = self.link.borrow();
129        let last_contact = self.last_contact.get()?.elapsed();
130        (link.is_remote() && (!link.is_open() || last_contact > SILENCE))
131            .then_some(Interruption { last_contact })
132    }
133
134    /// The number of the last action sent.
135    pub(crate) fn sent_sequence(&self) -> u64 {
136        self.sequence.get()
137    }
138
139    /// Starts the numbering over, for a new session with the engine after
140    /// the link reconnected.
141    pub(crate) fn reset_sequence(&self) {
142        self.sequence.set(0);
143    }
144}