1//! The page's own pictures, decoded off the frame thread at the size they show and let go
2//! under a budget.
3
4use super::{SETTLE, Slot, density, queue};
5use draw::RasterImage;
6use onestore::ExGuid;
7use std::{collections::BTreeMap, sync::Arc, task::Waker, time::Duration};
8use web_time::Instant;
9
10/// Decoded bytes a scene's pictures keep at once. Pictures in view shrink to fit it, and
11/// the rest of the renderer's frame budget stays for template art and the interface.
12pub(super) const BUDGET: u64 = draw::MAX_IMAGE_BYTES / 2;
13
14pub(super) struct Picture {
15 /// The stored picture, which saving keeps as it is.
16 bytes: Arc<[u8]>,
17 /// The stored picture's size in pixels.
18 native: [u32; 2],
19 shown: Option<RasterImage>,
20 /// The one decode under way and the size it makes; replacing it abandons it.
21 pending: Option<(Slot<Option<RasterImage>>, [u32; 2])>,
22 /// The stored bytes did not decode, so the picture paints as a placeholder.
23 failed: bool,
24 /// When the picture was last near the view; the longest unseen are let go first.
25 seen: Instant,
26}
27
28impl Picture {
29 /// Whether both show the same stored bytes.
30 pub(crate) fn same(&self, other: &Self) -> bool {
31 self.bytes == other.bytes
32 }
33
34 /// Reads the size from the picture's header; None when the renderer cannot decode it.
35 pub fn new(bytes: &Arc<[u8]>) -> Option<Self> {
36 Some(Self {
37 native: RasterImage::measure(bytes).ok()?,
38 bytes: Arc::clone(bytes),
39 shown: None,
40 pending: None,
41 failed: false,
42 seen: Instant::now(),
43 })
44 }
45
46 /// The latest raster, once one has landed.
47 pub(crate) fn image(&self) -> Option<&RasterImage> {
48 self.shown.as_ref()
49 }
50
51 pub(crate) fn failed(&self) -> bool {
52 self.failed
53 }
54}
55
56fn bytes([width, height]: [u32; 2]) -> u64 {
57 u64::from(width) * u64::from(height) * 4
58}
59
60/// Takes finished decodes and asks for the pictures `rects` places near `view` at `scale`
61/// device pixels per point; `waker` hears when one lands. Pictures in view come first and
62/// shrink together to fit the budget, the half view around it decodes ahead with what
63/// room is left, and those unseen longest are let go past it. True once every picture in
64/// view shows the raster it asked for.
65pub(super) fn update(
66 pictures: &mut BTreeMap<ExGuid, Picture>,
67 rects: impl IntoIterator<Item = (ExGuid, [f32; 4])>,
68 view: [f32; 4],
69 scale: f32,
70 waker: &Waker,
71) -> bool {
72 let now = Instant::now();
73 for picture in pictures.values_mut() {
74 let landed = picture
75 .pending
76 .as_ref()
77 .and_then(|(slot, _)| slot.lock().unwrap().take());
78 if let Some(image) = landed {
79 picture.failed = image.is_none();
80 picture.shown = image;
81 picture.pending = None;
82 }
83 }
84 let density = density(scale);
85 let [x0, y0, x1, y1] = view;
86 let [ahead_x, ahead_y] = [(x1 - x0) / 2.0, (y1 - y0) / 2.0];
87 let near = [x0 - ahead_x, y0 - ahead_y, x1 + ahead_x, y1 + ahead_y];
88 let overlaps = |rect: [f32; 4], area: [f32; 4]| {
89 rect[0] <= area[2] && rect[2] >= area[0] && rect[1] <= area[3] && rect[3] >= area[1]
90 };
91 let mut wanted: Vec<_> = rects
92 .into_iter()
93 .filter_map(|(id, rect)| {
94 let picture = pictures.get(&id)?;
95 if picture.failed || !overlaps(rect, near) {
96 return None;
97 }
98 let shown = [rect[2] - rect[0], rect[3] - rect[1]];
99 let size = [0, 1]
100 .map(|axis| ((shown[axis] * density).ceil() as u32).clamp(1, picture.native[axis]));
101 Some((id, size, overlaps(rect, view)))
102 })
103 .collect();
104 let visible: u64 = wanted
105 .iter()
106 .filter(|(.., visible)| *visible)
107 .map(|(_, size, _)| bytes(*size))
108 .sum();
109 let shrink = (BUDGET as f64 / visible as f64).sqrt();
110 let mut room = BUDGET.saturating_sub(visible);
111 wanted.retain_mut(|(_, size, visible)| {
112 if *visible && shrink < 1.0 {
113 *size = size.map(|side| ((f64::from(side) * shrink) as u32).max(1));
114 } else if !*visible {
115 if bytes(*size) > room {
116 return false;
117 }
118 room -= bytes(*size);
119 }
120 true
121 });
122 let wanted: BTreeMap<_, _> = wanted
123 .into_iter()
124 .map(|(id, size, visible)| (id, (size, visible)))
125 .collect();
126 let mut settled = true;
127 for (id, picture) in pictures.iter_mut() {
128 let Some(&(size, visible)) = wanted.get(id) else {
129 picture.pending = None;
130 continue;
131 };
132 picture.seen = now;
133 let shown = picture.shown.as_ref().map(RasterImage::size);
134 if shown != Some(size) && picture.pending.as_ref().map(|(_, made)| *made) != Some(size) {
135 let encoded = Arc::clone(&picture.bytes);
136 let start = now
137 + if shown.is_some() {
138 SETTLE
139 } else {
140 Duration::ZERO
141 };
142 let slot = queue(start, waker, move || {
143 RasterImage::decode(&encoded, size).ok()
144 });
145 picture.pending = Some((slot, size));
146 }
147 settled &= !visible || shown == Some(size);
148 }
149 let mut kept: u64 = pictures
150 .values()
151 .filter_map(Picture::image)
152 .map(|image| bytes(image.size()))
153 .sum();
154 if kept > BUDGET {
155 let mut spare: Vec<_> = pictures
156 .iter_mut()
157 .filter(|(id, picture)| {
158 picture.shown.as_ref().is_some_and(|image| {
159 wanted
160 .get(*id)
161 .is_none_or(|(size, _)| image.size() != *size)
162 })
163 })
164 .collect();
165 spare.sort_by_key(|(id, picture)| (wanted.contains_key(*id), picture.seen));
166 for (_, picture) in spare {
167 if kept <= BUDGET {
168 break;
169 }
170 kept -= picture.shown.take().map_or(0, |image| bytes(image.size()));
171 }
172 }
173 settled
174}