This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

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}