ferroui_base/rendering/composition/server/
readback_indices.rs1use std::sync::atomic::{AtomicU64, Ordering};
2use std::sync::{Mutex, MutexGuard, PoisonError};
3
4pub struct ReadbackIndices {
21 lock: Mutex<()>,
22
23 read_revision: AtomicU64,
24 next_write_revision: AtomicU64,
25 write_revision: AtomicU64,
26 last_completed_write: AtomicU64,
27}
28
29impl ReadbackIndices {
30 pub fn new() -> Self {
31 Self {
32 lock: Mutex::new(()),
33 read_revision: AtomicU64::new(0),
34 next_write_revision: AtomicU64::new(1),
35 write_revision: AtomicU64::new(0),
36 last_completed_write: AtomicU64::new(0),
37 }
38 }
39
40 pub fn lock(&self) -> MutexGuard<'_, ()> {
44 self.lock.lock().unwrap_or_else(PoisonError::into_inner)
45 }
46
47 pub fn read_revision(&self) -> u64 {
48 self.read_revision.load(Ordering::SeqCst)
49 }
50
51 pub fn write_revision(&self) -> u64 {
52 self.write_revision.load(Ordering::SeqCst)
53 }
54
55 pub fn last_completed_write(&self) -> u64 {
56 self.last_completed_write.load(Ordering::SeqCst)
57 }
58
59 pub fn next_read(&self) {
60 let _guard = self.lock();
61 self.read_revision.store(self.last_completed_write.load(Ordering::SeqCst), Ordering::SeqCst);
62 }
63
64 pub fn begin_write(&self) -> ReadbackWriteScope<'_> {
67 let guard = self.lock();
68 let revision = self.next_write_revision.fetch_add(1, Ordering::SeqCst);
69 self.write_revision.store(revision, Ordering::SeqCst);
70 ReadbackWriteScope { indices: self, _guard: guard }
71 }
72}
73
74impl Default for ReadbackIndices {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80#[must_use = "the write ends as soon as the scope is dropped"]
85pub struct ReadbackWriteScope<'a> {
86 indices: &'a ReadbackIndices,
87 _guard: MutexGuard<'a, ()>,
88}
89
90impl ReadbackWriteScope<'_> {
91 pub fn end_write(self) {}
93}
94
95impl Drop for ReadbackWriteScope<'_> {
96 fn drop(&mut self) {
97 self.indices
99 .last_completed_write
100 .store(self.indices.write_revision.load(Ordering::SeqCst), Ordering::SeqCst);
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use std::sync::Arc;
108
109 fn assert_send_sync<T: Send + Sync>() {}
110
111 #[test]
112 fn is_send_and_sync() {
113 assert_send_sync::<ReadbackIndices>();
114 }
115
116 #[test]
117 fn initial_state() {
118 let indices = ReadbackIndices::new();
119 assert_eq!(0, indices.read_revision());
120 assert_eq!(0, indices.write_revision());
121 assert_eq!(0, indices.last_completed_write());
122 indices.next_read();
123 assert_eq!(0, indices.read_revision());
124 }
125
126 #[test]
127 fn write_revisions_advance_and_complete_on_end_write() {
128 let indices = ReadbackIndices::new();
129
130 let write = indices.begin_write();
131 assert_eq!(1, indices.write_revision());
132 assert_eq!(0, indices.last_completed_write());
133 write.end_write();
134 assert_eq!(1, indices.last_completed_write());
135 assert_eq!(0, indices.read_revision());
137 indices.next_read();
138 assert_eq!(1, indices.read_revision());
139
140 indices.begin_write().end_write();
141 indices.begin_write().end_write();
142 assert_eq!(3, indices.write_revision());
143 assert_eq!(3, indices.last_completed_write());
144 assert_eq!(1, indices.read_revision());
145 indices.next_read();
146 assert_eq!(3, indices.read_revision());
147 }
148
149 #[test]
150 fn dropping_the_scope_ends_the_write() {
151 let indices = ReadbackIndices::new();
152 {
153 let _write = indices.begin_write();
154 assert_eq!(0, indices.last_completed_write());
155 }
156 assert_eq!(1, indices.last_completed_write());
157 drop(indices.lock());
159 }
160
161 #[test]
162 fn the_lock_is_held_during_a_write() {
163 let indices = ReadbackIndices::new();
164 let write = indices.begin_write();
165 assert!(indices.lock.try_lock().is_err());
166 write.end_write();
167 assert!(indices.lock.try_lock().is_ok());
168 }
169
170 #[test]
171 fn next_read_waits_for_a_write_in_progress() {
172 let indices = Arc::new(ReadbackIndices::new());
173 let (started_tx, started_rx) = std::sync::mpsc::channel();
174 let (finish_tx, finish_rx) = std::sync::mpsc::channel::<()>();
175
176 let writer = {
177 let indices = indices.clone();
178 std::thread::spawn(move || {
179 let write = indices.begin_write();
180 started_tx.send(()).unwrap();
181 finish_rx.recv().unwrap();
182 write.end_write();
183 })
184 };
185
186 started_rx.recv().unwrap();
187 assert_eq!(1, indices.write_revision());
189 assert_eq!(0, indices.last_completed_write());
190
191 let reader = {
192 let indices = indices.clone();
193 std::thread::spawn(move || {
194 indices.next_read();
196 indices.read_revision()
197 })
198 };
199
200 finish_tx.send(()).unwrap();
201 writer.join().unwrap();
202 assert_eq!(1, reader.join().unwrap());
203 }
204}