1
use std::collections::{HashMap, HashSet};
2

            
3
use easygpu::transform::ScreenSpace;
4
use figures::SizedRect;
5
use tracing::instrument;
6

            
7
use crate::math::{Pixels, Size};
8
use crate::scene::{Element, SceneEvent};
9
use crate::text::font::LoadedFont;
10
use crate::text::prepared::PreparedSpan;
11
use crate::texture::Texture;
12
use crate::{shape, sprite};
13
1
#[derive(Default, Debug)]
14
pub struct Frame {
15
    pub size: Size<u32, ScreenSpace>,
16
    pub textures: HashMap<u64, Texture>,
17
    pub(crate) commands: Vec<FrameCommand>,
18
    pub(crate) fonts: HashMap<u64, LoadedFont>,
19
    pub(crate) pending_font_updates: Vec<FontUpdate>,
20
    receiver: FrameReceiver,
21
}
22

            
23
1
#[derive(Default, Debug)]
24
struct FrameReceiver {
25
    size: Size<u32, ScreenSpace>,
26
    elements: Vec<Element>,
27
}
28

            
29
impl FrameReceiver {
30
1
    pub fn get_latest_frame(
31
1
        &mut self,
32
1
        receiver: &flume::Receiver<SceneEvent>,
33
1
    ) -> Option<Vec<Element>> {
34
        // Receive a frame, blocking until we get an EndFrame
35
        loop {
36
5
            let evt = receiver.recv().ok()?;
37
5
            if self.process_scene_event(evt) {
38
                // New frame
39
1
                break;
40
4
            }
41
        }
42
1
        let mut latest_frame = std::mem::take(&mut self.elements);
43
        // Receive any pending events in a non-blocking fashion. We only want to render
44
        // the frame we have the most recent information for
45
1
        while let Ok(evt) = receiver.try_recv() {
46
            if self.process_scene_event(evt) {
47
                // New frame
48
                latest_frame = std::mem::take(&mut self.elements);
49
            }
50
        }
51
1
        Some(latest_frame)
52
1
    }
53

            
54
5
    fn process_scene_event(&mut self, event: SceneEvent) -> bool {
55
5
        match event {
56
1
            SceneEvent::BeginFrame { size } => {
57
1
                self.size = size.cast_unit();
58
1
                false
59
            }
60
3
            SceneEvent::Render(element) => {
61
3
                self.elements.push(element);
62
3
                false
63
            }
64

            
65
1
            SceneEvent::EndFrame => true,
66
        }
67
5
    }
68
}
69

            
70
#[derive(Debug)]
71
pub struct FontUpdate {
72
    pub font_id: u64,
73
    pub rect: rusttype::Rect<u32>,
74
    pub data: Vec<u8>,
75
}
76

            
77
enum FrameBatch {
78
    Sprite(sprite::Batch),
79
    Shape(shape::Batch),
80
}
81

            
82
impl FrameBatch {
83
2
    const fn is_shape(&self) -> bool {
84
2
        matches!(self, Self::Shape(_))
85
2
    }
86

            
87
    const fn is_sprite(&self) -> bool {
88
        !self.is_shape()
89
    }
90

            
91
    const fn sprite_batch(&self) -> Option<&'_ sprite::Batch> {
92
        if let FrameBatch::Sprite(batch) = self {
93
            Some(batch)
94
        } else {
95
            None
96
        }
97
    }
98

            
99
    // fn shape_batch(&self) -> Option<&'_ shape::Batch> {
100
    //     if let FrameBatch::Shape(batch) = self {
101
    //         Some(batch)
102
    //     } else {
103
    //         None
104
    //     }
105
    // }
106

            
107
    fn sprite_batch_mut(&mut self) -> Option<&'_ mut sprite::Batch> {
108
        if let FrameBatch::Sprite(batch) = self {
109
            Some(batch)
110
        } else {
111
            None
112
        }
113
    }
114

            
115
    fn shape_batch_mut(&mut self) -> Option<&'_ mut shape::Batch> {
116
3
        if let FrameBatch::Shape(batch) = self {
117
3
            Some(batch)
118
        } else {
119
            None
120
        }
121
3
    }
