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}