Skip to main content

ferroui_base/rendering/composition/server/server_composition_visual/
readback.rs

1//! Here we are using a simplified version Multi-Version Concurrency Control
2//! with only one reader and only one writer.
3//!
4//! The goal is to provide un-teared view of a particular revision for the
5//! UI thread.
6//!
7//! We are taking a shared lock before switching reader's revision and are
8//! using the same lock to produce a new revision, so we know for sure that
9//! reader can't switch to a newer revision while we are writing.
10//!
11//! Reader's behavior:
12//! 1) reader will only pick slots with revision <= its current revision
13//! 2) reader will pick the newest revision among slots from (1)
14//!
15//! There are two scenarios that can be encountered by the writer:
16//! 1) both slots contain data for revisions older than the reader's current
17//!    revision, in that case we pick the slot with the oldest revision and
18//!    update it.
19//!    1.1) if reader comes before update it will pick the newer one
20//!    1.2) if reader comes after update, the overwritten slot would have a
21//!         revision that's higher than the reader's one, so it will still
22//!         pick the same slot
23//! 2) one of the slots contains data for a revision newer than the reader's
24//!    current revision. In that case we simply pick the slot with revision
25//!    the reader isn't allowed to touch anyway. Both before and after
26//!    update the reader will see only one (same) slot it's allowed to touch
27//!
28//! The slots are shared between the UI-thread visual and its server visual
29//! through an `Arc`; each slot is guarded by its own mutex, which is never
30//! contended when the protocol above is followed.
31
32use super::ServerCompositionVisual;
33use crate::platform::LtrbRect;
34use crate::Matrix;
35use std::sync::atomic::{AtomicU64, Ordering};
36use std::sync::{Mutex, PoisonError};
37
38/// What the UI thread reads back from a server visual.
39#[derive(Clone, Copy, Debug, PartialEq)]
40pub struct ReadbackData {
41    pub matrix: Matrix,
42    pub revision: u64,
43    pub target_id: i64,
44    pub visible: bool,
45    pub transformed_subtree_bounds: Option<LtrbRect>,
46}
47
48struct Slot {
49    revision: AtomicU64,
50    data: Mutex<ReadbackData>,
51}
52
53impl Slot {
54    fn new() -> Self {
55        Self {
56            revision: AtomicU64::new(u64::MAX),
57            data: Mutex::new(ReadbackData {
58                matrix: Matrix::IDENTITY,
59                revision: u64::MAX,
60                target_id: 0,
61                visible: false,
62                transformed_subtree_bounds: None,
63            }),
64        }
65    }
66
67    fn read(&self) -> ReadbackData {
68        *self.data.lock().unwrap_or_else(PoisonError::into_inner)
69    }
70}
71
72/// The two readback slots of a visual.
73pub struct VisualReadback {
74    readback0: Slot,
75    readback1: Slot,
76}
77
78impl Default for VisualReadback {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl VisualReadback {
85    pub fn new() -> Self {
86        Self { readback0: Slot::new(), readback1: Slot::new() }
87    }
88
89    pub fn get_readback(&self, reader_revision: u64) -> Option<ReadbackData> {
90        let slot0_revision = self.readback0.revision.load(Ordering::SeqCst);
91        let slot1_revision = self.readback1.revision.load(Ordering::SeqCst);
92
93        if slot0_revision <= reader_revision && slot1_revision <= reader_revision {
94            // Pick the newest one, it's guaranteed to be not touched by the writer
95            return Some(if slot1_revision > slot0_revision { self.readback1.read() } else { self.readback0.read() });
96        }
97
98        if slot0_revision <= reader_revision {
99            return Some(self.readback0.read());
100        }
101
102        if slot1_revision <= reader_revision {
103            return Some(self.readback1.read());
104        }
105
106        // No readback was written for this visual yet
107        None
108    }
109
110    pub fn update_readback(&self, writer_revision: u64, reader_revision: u64, mut data: ReadbackData) {
111        let revision0 = self.readback0.revision.load(Ordering::SeqCst);
112        let revision1 = self.readback1.revision.load(Ordering::SeqCst);
113        let slot = if revision0 > reader_revision {
114            // Future revision is in slot0
115            &self.readback0
116        } else if revision1 > reader_revision {
117            // Future revision is in slot1
118            &self.readback1
119        } else if revision0 < revision1 {
120            // No future revisions, overwrite the oldest one since reader will always pick the newest
121            &self.readback0
122        } else {
123            &self.readback1
124        };
125
126        let mut guard = slot.data.lock().unwrap_or_else(PoisonError::into_inner);
127        slot.revision.store(writer_revision, Ordering::SeqCst);
128        data.revision = writer_revision;
129        *guard = data;
130    }
131}
132
133impl ServerCompositionVisual {
134    pub(super) fn enqueue_for_readback_update(&self) {
135        if !self.enqueued_for_readback_update.get() {
136            if let (Some(compositor), Some(this)) = (self.compositor(), self.rc()) {
137                self.enqueued_for_readback_update.set(true);
138                compositor.enqueue_visual_for_readback_update_pass(this);
139            }
140        }
141    }
142
143    pub fn get_readback(&self, reader_revision: u64) -> Option<ReadbackData> {
144        self.readback.get_readback(reader_revision)
145    }
146
147    pub fn update_readback(&self, writer_revision: u64, reader_revision: u64) {
148        self.enqueued_for_readback_update.set(false);
149        self.readback.update_readback(
150            writer_revision,
151            reader_revision,
152            ReadbackData {
153                matrix: self.own_transform.get().unwrap_or(Matrix::IDENTITY),
154                revision: writer_revision,
155                target_id: self.root().map_or(-1, |root| root.id()),
156                transformed_subtree_bounds: self.transformed_sub_tree_bounds.get(),
157                visible: self.visible(),
158            },
159        );
160    }
161}