ftui_core/
read_optimized.rs1#![forbid(unsafe_code)]
2
3use std::sync::{Arc, Mutex, RwLock};
54
55use arc_swap::ArcSwap;
56
57pub trait ReadOptimized<T: Clone + Send + Sync>: Send + Sync {
68 fn load(&self) -> T;
70
71 fn store(&self, val: T);
73}
74
75pub struct ArcSwapStore<T> {
86 inner: ArcSwap<T>,
87}
88
89impl<T: Clone + Send + Sync> ArcSwapStore<T> {
90 pub fn new(val: T) -> Self {
92 Self {
93 inner: ArcSwap::from_pointee(val),
94 }
95 }
96
97 pub fn load_ref(&self) -> arc_swap::Guard<Arc<T>> {
101 self.inner.load()
102 }
103}
104
105impl<T: Clone + Send + Sync> ReadOptimized<T> for ArcSwapStore<T> {
106 #[inline]
107 fn load(&self) -> T {
108 let guard = self.inner.load();
110 T::clone(&guard)
111 }
112
113 #[inline]
114 fn store(&self, val: T) {
115 self.inner.store(Arc::new(val));
116 }
117}
118
119pub struct RwLockStore<T> {
127 inner: RwLock<T>,
128}
129
130impl<T: Clone + Send + Sync> RwLockStore<T> {
131 pub fn new(val: T) -> Self {
133 Self {
134 inner: RwLock::new(val),
135 }
136 }
137}
138
139impl<T: Clone + Send + Sync> ReadOptimized<T> for RwLockStore<T> {
140 #[inline]
141 fn load(&self) -> T {
142 self.inner.read().expect("RwLock poisoned").clone()
143 }
144
145 #[inline]
146 fn store(&self, val: T) {
147 *self.inner.write().expect("RwLock poisoned") = val;
148 }
149}
150
151pub struct MutexStore<T> {
159 inner: Mutex<T>,
160}
161
162impl<T: Clone + Send + Sync> MutexStore<T> {
163 pub fn new(val: T) -> Self {
165 Self {
166 inner: Mutex::new(val),
167 }
168 }
169}
170
171impl<T: Clone + Send + Sync> ReadOptimized<T> for MutexStore<T> {
172 #[inline]
173 fn load(&self) -> T {
174 self.inner.lock().expect("Mutex poisoned").clone()
175 }
176
177 #[inline]
178 fn store(&self, val: T) {
179 *self.inner.lock().expect("Mutex poisoned") = val;
180 }
181}
182
183#[cfg(test)]
188mod tests {
189 use super::*;
190 use std::sync::Barrier;
191 use std::thread;
192
193 #[derive(Debug, Clone, PartialEq, Eq)]
197 struct Config {
198 name: String,
199 value: u64,
200 }
201
202 fn make_config(n: u64) -> Config {
203 Config {
204 name: format!("cfg-{n}"),
205 value: n,
206 }
207 }
208
209 #[test]
212 fn arcswap_load_returns_initial_value() {
213 let store = ArcSwapStore::new(42u64);
214 assert_eq!(store.load(), 42);
215 }
216
217 #[test]
218 fn arcswap_store_then_load() {
219 let store = ArcSwapStore::new(0u64);
220 store.store(99);
221 assert_eq!(store.load(), 99);
222 }
223
224 #[test]
225 fn arcswap_load_ref_borrows_without_clone() {
226 let store = ArcSwapStore::new(make_config(1));
227 let guard = store.load_ref();
228 assert_eq!(guard.name, "cfg-1");
229 assert_eq!(guard.value, 1);
230 }
231
232 #[test]
233 fn arcswap_multiple_stores_last_wins() {
234 let store = ArcSwapStore::new(0u64);
235 for i in 1..=100 {
236 store.store(i);
237 }
238 assert_eq!(store.load(), 100);
239 }
240
241 #[test]
242 fn arcswap_concurrent_reads_never_panic() {
243 let store = Arc::new(ArcSwapStore::new(make_config(0)));
244 let barrier = Arc::new(Barrier::new(8));
245
246 let handles: Vec<_> = (0..8)
247 .map(|_| {
248 let s = Arc::clone(&store);
249 let b = Arc::clone(&barrier);
250 thread::spawn(move || {
251 b.wait();
252 for _ in 0..1000 {
253 let _ = s.load();
254 }
255 })
256 })
257 .collect();
258
259 for h in handles {
260 h.join().unwrap();
261 }
262 }
263
264 #[test]
265 fn arcswap_concurrent_read_write() {
266 let store = Arc::new(ArcSwapStore::new(0u64));
267 let barrier = Arc::new(Barrier::new(9)); let readers: Vec<_> = (0..8)
271 .map(|_| {
272 let s = Arc::clone(&store);
273 let b = Arc::clone(&barrier);
274 thread::spawn(move || {
275 b.wait();
276 let mut last = 0u64;
277 for _ in 0..10_000 {
278 let v = s.load();
279 assert!(v >= last, "stale read: got {v}, expected >= {last}");
282 last = v;
283 }
284 })
285 })
286 .collect();
287
288 let writer = {
290 let s = Arc::clone(&store);
291 let b = Arc::clone(&barrier);
292 thread::spawn(move || {
293 b.wait();
294 for i in 1..=10_000u64 {
295 s.store(i);
296 }
297 })
298 };
299
300 writer.join().unwrap();
301 for h in readers {
302 h.join().unwrap();
303 }
304 assert_eq!(store.load(), 10_000);
305 }
306
307 #[test]
310 fn rwlock_load_returns_initial_value() {
311 let store = RwLockStore::new(42u64);
312 assert_eq!(store.load(), 42);
313 }
314
315 #[test]
316 fn rwlock_store_then_load() {
317 let store = RwLockStore::new(0u64);
318 store.store(99);
319 assert_eq!(store.load(), 99);
320 }
321
322 #[test]
323 fn rwlock_concurrent_read_write() {
324 let store = Arc::new(RwLockStore::new(0u64));
325 let barrier = Arc::new(Barrier::new(5));
326
327 let readers: Vec<_> = (0..4)
328 .map(|_| {
329 let s = Arc::clone(&store);
330 let b = Arc::clone(&barrier);
331 thread::spawn(move || {
332 b.wait();
333 for _ in 0..5_000 {
334 let _ = s.load();
335 }
336 })
337 })
338 .collect();
339
340 let writer = {
341 let s = Arc::clone(&store);
342 let b = Arc::clone(&barrier);
343 thread::spawn(move || {
344 b.wait();
345 for i in 1..=5_000u64 {
346 s.store(i);
347 }
348 })
349 };
350
351 writer.join().unwrap();
352 for h in readers {
353 h.join().unwrap();
354 }
355 assert_eq!(store.load(), 5_000);
356 }
357
358 #[test]
361 fn mutex_load_returns_initial_value() {
362 let store = MutexStore::new(42u64);
363 assert_eq!(store.load(), 42);
364 }
365
366 #[test]
367 fn mutex_store_then_load() {
368 let store = MutexStore::new(0u64);
369 store.store(99);
370 assert_eq!(store.load(), 99);
371 }
372
373 #[test]
374 fn mutex_concurrent_read_write() {
375 let store = Arc::new(MutexStore::new(0u64));
376 let barrier = Arc::new(Barrier::new(5));
377
378 let readers: Vec<_> = (0..4)
379 .map(|_| {
380 let s = Arc::clone(&store);
381 let b = Arc::clone(&barrier);
382 thread::spawn(move || {
383 b.wait();
384 for _ in 0..5_000 {
385 let _ = s.load();
386 }
387 })
388 })
389 .collect();
390
391 let writer = {
392 let s = Arc::clone(&store);
393 let b = Arc::clone(&barrier);
394 thread::spawn(move || {
395 b.wait();
396 for i in 1..=5_000u64 {
397 s.store(i);
398 }
399 })
400 };
401
402 writer.join().unwrap();
403 for h in readers {
404 h.join().unwrap();
405 }
406 assert_eq!(store.load(), 5_000);
407 }
408
409 #[test]
412 fn trait_object_arcswap() {
413 let store: Box<dyn ReadOptimized<u64>> = Box::new(ArcSwapStore::new(10));
414 assert_eq!(store.load(), 10);
415 store.store(20);
416 assert_eq!(store.load(), 20);
417 }
418
419 #[test]
420 fn trait_object_rwlock() {
421 let store: Box<dyn ReadOptimized<u64>> = Box::new(RwLockStore::new(10));
422 assert_eq!(store.load(), 10);
423 store.store(20);
424 assert_eq!(store.load(), 20);
425 }
426
427 #[test]
428 fn trait_object_mutex() {
429 let store: Box<dyn ReadOptimized<u64>> = Box::new(MutexStore::new(10));
430 assert_eq!(store.load(), 10);
431 store.store(20);
432 assert_eq!(store.load(), 20);
433 }
434
435 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
438 struct FakeCaps {
439 true_color: bool,
440 sync_output: bool,
441 mouse_sgr: bool,
442 }
443
444 #[test]
445 fn arcswap_with_copy_type() {
446 let caps = FakeCaps {
447 true_color: true,
448 sync_output: false,
449 mouse_sgr: true,
450 };
451 let store = ArcSwapStore::new(caps);
452 assert_eq!(store.load(), caps);
453
454 let updated = FakeCaps {
455 true_color: true,
456 sync_output: true,
457 mouse_sgr: true,
458 };
459 store.store(updated);
460 assert_eq!(store.load(), updated);
461 }
462
463 #[test]
464 fn concurrent_copy_type_reads() {
465 let caps = FakeCaps {
466 true_color: true,
467 sync_output: false,
468 mouse_sgr: true,
469 };
470 let store = Arc::new(ArcSwapStore::new(caps));
471 let barrier = Arc::new(Barrier::new(8));
472
473 let handles: Vec<_> = (0..8)
474 .map(|_| {
475 let s = Arc::clone(&store);
476 let b = Arc::clone(&barrier);
477 thread::spawn(move || {
478 b.wait();
479 for _ in 0..10_000 {
480 let v = s.load();
481 assert!(v.true_color);
483 assert!(v.mouse_sgr);
484 }
485 })
486 })
487 .collect();
488
489 for h in handles {
490 h.join().unwrap();
491 }
492 }
493}