microcad_lang/render/
context.rs1use std::sync::mpsc;
7
8use microcad_core::RenderResolution;
9
10use crate::{model::Model, rc::RcMut, render::*};
11
12pub type ProgressTx = mpsc::Sender<f32>;
14
15#[derive(Default)]
19pub struct RenderContext {
20 pub model_stack: Vec<Model>,
22
23 pub cache: Option<RcMut<RenderCache>>,
25
26 models_to_render: usize,
28
29 models_rendered: usize,
31
32 pub progress_tx: Option<ProgressTx>,
34}
35
36impl RenderContext {
37 pub fn new(
39 model: &Model,
40 resolution: RenderResolution,
41 cache: Option<RcMut<RenderCache>>,
42 progress_tx: Option<ProgressTx>,
43 ) -> RenderResult<Self> {
44 Ok(Self {
45 model_stack: vec![model.clone()],
46 cache,
47 models_to_render: model.prerender(resolution)?,
48 models_rendered: 0,
49 progress_tx,
50 })
51 }
52
53 pub fn model(&self) -> Model {
55 self.model_stack.last().expect("A model").clone()
56 }
57
58 pub fn with_model<T>(&mut self, model: Model, f: impl FnOnce(&mut RenderContext) -> T) -> T {
60 self.model_stack.push(model);
61 let result = f(self);
62 self.model_stack.pop();
63
64 self.step();
65
66 result
67 }
68
69 fn step(&mut self) {
71 let old_percent = self.progress_in_percent();
72 self.models_rendered += 1;
73 let new_percent = self.progress_in_percent();
74
75 if (old_percent.floor() as u32) < (new_percent.floor() as u32)
77 && let Some(progress_tx) = &mut self.progress_tx
78 {
79 progress_tx.send(new_percent).expect("No error");
80 }
81 }
82
83 pub fn progress_in_percent(&self) -> f32 {
85 (self.models_rendered as f32 / self.models_to_render as f32) * 100.0
86 }
87
88 pub fn update_2d<T: Into<WithBounds2D<Geometry2D>>>(
90 &mut self,
91 f: impl FnOnce(&mut RenderContext, Model) -> RenderResult<T>,
92 ) -> RenderResult<Geometry2DOutput> {
93 let model = self.model();
94 let hash = model.computed_hash();
95
96 match self.cache.clone() {
97 Some(cache) => {
98 {
99 let mut cache = cache.borrow_mut();
100 if let Some(GeometryOutput::Geometry2D(geo)) = cache.get(&hash) {
101 return Ok(geo.clone());
102 }
103 }
104 {
105 let (geo, cost) = self.call_with_cost(model, f)?;
106 let geo: Geometry2DOutput = Rc::new(geo.into());
107 let mut cache = cache.borrow_mut();
108 cache.insert_with_cost(hash, geo.clone(), cost);
109 Ok(geo)
110 }
111 }
112 None => Ok(Rc::new(f(self, model)?.into())),
113 }
114 }
115
116 pub fn update_3d<T: Into<WithBounds3D<Geometry3D>>>(
118 &mut self,
119 f: impl FnOnce(&mut RenderContext, Model) -> RenderResult<T>,
120 ) -> RenderResult<Geometry3DOutput> {
121 let model = self.model();
122 let hash = model.computed_hash();
123 match self.cache.clone() {
124 Some(cache) => {
125 {
126 let mut cache = cache.borrow_mut();
127 if let Some(GeometryOutput::Geometry3D(geo)) = cache.get(&hash) {
128 return Ok(geo.clone());
129 }
130 }
131 {
132 let (geo, cost) = self.call_with_cost(model, f)?;
133 let geo: Geometry3DOutput = Rc::new(geo.into());
134 let mut cache = cache.borrow_mut();
135 cache.insert_with_cost(hash, geo.clone(), cost);
136 Ok(geo)
137 }
138 }
139 None => Ok(Rc::new(f(self, model)?.into())),
140 }
141 }
142
143 pub fn current_resolution(&self) -> RenderResolution {
145 self.model().borrow().resolution()
146 }
147
148 fn call_with_cost<T>(
150 &mut self,
151 model: Model,
152 f: impl FnOnce(&mut RenderContext, Model) -> RenderResult<T>,
153 ) -> RenderResult<(T, f64)> {
154 use std::time::Instant;
155 let start = Instant::now();
156
157 let r = f(self, model)?;
158
159 let duration = start.elapsed();
160 Ok((r, (duration.as_nanos() as f64) / 1_000_000.0))
161 }
162}