122
}
123

            
124
impl Frame {
125
1
    #[instrument(name = "Frame::update", level = "trace", skip(self, event_receiver))]
126
    pub fn update(&mut self, event_receiver: &flume::Receiver<SceneEvent>) -> bool {
127
        let elements = match self.receiver.get_latest_frame(event_receiver) {
128
            Some(elements) => elements,
129
            None => return false,
130
        };
131
        self.size = self.receiver.size;
132
        self.commands.clear();
133

            
134
        self.cache_glyphs(&elements);
135

            
136
        let mut referenced_texture_ids = HashSet::new();
137

            
138
        let mut current_texture_id: Option<u64> = None;
139
        let mut current_batch: Option<FrameBatch> = None;
140
        for element in &elements {
141
            match element {
142
                Element::Sprite {
143
                    sprite: sprite_handle,
144
                    clip,
145
                } => {
146
                    let sprite = sprite_handle.data.clone();
147
                    let texture = &sprite.source.texture;
148

            
149
                    if current_texture_id.is_none()
150
                        || current_texture_id.as_ref().unwrap() != &texture.id()
151
                        || current_batch.is_none()
152
                        || !current_batch.as_ref().unwrap().is_sprite()
153
                        || current_batch
154
                            .as_ref()
155
                            .unwrap()
156
                            .sprite_batch()
157
                            .unwrap()
158
                            .clipping_rect
159
                            != *clip
160
                    {
161
                        self.commit_batch(current_batch);
162
                        current_texture_id = Some(texture.id());
163
                        referenced_texture_ids.insert(texture.id());
164

            
165
                        // Load the texture if needed
166
                        #[allow(clippy::map_entry)]
167
                        if !self.textures.contains_key(&texture.id()) {
168
                            self.textures
169
                                .insert(texture.id(), sprite.source.texture.clone());
170
                            self.commands
171
                                .push(FrameCommand::LoadTexture(sprite.source.texture.clone()));
172
                        }
173

            
174
                        current_batch = Some(FrameBatch::Sprite(sprite::Batch::new(
175
                            texture.id(),
176
                            texture.size(),
177
                            *clip,
178
                        )));
179
                    }
180

            
181
                    let current_batch = current_batch.as_mut().unwrap().sprite_batch_mut().unwrap();
182
                    current_batch.sprites.push(sprite_handle.clone());
183
                }
184
                Element::Text { span, clip } => {
185
                    current_batch = self.commit_batch(current_batch);
186
                    self.commands.push(FrameCommand::DrawText {
187
                        text: span.clone(),
188
                        clip: *clip,
189
                    });
190
                }
191
                Element::Shape(shape) => {
192
                    if current_batch.is_some() && !current_batch.as_ref().unwrap().is_shape() {
193
                        current_batch = self.commit_batch(current_batch);
194
                    }
195

            
196
                    if current_batch.is_none() {
197
                        current_batch = Some(FrameBatch::Shape(shape::Batch::default()));
198
                    }
199

            
200
                    let current_batch = current_batch.as_mut().unwrap().shape_batch_mut().unwrap();
201
                    current_batch.add(shape.clone());
202
                }
203
            }
204
        }
205

            
206
        self.commit_batch(current_batch);
207

            
208
        let dead_texture_ids = self
209
            .textures
210
            .keys()
211
            .filter(|id| !referenced_texture_ids.contains(id))
212
            .copied()
213
            .collect::<Vec<_>>();
214
        for id in dead_texture_ids {
215
            self.textures.remove(&id);
216
        }
217

            
218
        true
219
    }
220

            
221
    fn commit_batch(&mut self, batch: Option<FrameBatch>) -> Option<FrameBatch> {
222
1
        if let Some(batch) = batch {
223
1
            match batch {
224
                FrameBatch::Sprite(batch) => self.commands.push(FrameCommand::DrawBatch(batch)),
225
1
                FrameBatch::Shape(batch) => self.commands.push(FrameCommand::DrawShapes(batch)),
226
            }
227
        }
228

            
229
1
        None
230
1
    }
231

            
232
1
    fn cache_glyphs(&mut self, elements: &[Element]) {
233
1
        let mut referenced_fonts = HashSet::new();
234
3
        for text in elements.iter().filter_map(|e| match &e {
235
            Element::Text { span, .. } => Some(span),
236
3
            _ => None,
237
3
        }) {
238
            referenced_fonts.insert(text.font.id());
239

            
240
            for glyph_info in &text.glyphs {
241
                let loaded_font = self
242
                    .fonts
243
                    .entry(text.font.id())
244
                    .or_insert_with(|| LoadedFont::new(&text.font));
245
                loaded_font.cache.queue_glyph(0, glyph_info.glyph.clone());
246
            }
247
        }
248

            
249
1
        let fonts_to_remove = self
250
1
            .fonts
251
1
            .keys()
252
1
            .filter(|id| !referenced_fonts.contains(id))
253
1
            .copied()
254
1
            .collect::<Vec<_>>();
255
1
        for id in fonts_to_remove {
256
            self.fonts.remove(&id);
257
        }
258

            
259
1
        let mut updates = Vec::new();
260
1
        for font in self.fonts.values_mut() {
261
            let font_id = font.font.id();
262
            font.cache
263
                .cache_queued(|rect, data| {
264
                    updates.push(FontUpdate {
265
                        font_id,
266
                        rect,
267
                        data: data.to_vec(),
268
                    });
269
                })
270
                .expect("Error caching font"); // TODO Change this to a graceful
271
                                               // failure that
272
                                               // spams the console but doesn't
273
                                               // crash
274
        }
275
1
        self.pending_font_updates.extend(updates);
276
1
    }
277
}
278

            
279
#[derive(Debug)]
280
pub enum FrameCommand {
281
    LoadTexture(Texture),
282
    DrawBatch(sprite::Batch),
283
    DrawShapes(shape::Batch),
284
    DrawText {
285
        text: PreparedSpan,
286
        clip: Option<SizedRect<u32, Pixels>>,
287
    },
288
}