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