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
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),
})
}
/// Process-unique snapshot identity, shared by clones. An [`crate::Exporter`] renders only
/// its own snapshot.
#[must_use]
pub const fn generation(&self) -> u64 {
self.generation
}
/// Whether any frame may draw text, which the host must prepare
/// ([`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)
}
/// [`Self::evaluate_audio`] into reusable `scratch`.
///
/// # 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)
}
/// The smallest interval holding everything the timeline shows, captions 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. Compositions that cannot
/// draw are reported when a frame shows them.
#[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 missing one.
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
}
}