fj_host/
host.rs

1use std::thread::JoinHandle;
2
3use crossbeam_channel::Sender;
4use fj_operations::shape_processor::ShapeProcessor;
5
6use crate::{EventLoopClosed, HostThread, Model, ModelEvent};
7
8/// A host for watching models and responding to model updates
9pub struct Host {
10    command_tx: Sender<HostCommand>,
11    host_thread: Option<JoinHandle<Result<(), EventLoopClosed>>>,
12    model_loaded: bool,
13}
14
15impl Host {
16    /// Create a host with a shape processor and a send channel to the event
17    /// loop.
18    pub fn new(
19        shape_processor: ShapeProcessor,
20        model_event_tx: Sender<ModelEvent>,
21    ) -> Self {
22        let (command_tx, host_thread) =
23            HostThread::spawn(shape_processor, model_event_tx);
24
25        Self {
26            command_tx,
27            host_thread: Some(host_thread),
28            model_loaded: false,
29        }
30    }
31
32    /// Send a model to the host for evaluation and processing.
33    pub fn load_model(&mut self, model: Model) {
34        self.command_tx
35            .try_send(HostCommand::LoadModel(model))
36            .expect("Host channel disconnected unexpectedly");
37        self.model_loaded = true;
38    }
39
40    /// Whether a model has been sent to the host yet
41    pub fn is_model_loaded(&self) -> bool {
42        self.model_loaded
43    }
44
45    /// Check if the host thread has exited with a panic. This method runs at
46    /// each tick of the event loop. Without an explicit check, an operation
47    /// will appear to hang forever (e.g. processing a model).  An error
48    /// will be printed to the terminal, but the gui will not notice until
49    /// a new `HostCommand` is issued on the disconnected channel.
50    ///
51    /// # Panics
52    ///
53    /// This method panics on purpose so the main thread can exit on an
54    /// unrecoverable error.
55    pub fn propagate_panic(&mut self) {
56        if self.host_thread.is_none() {
57            unreachable!("Constructor requires host thread")
58        }
59        if let Some(host_thread) = &self.host_thread {
60            // The host thread should not finish while this handle holds the
61            // `command_tx` channel open, so an exit means the thread panicked.
62            if host_thread.is_finished() {
63                let host_thread = self.host_thread.take().unwrap();
64                match host_thread.join() {
65                    Ok(_) => {
66                        unreachable!(
67                            "Host thread cannot exit until host handle disconnects"
68                        )
69                    }
70                    // The error value has already been reported by the panic
71                    // in the host thread, so just ignore it here.
72                    Err(_) => {
73                        panic!("Host thread panicked")
74                    }
75                }
76            }
77        }
78    }
79}
80
81/// Commands that can be sent to a host
82pub enum HostCommand {
83    /// Load a model to be evaluated and processed
84    LoadModel(Model),
85    /// Used by a `Watcher` to trigger evaluation when a model is edited
86    TriggerEvaluation,
87}