Skip to main content

indicatrix_dispatch/
lane.rs

1//! [`WorkerLane`]: one backend that traces sample chunks, and what it returns.
2
3use crate::CancelToken;
4use glam::Vec3;
5use indicatrix_net::SceneState;
6
7/// A contiguous absolute sample range `[first_sample, first_sample + samples)`, named
8/// like the protocol's `RenderRequest` fields.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct SampleRange {
11    /// The first absolute sample index.
12    pub first_sample: u32,
13    /// How many samples, starting at `first_sample`.
14    pub samples: u32,
15}
16
17impl SampleRange {
18    /// `[first_sample, first_sample + samples)`.
19    #[must_use]
20    pub const fn new(first_sample: u32, samples: u32) -> Self {
21        Self {
22            first_sample,
23            samples,
24        }
25    }
26
27    /// One past the last sample index.
28    #[must_use]
29    pub const fn end(self) -> u32 {
30        self.first_sample + self.samples
31    }
32
33    /// Whether the range holds no samples.
34    #[must_use]
35    pub const fn is_empty(self) -> bool {
36        self.samples == 0
37    }
38
39    /// The untraced tail after a valid prefix of `done` samples, `[first_sample +
40    /// done, end)`. `done` larger than the range yields an empty tail.
41    #[must_use]
42    pub const fn after_prefix(self, done: u32) -> Self {
43        let done = if done > self.samples {
44            self.samples
45        } else {
46            done
47        };
48        Self::new(self.first_sample + done, self.samples - done)
49    }
50}
51
52/// What one [`WorkerLane::render_chunk`] call produced.
53///
54/// `done` is always a PREFIX of the assigned range: `sum` holds exactly the samples
55/// `[first_sample, first_sample + done)`, never a gap and never anything past it. This
56/// is the worker protocol's own guarantee (a stream's delivered `FRAME`s cover the
57/// request from its start), so a lane over a real connection gets it for free.
58#[derive(Debug, Clone, PartialEq)]
59pub struct ChunkResult {
60    /// Per-pixel summed CIE XYZ radiance of the `done` traced samples, `width *
61    /// height` long. May be empty when `done == 0`.
62    pub sum: Vec<Vec3>,
63    /// How many samples of the assigned range were traced: the valid prefix.
64    pub done: u32,
65    /// The lane's own steady-state throughput measurement for this chunk, in
66    /// samples per second, excluding one-time costs such as connection setup and scene
67    /// upload (for a remote lane: the marginal rate between the first and last
68    /// progress report). `None` lets the pool fall back to `done / wall time`.
69    pub rate: Option<f64>,
70    /// Why the chunk ended short, when it did. `None` together with `done` short of
71    /// the range is reported as "ended early".
72    pub error: Option<String>,
73}
74
75impl ChunkResult {
76    /// Every sample of the range was traced.
77    #[must_use]
78    pub const fn complete(sum: Vec<Vec3>, done: u32, rate: Option<f64>) -> Self {
79        Self {
80            sum,
81            done,
82            rate,
83            error: None,
84        }
85    }
86
87    /// Only the prefix `done` was traced before `error` ended the chunk.
88    #[must_use]
89    pub const fn partial(sum: Vec<Vec3>, done: u32, error: String) -> Self {
90        Self {
91            sum,
92            done,
93            rate: None,
94            error: Some(error),
95        }
96    }
97
98    /// Nothing was traced.
99    #[must_use]
100    pub const fn failed(error: String) -> Self {
101        Self {
102            sum: Vec::new(),
103            done: 0,
104            rate: None,
105            error: Some(error),
106        }
107    }
108}
109
110/// One backend contributing samples to an image: a joined remote worker, a TLS
111/// connection to a plain worker, or the local CPU/GPU. The [`crate::LanePool`] does
112/// not care which.
113///
114/// Implementations are shared across the pool's lane threads, hence `Send + Sync`;
115/// one lane is only ever asked for one chunk at a time by the pool.
116pub trait WorkerLane: Send + Sync {
117    /// A short human-readable name for notes ("worker gpu-01", "local CPU").
118    fn name(&self) -> &str;
119
120    /// Traces `range` of `scene` at `scene.width x scene.height` and returns the summed
121    /// radiance of the traced prefix.
122    ///
123    /// Must return promptly (with whatever prefix is done) once `cancel` is raised.
124    /// Must never trace or report samples outside `range`: a result whose `done`
125    /// exceeds `range.samples`, or whose `sum` has the wrong length, is discarded and
126    /// counted as a failed chunk.
127    fn render_chunk(
128        &self,
129        scene: &SceneState,
130        range: SampleRange,
131        cancel: &CancelToken,
132    ) -> ChunkResult;
133}