This repository has no description
1use super::{Video, context::Context};
2use crate::SVGRenderable;
3use crate::rendering::svg;
4use crate::ui::{Log, Pretty};
5use anyhow::{Result, anyhow};
6use measure_time::debug_time;
7use std::sync::mpsc::SyncSender;
8
9pub type EngineController<C> = dyn Fn(&Context<'_, C>) -> EngineControl;
10
11/// What data is sent to the output by the rendering engine for each rendered frame
12#[derive(Debug)]
13pub enum EngineOutput {
14 Finished,
15 Frame {
16 index: usize,
17 size: (usize, usize),
18 svg: svg::Node,
19 },
20}
21
22pub struct EngineProgression {
23 pub ms: usize,
24 pub timestamp: String,
25 pub scene_name: Option<String>,
26}
27
28impl<'a, C: Default> Context<'a, C> {
29 pub fn engine_progression(&self) -> EngineProgression {
30 EngineProgression {
31 ms: self.ms,
32 timestamp: self.timestamp().pretty(),
33 scene_name: self.current_scene.clone(),
34 }
35 }
36}
37
38impl<C: Default> Video<C> {
39 pub fn render(
40 &self,
41 output: SyncSender<EngineOutput>,
42 controller: &EngineController<C>,
43 ) -> Result<usize> {
44 debug_time!("render");
45
46 let mut context = Context {
47 rendered_frames: 0,
48 ms: 0,
49 current_scene: None,
50 fps: self.fps,
51 syncdata: &self.syncdata,
52 extra: C::default(),
53 inner_hooks: vec![],
54 audiofile: self.audiofile.clone(),
55 duration_override: self.duration_override,
56 scene_started_at_ms: None,
57 bpm: self
58 .syncdata
59 .bpm
60 .expect("No sync source could determine the BPM"),
61 };
62
63 let mut canvas = self.initial_canvas.clone();
64
65 let mut previous_rendered_beat = 0;
66 let mut previous_rendered_frame = 0;
67
68 let pb = self.progress_bars.rendering.clone();
69 pb.set_prefix("Rendering");
70 pb.set_message("");
71 pb.set_position(0);
72 pb.set_length(self.duration_ms() as _);
73
74 for _ in 0..self.total_duration_ms() {
75 context.ms += 1;
76
77 let control = controller(&context);
78
79 let (stop_before, stop_after, skip_rendering, skip_hooks) = (
80 control.stop_rendering_beforehand(),
81 control.stop_rendering_afterwards(),
82 !control.render_this_one(),
83 !control.run_hooks_on_this_one(),
84 );
85
86 if stop_before {
87 break;
88 }
89
90 if skip_hooks {
91 continue;
92 }
93
94 pb.inc(1);
95 pb.set_message(match context.current_scene {
96 Some(ref scene) => {
97 format!("{}: {scene}", context.timestamp())
98 }
99 None => format!("{}", context.timestamp()),
100 });
101
102 if context.marker().starts_with(':') {
103 let marker_text = context.marker();
104 let commandline = marker_text.trim_start_matches(':').to_string();
105
106 for command in &self.commands {
107 if commandline.starts_with(&command.name) {
108 let args = commandline
109 .trim_start_matches(&command.name)
110 .trim()
111 .to_string();
112 (command.action)(args, &mut canvas, &mut context)?;
113 }
114 }
115 }
116
117 // Render later hooks first, so that for example animations that aren't finished yet get overwritten by next frame's hook, if the next frames touches the same object
118 // This is way better to cancel early animations such as fading out an object that appears on every note of a stem, if the next note is too close for the fade-out to finish.
119
120 let mut later_hooks_to_delete: Vec<usize> = vec![];
121
122 for (i, hook) in context.inner_hooks.iter().enumerate() {
123 if (hook.when)(&canvas, &context, previous_rendered_beat) {
124 (hook.render_function)(&mut canvas, context.ms)?;
125 if hook.once {
126 later_hooks_to_delete.push(i);
127 }
128 } else if !hook.once {
129 later_hooks_to_delete.push(i);
130 }
131 }
132
133 for i in later_hooks_to_delete {
134 if i < context.inner_hooks.len() {
135 context.inner_hooks.remove(i);
136 }
137 }
138
139 for hook in &self.hooks {
140 if (hook.when)(
141 &canvas,
142 &context,
143 previous_rendered_beat,
144 previous_rendered_frame,
145 ) {
146 (hook.render_function)(&mut canvas, &mut context).map_err(
147 |e| {
148 anyhow!(
149 "Could not render frame at {}: {e}",
150 context.timestamp().pretty()
151 )
152 },
153 )?;
154 }
155 }
156
157 if context.frame() != previous_rendered_frame {
158 if !skip_rendering {
159 output.send(EngineOutput::Frame {
160 index: context.rendered_frames,
161 size: (canvas.width(), canvas.height()),
162 svg: canvas.render_to_svg(
163 canvas.colormap.clone(),
164 canvas.cell_size,
165 canvas.object_sizes,
166 "",
167 )?,
168 })?;
169 }
170
171 context.rendered_frames += 1;
172
173 previous_rendered_beat = context.beat();
174 previous_rendered_frame = context.frame();
175 }
176
177 if stop_after {
178 break;
179 }
180 }
181
182 output.send(EngineOutput::Finished)?;
183
184 pb.finish();
185 pb.log(
186 "Rendered",
187 &format!(
188 "{} frames in {}",
189 context.rendered_frames,
190 pb.elapsed().pretty()
191 ),
192 );
193 self.progress.remove(&pb);
194
195 Ok(context.rendered_frames)
196 }
197
198 /// Render a single frame at the given frame number. Skip all hooks, expect for `render_ahead`
199 /// frames before the requested one.
200 pub fn render_frame(
201 &self,
202 frame_no: usize,
203 render_ahead: usize,
204 ) -> Result<svg::Node> {
205 debug_time!("render_single_frame");
206 let (tx, rx) = std::sync::mpsc::sync_channel::<EngineOutput>(2);
207
208 let render_ahead_range = frame_no.saturating_sub(render_ahead)..frame_no;
209
210 self.render(tx, &move |ctx| {
211 if ctx.frame() == frame_no {
212 EngineControl::Finish
213 } else if render_ahead_range.contains(&ctx.frame()) {
214 EngineControl::Walk
215 } else {
216 EngineControl::Skip
217 }
218 })?;
219
220 for output in rx.iter() {
221 match output {
222 EngineOutput::Finished => break,
223 EngineOutput::Frame { svg, .. } => return Ok(svg),
224 }
225 }
226
227 return Err(anyhow::format_err!(
228 "Renderer did not output any non-empty frames"
229 ));
230 }
231}
232
233/// Tells the rendering engine what to do with a frame
234pub enum EngineControl {
235 /// Don't run hooks or anything on this frame
236 Skip,
237 /// Skip to the next frame, don't render this one
238 Walk,
239 /// Render this frame as usual
240 Render,
241 /// Render this frame and stop rendering afterwards
242 Finish,
243 /// Don't render this frame and stop rendering
244 Stop,
245}
246
247impl EngineControl {
248 pub fn render_this_one(&self) -> bool {
249 match self {
250 EngineControl::Render | EngineControl::Finish => true,
251 EngineControl::Skip | EngineControl::Walk | EngineControl::Stop => {
252 false
253 }
254 }
255 }
256
257 pub fn run_hooks_on_this_one(&self) -> bool {
258 match self {
259 EngineControl::Skip => false,
260 _ => true,
261 }
262 }
263
264 pub fn stop_rendering_beforehand(&self) -> bool {
265 match self {
266 EngineControl::Stop => true,
267 _ => false,
268 }
269 }
270
271 pub fn stop_rendering_afterwards(&self) -> bool {
272 match self {
273 EngineControl::Finish => true,
274 _ => false,
275 }
276 }
277}