This repository has no description
0

Configure Feed

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

at main 8.5 kB View raw
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}