use std::sync::{Arc, Weak};
use std::time::{Duration, Instant};
use super::consumer::{ConsumerShared, LiveImageConsumer};
use super::damage::{DamageRing, Pending};
use super::held::{self, Rank, Ranked};
use super::lock::{Guard, Lock};
use super::meta::LiveMeta;
use super::stats::LiveImageSourceStats;
use super::writer::{LiveImageWriter, WriterToken};
use super::{LiveImageError, LiveImageId, LiveImageStatus, LivePixelFormat};
use crate::sync::{AtomicU32, AtomicU64, Mutex, Ordering};
use crate::wake::{RedrawWaker, WakeFlag, WakeKind};
#[derive(Clone)]
pub struct LiveImageSource {
pub(crate) shared: Arc<Shared>,
}
#[derive(Debug)]
#[must_use]
pub struct LiveImageSourceBuilder {
format: LivePixelFormat,
label: Option<String>,
hint: Option<(u32, u32)>,
}
impl LiveImageSourceBuilder {
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn size_hint(mut self, width: u32, height: u32) -> Self {
self.hint = validate_size(width, height).ok().map(|()| (width, height));
self
}
pub fn build(self) -> LiveImageSource {
LiveImageSource::with(self.format, self.label, self.hint)
}
}
pub(crate) struct Shared {
pub(crate) id: LiveImageId,
pub(crate) label: Option<Box<str>>,
pub(crate) format: LivePixelFormat,
state: Lock<State>,
meta: AtomicU32,
generation: AtomicU64,
displayed_generation: AtomicU64,
consumers: Mutex<Vec<Weak<ConsumerShared>>>,
handoff: WakeFlag,
parked: AtomicU32,
pub(crate) counters: SourceCounters,
}
#[derive(Default)]
pub(crate) struct SourceCounters {
pub(crate) commits: AtomicU64,
pub(crate) abandoned: AtomicU64,
pub(crate) bytes_written: AtomicU64,
pub(crate) wakes: AtomicU64,
pub(crate) wakes_coalesced: AtomicU64,
pub(crate) writers: AtomicU32,
pub(crate) attachments: AtomicU32,
pub(crate) observed: AtomicU32,
pub(crate) paused: AtomicU32,
}
pub(crate) struct State {
pub(crate) pixels: Vec<u8>,
pub(crate) size: Option<(u32, u32)>,
pub(crate) stride: usize,
pub(crate) hint: Option<(u32, u32)>,
pub(crate) status: LiveImageStatus,
pub(crate) writers: u32,
pub(crate) session: u64,
pub(crate) generation: u64,
pub(crate) ring: DamageRing,
pub(crate) pending: Pending,
}
impl State {
fn meta(&self) -> LiveMeta {
LiveMeta {
status: self.status,
size: self.size.or(self.hint),
}
}
pub(crate) fn free(&mut self) {
if let Some(size) = self.size.take() {
self.hint = Some(size);
}
self.pixels = Vec::new();
self.stride = 0;
self.pending = Pending::new();
self.pending.force_full = true;
}
pub(crate) fn resize(
&mut self,
width: u32,
height: u32,
bpp: usize,
) -> Result<(), LiveImageError> {
validate_size(width, height)?;
if self.size == Some((width, height)) {
return Ok(());
}
let stride = width as usize * bpp;
let needed = stride * height as usize;
if needed > self.pixels.capacity() {
reserve(&mut self.pixels, needed)?;
}
self.pixels.clear();
self.pixels.resize(needed, 0);
self.stride = stride;
self.size = Some((width, height));
self.pending.resized = true;
self.pending.force_full = true;
Ok(())
}
pub(crate) fn publish(&mut self, now: Instant) -> Option<u64> {
if !self.pending.publishes() {
return None;
}
let damage = if self.pending.force_full || self.pending.resized {
super::damage::Damage::Full
} else {
self.pending.damage
};
if self.status == LiveImageStatus::Waiting && self.pending.wrote {
self.status = LiveImageStatus::Live;
}
self.generation += 1;
let (w, h) = self.size.unwrap_or((0, 0));
self.ring.record(self.generation, damage, w, h, now);
self.pending = Pending::new();
Some(self.generation)
}
}
fn reserve(pixels: &mut Vec<u8>, needed: usize) -> Result<(), LiveImageError> {
let out_of_memory = || LiveImageError::OutOfMemory {
bytes: needed as u64,
};
#[cfg(all(test, not(teksilo_loom)))]
if test_hooks::take_reserve_failure() {
return Err(out_of_memory());
}
pixels
.try_reserve_exact(needed - pixels.len())
.map_err(|_| out_of_memory())
}
pub(crate) fn validate_size(width: u32, height: u32) -> Result<(), LiveImageError> {
if width == 0 || height == 0 {
return Err(LiveImageError::ZeroSize { width, height });
}
let bytes = u64::from(width) * u64::from(height) * 4;
if width > LiveImageSource::MAX_DIMENSION
|| height > LiveImageSource::MAX_DIMENSION
|| bytes > LiveImageSource::MAX_BYTES as u64
{
return Err(LiveImageError::TooLarge { width, height });
}
Ok(())
}
pub(crate) struct StateGuard<'a> {
guard: Option<Guard<'a, State>>,
rank: Option<Ranked>,
shared: &'a Shared,
}
impl<'a> StateGuard<'a> {
pub(crate) fn state(&self) -> &State {
self.guard.as_ref().expect("held until drop").get()
}
pub(crate) fn state_mut(&mut self) -> &mut State {
self.guard.as_mut().expect("held until drop").get_mut()
}
pub(crate) fn shared(&self) -> &'a Shared {
self.shared
}
}
impl Drop for StateGuard<'_> {
fn drop(&mut self) {
let fair = self.shared.parked.load(Ordering::Relaxed) > 0;
if let Some(guard) = self.guard.take() {
guard.unlock(fair);
}
drop(self.rank.take());
self.shared.run_handoff();
}
}
impl Shared {
fn wrap<'a>(&'a self, guard: Guard<'a, State>, rank: Ranked) -> StateGuard<'a> {
StateGuard {
guard: Some(guard),
rank: Some(rank),
shared: self,
}
}
pub(crate) fn lock_state(&self) -> StateGuard<'_> {
let rank = Ranked::acquire(Rank::State);
let guard = self.state.lock();
self.wrap(guard, rank)
}
pub(crate) fn try_lock_state(&self) -> Option<StateGuard<'_>> {
let rank = Ranked::acquire(Rank::State);
let guard = self.state.try_lock()?;
Some(self.wrap(guard, rank))
}
pub(crate) fn lock_state_for(&self, timeout: Duration) -> Option<StateGuard<'_>> {
let rank = Ranked::acquire(Rank::State);
self.parked.fetch_add(1, Ordering::Relaxed);
let guard = self.state.try_lock_for(timeout);
self.parked.fetch_sub(1, Ordering::Relaxed);
Some(self.wrap(guard?, rank))
}
pub(crate) fn assert_not_held(&self, what: &str) {
if held::holds(self.id) {
panic!(
"{what} on {} while this thread holds a write transaction on it: \
commit or drop the transaction first (the lock is not reentrant)",
self.describe()
);
}
}
pub(crate) fn describe(&self) -> String {
match &self.label {
Some(label) => format!("live image {} {label:?}", self.id),
None => format!("live image {}", self.id),
}
}
pub(crate) fn meta(&self) -> LiveMeta {
LiveMeta::unpack(self.meta.load(Ordering::Acquire))
}
pub(crate) fn generation(&self) -> u64 {
self.generation.load(Ordering::Acquire)
}
pub(crate) fn raise_displayed(&self, generation: u64) {
self.displayed_generation
.fetch_max(generation, Ordering::Relaxed);
}
pub(crate) fn publish_meta(&self, state: &State) -> bool {
let word = state.meta().pack();
word != self.meta.swap(word, Ordering::AcqRel)
}
pub(crate) fn publish_generation(&self, generation: u64) {
self.generation.store(generation, Ordering::Release);
self.counters.commits.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn live_consumers(&self) -> Vec<Arc<ConsumerShared>> {
let _rank = Ranked::acquire(Rank::List);
let mut list = self
.consumers
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
list.retain(|weak| weak.upgrade().is_some_and(|c| !c.is_detached()));
list.iter().filter_map(Weak::upgrade).collect()
}
pub(crate) fn register(&self, consumer: &Arc<ConsumerShared>) {
let _rank = Ranked::acquire(Rank::List);
let mut list = self
.consumers
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
list.retain(|weak| weak.upgrade().is_some_and(|c| !c.is_detached()));
list.push(Arc::downgrade(consumer));
}
fn run_handoff(&self) {
if !self.handoff.take() {
return;
}
let mut wakers = Wakers::default();
for consumer in self.live_consumers() {
if consumer.handoff.take()
&& let Some(waker) = consumer.waker()
{
wakers.add(waker, WakeKind::Draw);
}
}
wakers.wake(&self.counters);
}
pub(crate) fn request_handoff(&self, consumer: &ConsumerShared) {
let _ = consumer.handoff.raise();
let _ = self.handoff.raise();
}
pub(crate) fn notify(&self, pixels: bool, meta: bool) {
if !pixels && !meta {
return;
}
let consumers = self.live_consumers();
if consumers.is_empty() {
return;
}
let mut wakers = Wakers::default();
let mut raised = false;
for consumer in &consumers {
let mut kind = None;
if pixels && consumer.pixels.raise() {
kind = Some(WakeKind::Draw);
}
if meta && consumer.geometry.raise() {
kind = Some(WakeKind::Layout);
}
let Some(kind) = kind else {
continue;
};
raised = true;
if let Some(waker) = consumer.waker() {
wakers.add(waker, kind);
}
}
if !raised {
self.counters
.wakes_coalesced
.fetch_add(1, Ordering::Relaxed);
}
wakers.wake(&self.counters);
}
pub(crate) fn release_session(&self, session: u64) {
if held::defer_release(self.id, session) {
return;
}
let mut guard = self.lock_state();
self.apply_release(guard.state_mut(), session);
let meta = self.publish_meta(guard.state());
drop(guard);
self.notify(false, meta);
}
pub(crate) fn apply_release(&self, state: &mut State, session: u64) {
if state.session != session {
return;
}
state.writers -= 1;
self.counters
.writers
.store(state.writers, Ordering::Relaxed);
if state.writers == 0 {
state.free();
state.status = LiveImageStatus::Disconnected;
}
}
}
#[derive(Default)]
pub(crate) struct Wakers(Vec<(Arc<dyn RedrawWaker>, WakeKind)>);
impl Wakers {
pub(crate) fn add(&mut self, waker: Arc<dyn RedrawWaker>, kind: WakeKind) {
let data = Arc::as_ptr(&waker) as *const ();
match self
.0
.iter_mut()
.find(|(other, _)| Arc::as_ptr(other) as *const () == data)
{
Some((_, other)) => *other = (*other).max(kind),
None => self.0.push((waker, kind)),
}
}
pub(crate) fn wake(self, counters: &SourceCounters) {
for (waker, kind) in self.0 {
if std::thread::panicking() {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| waker.wake(kind)));
} else {
waker.wake(kind);
}
counters.wakes.fetch_add(1, Ordering::Relaxed);
}
}
}
impl LiveImageSource {
pub const MAX_DIMENSION: u32 = 16_384;
pub const MAX_BYTES: usize = 1 << 30;
pub fn new(format: LivePixelFormat) -> Self {
Self::with(format, None, None)
}
pub fn builder(format: LivePixelFormat) -> LiveImageSourceBuilder {
LiveImageSourceBuilder {
format,
label: None,
hint: None,
}
}
fn with(format: LivePixelFormat, label: Option<String>, hint: Option<(u32, u32)>) -> Self {
let state = State {
pixels: Vec::new(),
size: None,
stride: 0,
hint,
status: LiveImageStatus::Disconnected,
writers: 0,
session: 0,
generation: 0,
ring: DamageRing::new(),
pending: Pending::new(),
};
let meta = state.meta().pack();
Self {
shared: Arc::new(Shared {
id: LiveImageId::next(),
label: label.map(String::into_boxed_str),
format,
state: Lock::new(state),
meta: AtomicU32::new(meta),
generation: AtomicU64::new(0),
displayed_generation: AtomicU64::new(0),
consumers: Mutex::new(Vec::new()),
handoff: WakeFlag::new(),
parked: AtomicU32::new(0),
counters: SourceCounters::default(),
}),
}
}
pub fn writer(&self) -> LiveImageWriter {
self.shared.assert_not_held("LiveImageSource::writer");
let mut guard = self.shared.lock_state();
let state = guard.state_mut();
if state.writers == 0 {
state.status = LiveImageStatus::Waiting;
state.pending.force_full = true;
}
state.writers += 1;
let session = state.session;
self.shared
.counters
.writers
.store(state.writers, Ordering::Relaxed);
let meta = self.shared.publish_meta(guard.state());
drop(guard);
self.shared.notify(false, meta);
LiveImageWriter::new(WriterToken::new(self.clone(), session))
}
pub fn writer_exclusive(&self) -> LiveImageWriter {
self.shared
.assert_not_held("LiveImageSource::writer_exclusive");
let mut guard = self.shared.lock_state();
let state = guard.state_mut();
state.session += 1;
state.writers = 1;
state.free();
state.status = LiveImageStatus::Waiting;
let session = state.session;
self.shared.counters.writers.store(1, Ordering::Relaxed);
let meta = self.shared.publish_meta(guard.state());
drop(guard);
self.shared.notify(false, meta);
LiveImageWriter::new(WriterToken::new(self.clone(), session))
}
pub fn set_size_hint(&self, width: u32, height: u32) -> Result<(), LiveImageError> {
validate_size(width, height)?;
self.shared
.assert_not_held("LiveImageSource::set_size_hint");
let mut guard = self.shared.lock_state();
guard.state_mut().hint = Some((width, height));
let meta = self.shared.publish_meta(guard.state());
drop(guard);
self.shared.notify(false, meta);
Ok(())
}
pub fn id(&self) -> LiveImageId {
self.shared.id
}
pub fn label(&self) -> Option<&str> {
self.shared.label.as_deref()
}
pub fn format(&self) -> LivePixelFormat {
self.shared.format
}
pub fn status(&self) -> LiveImageStatus {
self.shared.meta().status
}
pub fn size(&self) -> Option<(u32, u32)> {
self.shared.meta().size
}
pub fn generation(&self) -> u64 {
self.shared.generation()
}
pub fn displayed_generation(&self) -> u64 {
self.shared.displayed_generation.load(Ordering::Relaxed)
}
pub fn is_observed(&self) -> bool {
self.shared.counters.observed.load(Ordering::Relaxed) > 0
}
pub fn is_displayed(&self) -> bool {
self.shared
.live_consumers()
.iter()
.any(|c| c.is_displayed())
}
pub fn stats(&self) -> LiveImageSourceStats {
let c = &self.shared.counters;
LiveImageSourceStats {
generation: self.generation(),
displayed_generation: self.displayed_generation(),
commits: c.commits.load(Ordering::Relaxed),
abandoned: c.abandoned.load(Ordering::Relaxed),
bytes_written: c.bytes_written.load(Ordering::Relaxed),
wakes: c.wakes.load(Ordering::Relaxed),
wakes_coalesced: c.wakes_coalesced.load(Ordering::Relaxed),
writers: c.writers.load(Ordering::Relaxed),
attachments: c.attachments.load(Ordering::Relaxed),
observed_attachments: c.observed.load(Ordering::Relaxed),
paused_attachments: c.paused.load(Ordering::Relaxed),
}
}
pub fn ptr_eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.shared, &other.shared)
}
#[doc(hidden)]
pub fn meta(&self) -> LiveMeta {
self.shared.meta()
}
#[doc(hidden)]
pub fn attach(&self, waker: Option<Arc<dyn RedrawWaker>>) -> LiveImageConsumer {
let consumer = Arc::new(ConsumerShared::new(self.clone(), waker));
self.shared.register(&consumer);
self.shared
.counters
.attachments
.fetch_add(1, Ordering::Relaxed);
consumer.record_layout_meta(self.shared.meta());
LiveImageConsumer::from_shared(consumer)
}
}
impl PartialEq for LiveImageSource {
fn eq(&self, other: &Self) -> bool {
self.ptr_eq(other)
}
}
impl Eq for LiveImageSource {}
impl std::hash::Hash for LiveImageSource {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.shared.id.hash(state);
}
}
impl std::fmt::Debug for LiveImageSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let meta = self.shared.meta();
write!(f, "LiveImageSource({}", self.shared.id)?;
if let Some(label) = &self.shared.label {
write!(f, " {label:?}")?;
}
write!(f, " {:?}", self.shared.format)?;
match meta.size {
Some((w, h)) => write!(f, " {w}x{h}")?,
None => write!(f, " no size")?,
}
write!(f, " {:?} gen {})", meta.status, self.generation())
}
}
#[cfg(all(test, not(teksilo_loom)))]
pub(crate) mod test_hooks {
use std::cell::Cell;
std::thread_local! {
static FAIL_RESERVE: Cell<bool> = const { Cell::new(false) };
}
pub(crate) fn fail_next_reserve() {
FAIL_RESERVE.with(|f| f.set(true));
}
pub(super) fn take_reserve_failure() -> bool {
FAIL_RESERVE.with(|f| f.replace(false))
}
}