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