use std::sync::Arc as StdArc;
use super::internal::ReadAttempt;
use super::*;
use crate::sync::{AtomicUsize, Ordering, model, thread};
use crate::wake::{RedrawWaker, WakeKind};
struct LoomWaker {
draw: AtomicUsize,
layout: AtomicUsize,
}
impl RedrawWaker for LoomWaker {
fn wake(&self, kind: WakeKind) {
match kind {
WakeKind::Draw => &self.draw,
_ => &self.layout,
}
.fetch_add(1, Ordering::Relaxed);
}
}
fn waker() -> StdArc<LoomWaker> {
StdArc::new(LoomWaker {
draw: AtomicUsize::new(0),
layout: AtomicUsize::new(0),
})
}
fn count(waker: &LoomWaker) -> usize {
waker.draw.load(Ordering::Relaxed) + waker.layout.load(Ordering::Relaxed)
}
fn layout_wakes(waker: &LoomWaker) -> usize {
waker.layout.load(Ordering::Relaxed)
}
fn live_with_raised_flag() -> (
LiveImageSource,
LiveImageWriter,
LiveImageConsumer,
StdArc<LoomWaker>,
) {
let source = LiveImageSource::new(LivePixelFormat::Rgba8);
let writer = source.writer();
writer.write_frame(1, 1, &[0; 4], 4).unwrap();
let w = waker();
let consumer = source.attach(Some(w.clone() as StdArc<dyn RedrawWaker>));
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[1; 4], 4)
.unwrap();
(source, writer, consumer, w)
}
#[test]
fn loom_a_commit_is_uploaded_or_wakes() {
model(|| {
let (source, writer, consumer, w) = live_with_raised_flag();
let before = count(&w);
let expected = source.generation() + 1;
let producer = thread::spawn(move || {
writer
.write_rect(PixelRect::new(0, 0, 1, 1), &[2; 4], 4)
.unwrap();
writer
});
let _ = consumer.take_pixels();
let seen = source.generation();
let writer = producer.join().unwrap();
assert!(
seen == expected || count(&w) > before,
"commit {expected} neither seen (saw {seen}) nor woken for"
);
drop(writer);
});
}
#[test]
fn loom_a_size_change_reaches_layout_or_wakes() {
model(|| {
let (source, writer, consumer, w) = live_with_raised_flag();
writer.resize(2, 1).unwrap();
let _ = consumer.take_pixels();
let before = layout_wakes(&w);
let producer = thread::spawn(move || {
writer.resize(3, 1).unwrap();
writer
});
let _ = consumer.take_geometry();
let seen = source.meta().size;
let writer = producer.join().unwrap();
assert!(
seen == Some((3, 1)) || layout_wakes(&w) > before,
"the resize neither reached layout (saw {seen:?}) nor sent a layout wake"
);
drop(writer);
});
}
#[test]
fn loom_attach_registers_then_reads() {
model(|| {
let source = LiveImageSource::new(LivePixelFormat::Rgba8);
let writer = source.writer();
writer.resize(1, 1).unwrap();
let producer = thread::spawn(move || {
writer.resize(2, 2).unwrap();
writer
});
let w = waker();
let consumer = source.attach(Some(w.clone() as StdArc<dyn RedrawWaker>));
let recorded = consumer.layout_meta().size;
let writer = producer.join().unwrap();
assert!(
recorded == Some((2, 2)) || layout_wakes(&w) > 0,
"attached with {recorded:?} and never woken for layout"
);
drop(writer);
});
}
#[test]
fn loom_a_busy_read_is_handed_off() {
model(|| {
let (_source, writer, consumer, w) = live_with_raised_flag();
let before = count(&w);
let holder = thread::spawn(move || {
let guard = writer.lock().unwrap();
drop(guard);
writer
});
let locked = matches!(consumer.try_read(), ReadAttempt::Locked(_));
let writer = holder.join().unwrap();
assert!(
locked || count(&w) > before,
"busy, and no unlock woke the window"
);
drop(writer);
});
}
#[test]
fn loom_the_last_writers_drop_races_a_new_writer() {
model(|| {
let source = LiveImageSource::new(LivePixelFormat::Rgba8);
let old = source.writer();
old.write_frame(1, 1, &[0; 4], 4).unwrap();
let dropper = thread::spawn(move || drop(old));
let new = source.writer();
new.resize(1, 1).unwrap();
new.write_rect(PixelRect::new(0, 0, 1, 1), &[1; 4], 4)
.expect("a live writer has a frame after its resize");
dropper.join().unwrap();
assert_eq!(source.status(), LiveImageStatus::Live);
assert_eq!(source.stats().writers, 1);
drop(new);
assert_eq!(source.status(), LiveImageStatus::Disconnected);
});
}