kozan_platform/pipeline/
render_loop.rs1use std::sync::mpsc;
8
9use kozan_core::compositor::Compositor;
10use kozan_primitives::geometry::{Offset, Point};
11
12use crate::context::FrameOutput;
13use crate::event::ViewEvent;
14use crate::renderer::{RenderParams, RenderSurface, RendererError};
15
16pub enum RenderEvent {
21 Commit(FrameOutput),
23 Resize { width: u32, height: u32 },
25 ScaleFactorChanged(f64),
27 Scroll { delta: Offset, point: Point },
30 Shutdown,
32}
33
34pub type OnSurfaceLost = Box<dyn FnOnce() + Send>;
37
38pub(crate) struct RenderLoop<S> {
42 surface: S,
43 compositor: Compositor,
44 view_tx: mpsc::Sender<ViewEvent>,
45 on_surface_lost: Option<OnSurfaceLost>,
46 width: u32,
47 height: u32,
48 scale_factor: f64,
49 queued_scrolls: Vec<(Offset, Point)>,
50}
51
52impl<S: RenderSurface> RenderLoop<S> {
53 pub fn new(
54 surface: S,
55 view_tx: mpsc::Sender<ViewEvent>,
56 on_surface_lost: OnSurfaceLost,
57 width: u32,
58 height: u32,
59 scale_factor: f64,
60 ) -> Self {
61 Self {
62 surface,
63 compositor: Compositor::new(),
64 view_tx,
65 on_surface_lost: Some(on_surface_lost),
66 width,
67 height,
68 scale_factor,
69 queued_scrolls: Vec::new(),
70 }
71 }
72
73 pub fn run(&mut self, rx: mpsc::Receiver<RenderEvent>) {
75 if !self.wait_for_first_commit(&rx) {
76 return;
77 }
78 loop {
79 if !self.drain_events(&rx) {
80 return;
81 }
82 if !self.render_frame() {
83 return;
84 }
85 }
86 }
87
88 fn wait_for_first_commit(&mut self, rx: &mpsc::Receiver<RenderEvent>) -> bool {
89 loop {
90 match rx.recv() {
91 Ok(RenderEvent::Commit(output)) => {
92 self.commit(output);
93 self.replay_queued_scrolls();
94 return true;
95 }
96 Ok(RenderEvent::Shutdown) | Err(_) => return false,
97 Ok(event) => self.handle_event(event),
98 }
99 }
100 }
101
102 fn drain_events(&mut self, rx: &mpsc::Receiver<RenderEvent>) -> bool {
103 loop {
104 match rx.try_recv() {
105 Ok(RenderEvent::Shutdown) => return false,
106 Ok(event) => self.handle_event(event),
107 Err(mpsc::TryRecvError::Empty) => return true,
108 Err(mpsc::TryRecvError::Disconnected) => return false,
109 }
110 }
111 }
112
113 fn render_frame(&mut self) -> bool {
114 if let Some(frame) = self.compositor.produce_frame() {
115 let params = RenderParams {
116 frame: &frame,
117 width: self.width,
118 height: self.height,
119 scale_factor: self.scale_factor,
120 };
121 match self.surface.render(¶ms) {
122 Ok(()) => return true,
123 Err(RendererError::SurfaceLost) => {
124 if let Some(cb) = self.on_surface_lost.take() {
125 cb();
126 }
127 return false;
128 }
129 Err(e) => {
130 eprintln!("kozan: render error: {e}");
131 return true;
132 }
133 }
134 }
135 true
137 }
138
139 fn handle_event(&mut self, event: RenderEvent) {
140 match event {
141 RenderEvent::Commit(output) => self.commit(output),
142 RenderEvent::Resize { width, height } => {
143 self.width = width;
144 self.height = height;
145 self.surface.resize(width, height);
146 }
147 RenderEvent::ScaleFactorChanged(sf) => self.scale_factor = sf,
148 RenderEvent::Scroll { delta, point } => self.apply_scroll(delta, point),
149 RenderEvent::Shutdown => {}
150 }
151 }
152
153 fn commit(&mut self, output: FrameOutput) {
154 self.compositor
155 .commit(output.display_list, output.layer_tree, output.scroll_tree);
156 }
157
158 fn apply_scroll(&mut self, delta: Offset, point: Point) {
159 if !self.compositor.has_content() {
160 self.queued_scrolls.push((delta, point));
161 return;
162 }
163
164 let target = self
167 .compositor
168 .hit_test_scroll_target(point)
169 .or_else(|| self.compositor.scroll_tree().root_scroller());
170
171 if let Some(target) = target {
172 if self.compositor.try_scroll(target, delta) {
173 let _ = self.view_tx.send(ViewEvent::ScrollSync(
174 self.compositor.scroll_offsets().clone(),
175 ));
176 }
177 }
178 }
179
180 fn replay_queued_scrolls(&mut self) {
181 for (delta, point) in std::mem::take(&mut self.queued_scrolls) {
182 self.apply_scroll(delta, point);
183 }
184 }
185}