1use alpha2_eval::FrameNeed;
2use alpha2_media::SourceId;
3use alpha2_render::{RenderedTexture, Renderer};
4use alpha2_time::MediaTime;
5
6use crate::schedule::frame_times;
7use crate::{
8 EngineError, EngineProject, FrameProvider, FrameRenderer, MissingFramePolicy, QualityProfile,
9};
10
11const MAX_PREFETCH_FRAMES: u32 = 120;
12
13#[derive(Debug)]
15pub struct PlaybackFrame<'a> {
16 pub requested_time: MediaTime,
18 pub presented_time: Option<MediaTime>,
20 pub texture: Option<&'a RenderedTexture>,
22 pub missing_sources: Vec<SourceId>,
24 pub repeated: bool,
26}
27
28#[derive(Debug)]
34pub struct Player {
35 quality: QualityProfile,
36 prefetch_frames: u32,
37 missing_frame_policy: MissingFramePolicy,
38 last_frame: Option<(MediaTime, RenderedTexture)>,
39}
40
41impl Default for Player {
42 fn default() -> Self {
43 Self {
44 quality: QualityProfile::preview(),
45 prefetch_frames: 2,
46 missing_frame_policy: MissingFramePolicy::HoldPrevious,
47 last_frame: None,
48 }
49 }
50}
51
52impl Player {
53 pub fn render_size(
59 &self,
60 project: &EngineProject,
61 ) -> Result<alpha2_render::RenderSize, EngineError> {
62 self.quality.render_size(&project.project().settings)
63 }
64
65 pub fn new(quality: QualityProfile) -> Result<Self, EngineError> {
70 quality.validate()?;
71 Ok(Self {
72 quality,
73 ..Self::default()
74 })
75 }
76
77 #[must_use]
80 pub const fn with_prefetch_frames(mut self, frames: u32) -> Self {
81 self.prefetch_frames = if frames > MAX_PREFETCH_FRAMES {
82 MAX_PREFETCH_FRAMES
83 } else {
84 frames
85 };
86 self
87 }
88
89 #[must_use]
91 pub const fn quality(&self) -> QualityProfile {
92 self.quality
93 }
94
95 #[must_use]
103 pub const fn with_missing_frame_policy(mut self, policy: MissingFramePolicy) -> Self {
104 self.missing_frame_policy = policy;
105 self
106 }
107
108 #[must_use]
110 pub const fn missing_frame_policy(&self) -> MissingFramePolicy {
111 self.missing_frame_policy
112 }
113
114 pub fn prefetch_needs(
119 &self,
120 project: &EngineProject,
121 time: MediaTime,
122 ) -> Result<Vec<FrameNeed>, EngineError> {
123 let rate = project.project().settings.frame_rate;
124 let first = rate
125 .frame_at_or_before(time)
126 .ok_or(EngineError::OutOfRange)?;
127 let mut needs = Vec::new();
128 for (_, frame_time) in
129 frame_times(rate, first, self.prefetch_frames.saturating_add(1), None)?
130 {
131 for need in project.frame_needs(frame_time)? {
132 if !needs.iter().any(|existing: &FrameNeed| {
133 existing.source_id == need.source_id && existing.media_time == need.media_time
134 }) {
135 needs.push(need);
136 }
137 }
138 }
139 Ok(needs)
140 }
141
142 pub fn render_at<'a>(
155 &'a mut self,
156 project: &EngineProject,
157 renderer: &Renderer,
158 frames: &dyn FrameProvider,
159 time: MediaTime,
160 ) -> Result<PlaybackFrame<'a>, EngineError> {
161 let frame = project.evaluate(time)?;
162 crate::frame::check_frame_effects(renderer, &frame)?;
163 let size = self.quality.render_size(&project.project().settings)?;
164 renderer.set_texture_budget_bytes(self.quality.texture_budget_bytes);
165 renderer.set_quality_controls(
166 self.quality.max_filter_samples,
167 self.quality.motion_blur_subframes,
168 );
169 let attempt = renderer.render_frame(size, &frame, frames)?;
170 if let Some(texture) = attempt.texture {
171 self.last_frame = Some((time, texture));
172 let (presented_time, texture) =
173 self.last_frame.as_ref().ok_or(EngineError::OutOfRange)?;
174 renderer.record_frame_outcome(true, false);
175 return Ok(PlaybackFrame {
176 requested_time: time,
177 presented_time: Some(*presented_time),
178 texture: Some(texture),
179 missing_sources: Vec::new(),
180 repeated: false,
181 });
182 }
183
184 let (presented_time, texture) = match self.missing_frame_policy {
185 MissingFramePolicy::HoldPrevious => self
186 .last_frame
187 .as_ref()
188 .map_or((None, None), |(time, texture)| (Some(*time), Some(texture))),
189 MissingFramePolicy::Wait => (None, None),
192 };
193 renderer.record_frame_outcome(texture.is_some(), texture.is_some());
194 Ok(PlaybackFrame {
195 requested_time: time,
196 presented_time,
197 texture,
198 missing_sources: attempt.report.missing_sources,
199 repeated: texture.is_some(),
200 })
201 }
202
203 pub fn reset(&mut self) {
205 self.last_frame = None;
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn prefetch_configuration_is_bounded() {
215 let player = Player::default().with_prefetch_frames(u32::MAX);
216 assert_eq!(player.prefetch_frames, MAX_PREFETCH_FRAMES);
217 }
218
219 #[test]
220 fn hold_previous_is_the_default_missing_frame_policy() {
221 assert_eq!(
222 Player::default().missing_frame_policy(),
223 MissingFramePolicy::HoldPrevious
224 );
225 }
226
227 #[test]
228 fn the_missing_frame_policy_is_configurable() {
229 let player = Player::default().with_missing_frame_policy(MissingFramePolicy::Wait);
230 assert_eq!(player.missing_frame_policy(), MissingFramePolicy::Wait);
231 assert_eq!(
232 player
233 .with_missing_frame_policy(MissingFramePolicy::HoldPrevious)
234 .missing_frame_policy(),
235 MissingFramePolicy::HoldPrevious
236 );
237 }
238
239 #[test]
240 fn configuring_the_policy_preserves_quality_and_prefetch() {
241 let player = Player::new(QualityProfile::export())
242 .expect("valid quality")
243 .with_prefetch_frames(7)
244 .with_missing_frame_policy(MissingFramePolicy::Wait);
245 assert_eq!(player.quality(), QualityProfile::export());
246 assert_eq!(player.prefetch_frames, 7);
247 assert_eq!(player.missing_frame_policy(), MissingFramePolicy::Wait);
248 }
249}