use crate::{Render, Result};
use anyhow::{anyhow, Error};
use frame_sequence::parse_frame_sequence;
use log::{debug, error, info, trace, warn, Level};
use std::thread;
pub fn render(args: Render) -> Result<()> {
let frame_sequence = if let Some(frame_sequence_string) = &args.frames {
parse_frame_sequence(frame_sequence_string)
.map_err(|e| anyhow!("Error in frame sequence expression{e}"))?
} else {
vec![]
};
let cloud = args.cloud;
let error_handler_arg = nsi::callback!(
"errorhandler",
nsi::ErrorCallback::new(|level: Level, error: i32, message: &str| match level {
Level::Error => error!("[{error}] {message}"),
Level::Warn => warn!("[{}] {}", error, message),
Level::Info => info!("[{}] {}", error, message),
Level::Debug => debug!("[{}] {}", error, message),
Level::Trace => trace!("[{}] {}", error, message),
})
);
args.file
.iter()
.flat_map(|file_name| {
if let Some(pos) = file_name.find('@') {
let padding = if let Some(number) = file_name.get(pos + 1..pos + 2) {
number.parse::<usize>().unwrap_or(0)
} else {
0
};
let frame_number_placeholder = if padding != 0 {
file_name.get(pos..pos + 2).unwrap()
} else {
"@"
};
frame_sequence
.iter()
.map(|frame| {
let frame_string = if padding != 0 {
format!("{:0width$}", frame, width = padding)
} else {
format!("{frame}")
};
file_name.replace(frame_number_placeholder, &frame_string)
})
.collect()
} else {
vec![file_name.clone()]
}
})
.filter_map(|file_name| {
let mut ctx_args = vec![error_handler_arg.clone()];
if cloud {
ctx_args.push(nsi::integer!("cloud", true as _));
let file_name = file_name.clone();
let args = args.clone();
Some(thread::spawn(move || {
render_file(&file_name, &args, ctx_args)?;
Ok::<(), Error>(())
}))
} else {
if let Some(ref collective) = args.collective {
ctx_args.push(nsi::string!("collective", collective.as_str()));
}
render_file(&file_name, &args, ctx_args).unwrap();
None
}
})
.for_each(|handle| {
if let Err(error) = handle.join().unwrap() {
error!("{}", error);
}
});
Ok(())
}
pub fn render_file(file_name: &str, args: &Render, ctx_args: nsi::ArgVec) -> Result<()> {
let ctx = nsi::Context::new(Some(&ctx_args)).ok_or(anyhow!("Error creating NSI context."))?;
if args.progress {
ctx.set_attribute(
nsi::node::GLOBAL,
&[nsi::integer!("statistics.progress", 1)],
);
}
if 0 < args.statistics {
match args.statistics {
1 => ctx.set_attribute(
nsi::node::GLOBAL,
&[nsi::integer!("statistics.embedinimage", 1)],
),
_ => ctx.set_attribute(
nsi::node::GLOBAL,
&[nsi::string!("statistics.filename", "")],
),
}
}
if let Some(thread_count) = args.threads {
ctx.set_attribute(
nsi::node::GLOBAL,
&[nsi::integer!("numberofthreads", thread_count as _)],
);
}
evaluate_file(&ctx, file_name, args.dry_run);
debug!("Done evaluating files");
if args.force_render {
ctx.render_control(nsi::Action::Start, None);
}
ctx.render_control(nsi::Action::Wait, None);
Ok(())
}
pub fn evaluate_file(ctx: &nsi::Context, file_name: &str, dry_run: bool) {
info!("Rendering '{}'", file_name);
if dry_run {
return;
}
ctx.evaluate(&[
nsi::string!(
"type",
if file_name.ends_with(".lua") {
"lua"
} else {
"apistream"
}
),
nsi::string!("filename", file_name),
]);
}