use std::sync::mpsc;
use microcad_core::{RenderResolution, hash::ComputedHash};
use microcad_lang_base::RcMut;
use crate::{model::Model, render::*};
pub type ProgressTx = mpsc::Sender<f32>;
#[derive(Default)]
pub struct RenderContext {
pub model_stack: Vec<Model>,
pub cache: Option<RcMut<RenderCache>>,
models_to_render: usize,
models_rendered: usize,
pub progress_tx: Option<ProgressTx>,
}
impl RenderContext {
pub fn new(
model: &Model,
resolution: RenderResolution,
cache: Option<RcMut<RenderCache>>,
progress_tx: Option<ProgressTx>,
) -> RenderResult<Self> {
Ok(Self {
model_stack: vec![model.clone()],
cache,
models_to_render: model.prerender(resolution)?,
models_rendered: 0,
progress_tx,
})
}
pub fn model(&self) -> Model {
self.model_stack.last().expect("A model").clone()
}
pub fn with_model<T>(&mut self, model: Model, f: impl FnOnce(&mut RenderContext) -> T) -> T {
self.model_stack.push(model);
let result = f(self);
self.model_stack.pop();
self.step();
result
}
fn step(&mut self) {
let old_percent = self.progress_in_percent();
self.models_rendered += 1;
let new_percent = self.progress_in_percent();
if (old_percent.floor() as u32) < (new_percent.floor() as u32)
&& let Some(progress_tx) = &mut self.progress_tx
{
progress_tx.send(new_percent).expect("No error");
}
}
pub fn progress_in_percent(&self) -> f32 {
(self.models_rendered as f32 / self.models_to_render as f32) * 100.0
}
pub fn update_2d<T: Into<WithBounds2D<Geometry2D>>>(
&mut self,
f: impl FnOnce(&mut RenderContext, Model) -> RenderResult<T>,
) -> RenderResult<Geometry2DOutput> {
let model = self.model();
let hash = model.computed_hash();
match self.cache.clone() {
Some(cache) => {
{
let mut cache = cache.borrow_mut();
if let Some(GeometryOutput::Geometry2D(geo)) = cache.get(&hash) {
return Ok(geo.clone());
}
}
{
let (geo, cost) = self.call_with_cost(model, f)?;
let geo: Geometry2DOutput = Rc::new(geo.into());
let mut cache = cache.borrow_mut();
cache.insert_with_cost(hash, geo.clone(), cost);
Ok(geo)
}
}
None => Ok(Rc::new(f(self, model)?.into())),
}
}
pub fn update_3d<T: Into<WithBounds3D<Geometry3D>>>(
&mut self,
f: impl FnOnce(&mut RenderContext, Model) -> RenderResult<T>,
) -> RenderResult<Geometry3DOutput> {
let model = self.model();
let hash = model.computed_hash();
match self.cache.clone() {
Some(cache) => {
{
let mut cache = cache.borrow_mut();
if let Some(GeometryOutput::Geometry3D(geo)) = cache.get(&hash) {
return Ok(geo.clone());
}
}
{
let (geo, cost) = self.call_with_cost(model, f)?;
let geo: Geometry3DOutput = Rc::new(geo.into());
let mut cache = cache.borrow_mut();
cache.insert_with_cost(hash, geo.clone(), cost);
Ok(geo)
}
}
None => Ok(Rc::new(f(self, model)?.into())),
}
}
pub fn current_resolution(&self) -> RenderResolution {
self.model().borrow().resolution()
}
fn call_with_cost<T>(
&mut self,
model: Model,
f: impl FnOnce(&mut RenderContext, Model) -> RenderResult<T>,
) -> RenderResult<(T, f64)> {
use std::time::Instant;
let start = Instant::now();
let r = f(self, model)?;
let duration = start.elapsed();
Ok((r, (duration.as_nanos() as f64) / 1_000_000.0))
}
}