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