Skip to main content

alpha2_engine/
snapshot.rs

1use alpha2_eval::{
2    ActiveIndex, AudioSpan, AudioSpanDesc, AudioSpanScratch, EvalError, FrameDesc, FrameNeed,
3    evaluate, evaluate_audio, evaluate_audio_in, frame_needs,
4};
5use alpha2_model::Project;
6use alpha2_time::{MediaTime, TimeRange};
7
8/// The next [`EngineProject::generation`].
9static NEXT_GENERATION: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10
11/// An immutable project snapshot and its reusable evaluation index.
12#[derive(Clone, Debug)]
13pub struct EngineProject {
14    project: Project,
15    index: ActiveIndex,
16    generation: u64,
17    /// [`Project::draws_text`], read once.
18    draws_text: bool,
19}
20
21impl EngineProject {
22    /// Validates a project and builds its active interval index.
23    ///
24    /// # Errors
25    /// Returns an evaluation error when the project is structurally invalid.
26    pub fn new(project: Project) -> Result<Self, EvalError> {
27        let index = ActiveIndex::build(&project)?;
28        Ok(Self {
29            draws_text: project.draws_text(),
30            project,
31            index,
32            generation: NEXT_GENERATION.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
33        })
34    }
35
36    /// Which snapshot this is: unique among the snapshots this process has
37    /// made, and shared by a snapshot's clones, which hold the same project.
38    /// An [`crate::Exporter`] renders only the snapshot it was made for.
39    #[must_use]
40    pub const fn generation(&self) -> u64 {
41        self.generation
42    }
43
44    /// Whether any frame may draw text ([`Project::draws_text`]), so a host
45    /// must prepare it ([`crate::TextTextures::prepare_at`]).
46    #[must_use]
47    pub const fn draws_text(&self) -> bool {
48        self.draws_text
49    }
50
51    /// Evaluates a deterministic frame description.
52    ///
53    /// # Errors
54    /// Returns an error if the immutable index and project disagree.
55    pub fn evaluate(&self, time: MediaTime) -> Result<FrameDesc<'_>, EvalError> {
56        evaluate(&self.project, &self.index, time)
57    }
58
59    /// Returns the host frames needed at a timeline timestamp.
60    ///
61    /// # Errors
62    /// Returns an error if the immutable index and project disagree.
63    pub fn frame_needs(&self, time: MediaTime) -> Result<Vec<FrameNeed>, EvalError> {
64        frame_needs(&self.project, &self.index, time)
65    }
66
67    /// Evaluates every audio clip overlapping a half-open timeline span.
68    ///
69    /// # Errors
70    /// Returns an error if the immutable index and project disagree.
71    pub fn evaluate_audio(&self, range: TimeRange) -> Result<AudioSpanDesc<'_>, EvalError> {
72        evaluate_audio(&self.project, &self.index, range)
73    }
74
75    /// Evaluates the same clips as [`Self::evaluate_audio`] into reusable
76    /// memory, allocating nothing once `scratch` has grown to the span.
77    ///
78    /// # Errors
79    /// Returns an error if the immutable index and project disagree.
80    pub fn evaluate_audio_in<'s>(
81        &self,
82        range: TimeRange,
83        scratch: &'s mut AudioSpanScratch,
84    ) -> Result<AudioSpan<'_, 's>, EvalError> {
85        evaluate_audio_in(&self.project, &self.index, range, scratch)
86    }
87
88    /// Returns the smallest interval holding everything the timeline shows,
89    /// caption cues included ([`alpha2_model::Timeline::extent`]).
90    #[must_use]
91    pub fn timeline_range(&self) -> Option<TimeRange> {
92        self.project.timeline.extent()
93    }
94
95    /// Every effect the project can draw, by id and version, each once in
96    /// order: those of the compositions its clips show or draw as their
97    /// source, and its transitions'. A composition that cannot draw its
98    /// output is reported when a frame shows it.
99    #[must_use]
100    pub fn effects(&self) -> Vec<(&str, u32)> {
101        let mut effects = self
102            .index
103            .effects()
104            .map(|effect| (effect.id.as_str(), effect.version))
105            .collect::<Vec<_>>();
106        effects.sort_unstable();
107        effects.dedup();
108        effects
109    }
110
111    /// Checks that `renderer` can draw every effect the project uses
112    /// ([`Self::effects`]).
113    ///
114    /// # Errors
115    /// Returns [`crate::EngineError::UnregisteredEffect`] naming the first one
116    /// it cannot.
117    pub fn check_effects(
118        &self,
119        renderer: &alpha2_render::Renderer,
120    ) -> Result<(), crate::EngineError> {
121        crate::frame::require_effects(renderer, self.effects())
122    }
123
124    /// Returns the immutable project document.
125    #[must_use]
126    pub fn project(&self) -> &Project {
127        &self.project
128    }
129}