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