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    /// Process-unique snapshot identity, shared by clones. An [`crate::Exporter`] renders only
37    /// its own snapshot.
38    #[must_use]
39    pub const fn generation(&self) -> u64 {
40        self.generation
41    }
42
43    /// Whether any frame may draw text, which the host must prepare
44    /// ([`crate::TextTextures::prepare_at`]).
45    #[must_use]
46    pub const fn draws_text(&self) -> bool {
47        self.draws_text
48    }
49
50    /// Evaluates a deterministic frame description.
51    ///
52    /// # Errors
53    /// Returns an error if the immutable index and project disagree.
54    pub fn evaluate(&self, time: MediaTime) -> Result<FrameDesc<'_>, EvalError> {
55        evaluate(&self.project, &self.index, time)
56    }
57
58    /// Returns the host frames needed at a timeline timestamp.
59    ///
60    /// # Errors
61    /// Returns an error if the immutable index and project disagree.
62    pub fn frame_needs(&self, time: MediaTime) -> Result<Vec<FrameNeed>, EvalError> {
63        frame_needs(&self.project, &self.index, time)
64    }
65
66    /// Evaluates every audio clip overlapping a half-open timeline span.
67    ///
68    /// # Errors
69    /// Returns an error if the immutable index and project disagree.
70    pub fn evaluate_audio(&self, range: TimeRange) -> Result<AudioSpanDesc<'_>, EvalError> {
71        evaluate_audio(&self.project, &self.index, range)
72    }
73
74    /// [`Self::evaluate_audio`] into reusable `scratch`.
75    ///
76    /// # Errors
77    /// Returns an error if the immutable index and project disagree.
78    pub fn evaluate_audio_in<'s>(
79        &self,
80        range: TimeRange,
81        scratch: &'s mut AudioSpanScratch,
82    ) -> Result<AudioSpan<'_, 's>, EvalError> {
83        evaluate_audio_in(&self.project, &self.index, range, scratch)
84    }
85
86    /// The smallest interval holding everything the timeline shows, captions included
87    /// ([`alpha2_model::Timeline::extent`]).
88    #[must_use]
89    pub fn timeline_range(&self) -> Option<TimeRange> {
90        self.project.timeline.extent()
91    }
92
93    /// Every effect the project can draw, by id and version, each once. Compositions that cannot
94    /// draw are reported when a frame shows them.
95    #[must_use]
96    pub fn effects(&self) -> Vec<(&str, u32)> {
97        let mut effects = self
98            .index
99            .effects()
100            .map(|effect| (effect.id.as_str(), effect.version))
101            .collect::<Vec<_>>();
102        effects.sort_unstable();
103        effects.dedup();
104        effects
105    }
106
107    /// Checks that `renderer` can draw every effect the project uses
108    /// ([`Self::effects`]).
109    ///
110    /// # Errors
111    /// Returns [`crate::EngineError::UnregisteredEffect`] naming the first missing one.
112    pub fn check_effects(
113        &self,
114        renderer: &alpha2_render::Renderer,
115    ) -> Result<(), crate::EngineError> {
116        crate::frame::require_effects(renderer, self.effects())
117    }
118
119    /// Returns the immutable project document.
120    #[must_use]
121    pub fn project(&self) -> &Project {
122        &self.project
123    }
124}