use super::SampleCursor;
use glam::Vec3;
use indicatrix_net::client::Accumulator;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
struct InFlightChunk {
start: u32,
count: u32,
accumulator: Arc<Mutex<Accumulator>>,
}
struct RemoteSums {
sum: Vec<Vec3>,
count: u32,
in_flight: Option<InFlightChunk>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChunkEnd {
pub start: u32,
pub count: u32,
pub done: u32,
}
impl ChunkEnd {
#[must_use]
pub const fn remainder(self) -> (u32, u32) {
(self.start + self.done, self.count - self.done)
}
}
pub struct LiveEpoch {
cursor: SampleCursor,
width: u32,
height: u32,
scene_generation: u64,
remote: Mutex<RemoteSums>,
}
impl LiveEpoch {
#[must_use]
pub const fn new(width: u32, height: u32, target_samples: u32) -> Self {
Self {
cursor: SampleCursor::new(0, target_samples),
width,
height,
scene_generation: 0,
remote: Mutex::new(RemoteSums {
sum: Vec::new(),
count: 0,
in_flight: None,
}),
}
}
#[must_use]
pub const fn for_scene(mut self, scene_generation: u64) -> Self {
self.scene_generation = scene_generation;
self
}
#[must_use]
pub const fn scene_generation(&self) -> u64 {
self.scene_generation
}
fn lock(&self) -> MutexGuard<'_, RemoteSums> {
self.remote.lock().unwrap_or_else(PoisonError::into_inner)
}
const fn pixel_count(&self) -> usize {
self.width as usize * self.height as usize
}
#[must_use]
pub const fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
#[must_use]
pub fn claim_local(&self, want: u32) -> Option<(u32, u32)> {
self.cursor.claim_local_bounded(want)
}
#[must_use]
pub fn claim_remote(&self, want: u32) -> Option<(u32, u32)> {
self.cursor.claim(want)
}
pub fn return_to_local(&self, start: u32, count: u32) {
self.cursor.return_to_local(start, count);
}
pub fn begin_chunk(&self, start: u32, count: u32, accumulator: Arc<Mutex<Accumulator>>) {
self.lock().in_flight = Some(InFlightChunk {
start,
count,
accumulator,
});
}
pub fn finish_chunk(&self) -> Option<ChunkEnd> {
let mut sums = self.lock();
let chunk = sums.in_flight.take()?;
let acc = chunk
.accumulator
.lock()
.unwrap_or_else(PoisonError::into_inner);
let merged = if acc.buffer().len() == self.pixel_count() {
acc.samples_done().min(chunk.count)
} else {
0
};
if merged > 0 {
if sums.sum.is_empty() {
sums.sum = vec![Vec3::ZERO; self.pixel_count()];
}
for (dst, src) in sums.sum.iter_mut().zip(acc.buffer()) {
*dst += *src;
}
sums.count += merged;
}
drop(acc);
drop(sums);
Some(ChunkEnd {
start: chunk.start,
count: chunk.count,
done: merged,
})
}
pub fn abandon_chunk(&self) -> bool {
self.lock().in_flight.take().is_some()
}
#[must_use]
pub fn remote_done(&self) -> u32 {
let sums = self.lock();
let live = sums.in_flight.as_ref().map_or(0, |chunk| {
chunk
.accumulator
.lock()
.unwrap_or_else(PoisonError::into_inner)
.samples_done()
});
let finished = sums.count;
drop(sums);
finished + live
}
pub fn add_remote_into(&self, dst: &mut [Vec3]) -> u32 {
if dst.len() != self.pixel_count() {
return 0;
}
let sums = self.lock();
if !sums.sum.is_empty() {
for (d, s) in dst.iter_mut().zip(&sums.sum) {
*d += *s;
}
}
let mut count = sums.count;
if let Some(chunk) = sums.in_flight.as_ref() {
let acc = chunk
.accumulator
.lock()
.unwrap_or_else(PoisonError::into_inner);
if acc.buffer().len() == dst.len() {
for (d, s) in dst.iter_mut().zip(acc.buffer()) {
*d += *s;
}
count += acc.samples_done();
}
drop(acc);
}
drop(sums);
count
}
#[must_use]
pub fn remote_snapshot(&self) -> (Vec<Vec3>, u32) {
let mut buffer = vec![Vec3::ZERO; self.pixel_count()];
let count = self.add_remote_into(&mut buffer);
(buffer, count)
}
}