Skip to main content

ferroui_base/rendering/composition/transport/
batch.rs

1use super::BatchStreamData;
2use std::sync::atomic::{AtomicI64, Ordering};
3use std::sync::{Arc, Condvar, Mutex};
4use std::time::Duration;
5
6static NEXT_SEQUENCE_ID: AtomicI64 = AtomicI64::new(1);
7
8type Continuation = Box<dyn FnOnce() + Send>;
9
10/// A one-shot completion signal that can be observed from any thread
11/// (the role of the `Task` exposed by a batch upstream).
12#[derive(Default)]
13pub struct BatchCompletion {
14    state: Mutex<CompletionState>,
15    completed: Condvar,
16}
17
18#[derive(Default)]
19struct CompletionState {
20    completed: bool,
21    continuations: Vec<Continuation>,
22}
23
24impl BatchCompletion {
25    /// Whether the signal has been raised.
26    pub fn is_completed(&self) -> bool {
27        self.state.lock().unwrap_or_else(|e| e.into_inner()).completed
28    }
29
30    /// Runs `continuation` when the signal is raised: synchronously on the
31    /// raising thread, or right now if it has been raised already
32    /// (`ContinueWith(.., ExecuteSynchronously)`).
33    pub fn on_completed(&self, continuation: impl FnOnce() + Send + 'static) {
34        {
35            let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
36            if !state.completed {
37                state.continuations.push(Box::new(continuation));
38                return;
39            }
40        }
41        continuation();
42    }
43
44    /// Blocks until the completion is set (`Task.Wait`): how the UI thread
45    /// waits for the render thread at the synchronous points.
46    pub fn wait(&self) {
47        let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
48        while !state.completed {
49            state = self.completed.wait(state).unwrap_or_else(|e| e.into_inner());
50        }
51    }
52
53    pub(crate) fn try_set(&self) {
54        let continuations = {
55            let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
56            if state.completed {
57                return;
58            }
59            state.completed = true;
60            std::mem::take(&mut state.continuations)
61        };
62        self.completed.notify_all();
63        for continuation in continuations {
64            continuation();
65        }
66    }
67}
68
69/// Represents a group of composition changes that are applied by the server
70/// compositor atomically. This is the handle the UI thread keeps; it can be
71/// shared with any thread.
72pub struct CompositionBatch {
73    sequence_id: i64,
74    processed: BatchCompletion,
75    rendered: BatchCompletion,
76}
77
78impl CompositionBatch {
79    pub(crate) fn new() -> Arc<CompositionBatch> {
80        Arc::new(CompositionBatch {
81            sequence_id: NEXT_SEQUENCE_ID.fetch_add(1, Ordering::SeqCst) + 1,
82            processed: BatchCompletion::default(),
83            rendered: BatchCompletion::default(),
84        })
85    }
86
87    /// The process-wide sequence number of the batch.
88    pub fn sequence_id(&self) -> i64 {
89        self.sequence_id
90    }
91
92    /// Indicates that the batch got deserialized on the render thread and
93    /// will soon be rendered. It's generally a good time to start producing
94    /// the next one.
95    ///
96    /// To allow timing-sensitive code to receive the notification in time,
97    /// continuations run synchronously on the render thread, so they should
98    /// be as short as possible.
99    pub fn processed(&self) -> &BatchCompletion {
100        &self.processed
101    }
102
103    /// Indicates that the batch got rendered on the render thread.
104    ///
105    /// To allow timing-sensitive code to receive the notification in time,
106    /// continuations run synchronously on the render thread, so they should
107    /// be as short as possible.
108    pub fn rendered(&self) -> &BatchCompletion {
109        &self.rendered
110    }
111
112    pub(crate) fn notify_processed(&self) {
113        self.processed.try_set();
114    }
115
116    pub(crate) fn notify_rendered(&self) {
117        self.rendered.try_set();
118    }
119}
120
121/// A committed batch on its way to the server compositor: the serialized
122/// changes together with the handle the UI thread observes.
123///
124/// This is the one value that crosses from the UI-thread compositor to the
125/// server compositor.
126pub struct CommittedBatch {
127    pub(crate) batch: Arc<CompositionBatch>,
128    pub(crate) changes: BatchStreamData,
129    pub(crate) committed_at: Duration,
130}
131
132/// A batch crosses from the UI thread to the render thread: everything in
133/// it is `Send`, and stays so.
134const _: fn() = || {
135    fn sent<T: Send>() {}
136    sent::<CommittedBatch>();
137};