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