Skip to main content

lgui_core/memory/
options.rs

1use super::{CacheDomain, CacheScope, ImageCachePolicy, MemoryAction, MemoryEvent};
2
3#[cfg_attr(feature = "diagnostics-serde", derive(serde::Serialize))]
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub struct MemoryBudget {
6    pub cache_soft_bytes: usize,
7    pub cache_hard_bytes: usize,
8    pub transient_hard_bytes: usize,
9    pub persistent_bytes: u64,
10    pub max_encoded_resource_bytes: usize,
11    pub max_decoded_resource_bytes: usize,
12    pub max_parallel_large_tasks: usize,
13}
14
15impl MemoryBudget {
16    pub const fn new(
17        cache_soft_bytes: usize,
18        cache_hard_bytes: usize,
19        transient_hard_bytes: usize,
20        persistent_bytes: u64,
21        max_encoded_resource_bytes: usize,
22        max_decoded_resource_bytes: usize,
23        max_parallel_large_tasks: usize,
24    ) -> Self {
25        Self {
26            cache_soft_bytes,
27            cache_hard_bytes,
28            transient_hard_bytes,
29            persistent_bytes,
30            max_encoded_resource_bytes,
31            max_decoded_resource_bytes,
32            max_parallel_large_tasks,
33        }
34    }
35}
36
37#[cfg_attr(feature = "diagnostics-serde", derive(serde::Serialize))]
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub struct MemoryDomainBudgets {
40    pub encoded_image_bytes: usize,
41    pub decoded_image_bytes: usize,
42    pub svg_bytes: usize,
43    pub blur_bytes: usize,
44    pub text_bytes: usize,
45    pub static_layer_bytes: usize,
46    pub scroll_raster_bytes: usize,
47    pub gdi_bytes: usize,
48    pub d2d_bytes: usize,
49    pub skia_bytes: usize,
50    pub component_output_bytes: usize,
51    pub host_scene_bytes: usize,
52    pub diagnostics_bytes: usize,
53}
54
55impl MemoryDomainBudgets {
56    pub const fn budget_for(self, domain: CacheDomain) -> usize {
57        match domain {
58            CacheDomain::EncodedImage => self.encoded_image_bytes,
59            CacheDomain::DecodedImage => self.decoded_image_bytes,
60            CacheDomain::Svg => self.svg_bytes,
61            CacheDomain::Blur => self.blur_bytes,
62            CacheDomain::Text => self.text_bytes,
63            CacheDomain::StaticLayer => self.static_layer_bytes,
64            CacheDomain::ScrollRaster => self.scroll_raster_bytes,
65            CacheDomain::Gdi => self.gdi_bytes,
66            CacheDomain::D2d => self.d2d_bytes,
67            CacheDomain::Skia => self.skia_bytes,
68            CacheDomain::ComponentOutput => self.component_output_bytes,
69            CacheDomain::HostScene => self.host_scene_bytes,
70            CacheDomain::Diagnostics => self.diagnostics_bytes,
71            CacheDomain::Persistent => 0,
72        }
73    }
74
75    pub const fn unbounded() -> Self {
76        Self {
77            encoded_image_bytes: usize::MAX,
78            decoded_image_bytes: usize::MAX,
79            svg_bytes: usize::MAX,
80            blur_bytes: usize::MAX,
81            text_bytes: usize::MAX,
82            static_layer_bytes: usize::MAX,
83            scroll_raster_bytes: usize::MAX,
84            gdi_bytes: usize::MAX,
85            d2d_bytes: usize::MAX,
86            skia_bytes: usize::MAX,
87            component_output_bytes: usize::MAX,
88            host_scene_bytes: usize::MAX,
89            diagnostics_bytes: usize::MAX,
90        }
91    }
92}
93
94#[cfg_attr(feature = "diagnostics-serde", derive(serde::Serialize))]
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
96pub struct MemoryEventPolicy {
97    pub frame_committed: MemoryAction,
98    pub window_hidden: MemoryAction,
99    pub window_shown: MemoryAction,
100    pub all_windows_hidden: MemoryAction,
101    pub session_unmounted: MemoryAction,
102    pub renderer_device_lost: MemoryAction,
103    pub theme_or_scale_changed: MemoryAction,
104    pub moderate_pressure: MemoryAction,
105    pub critical_pressure: MemoryAction,
106    pub explicit_trim: MemoryAction,
107    pub application_shutdown: MemoryAction,
108}
109
110impl MemoryEventPolicy {
111    pub const fn action_for(self, event: MemoryEvent) -> MemoryAction {
112        match event {
113            MemoryEvent::FrameCommitted => self.frame_committed,
114            MemoryEvent::WindowHidden => self.window_hidden,
115            MemoryEvent::WindowShown => self.window_shown,
116            MemoryEvent::AllWindowsHidden => self.all_windows_hidden,
117            MemoryEvent::SessionUnmounted => self.session_unmounted,
118            MemoryEvent::RendererDeviceLost => self.renderer_device_lost,
119            MemoryEvent::ThemeOrScaleChanged => self.theme_or_scale_changed,
120            MemoryEvent::ModeratePressure => self.moderate_pressure,
121            MemoryEvent::CriticalPressure => self.critical_pressure,
122            MemoryEvent::ExplicitTrim => self.explicit_trim,
123            MemoryEvent::ApplicationShutdown => self.application_shutdown,
124        }
125    }
126
127    pub const fn ignore_all() -> Self {
128        Self {
129            frame_committed: MemoryAction::None,
130            window_hidden: MemoryAction::None,
131            window_shown: MemoryAction::None,
132            all_windows_hidden: MemoryAction::None,
133            session_unmounted: MemoryAction::None,
134            renderer_device_lost: MemoryAction::None,
135            theme_or_scale_changed: MemoryAction::None,
136            moderate_pressure: MemoryAction::None,
137            critical_pressure: MemoryAction::None,
138            explicit_trim: MemoryAction::None,
139            application_shutdown: MemoryAction::None,
140        }
141    }
142}
143
144#[cfg_attr(feature = "diagnostics-serde", derive(serde::Serialize))]
145#[derive(Clone, Copy, Debug, PartialEq, Eq)]
146pub struct MemoryOptions {
147    pub policy_name: &'static str,
148    pub budget: MemoryBudget,
149    pub domains: MemoryDomainBudgets,
150    pub events: MemoryEventPolicy,
151    pub default_image_cache_policy: ImageCachePolicy,
152    pub persistent_cache_enabled: bool,
153}
154
155impl MemoryOptions {
156    pub const fn new(
157        policy_name: &'static str,
158        budget: MemoryBudget,
159        domains: MemoryDomainBudgets,
160        events: MemoryEventPolicy,
161        default_image_cache_policy: ImageCachePolicy,
162        persistent_cache_enabled: bool,
163    ) -> Self {
164        Self {
165            policy_name,
166            budget,
167            domains,
168            events,
169            default_image_cache_policy,
170            persistent_cache_enabled,
171        }
172    }
173
174    pub const fn unbounded(
175        default_image_cache_policy: ImageCachePolicy,
176        persistent_cache_enabled: bool,
177    ) -> Self {
178        Self::new(
179            "unbounded",
180            MemoryBudget::new(
181                usize::MAX,
182                usize::MAX,
183                usize::MAX,
184                u64::MAX,
185                usize::MAX,
186                usize::MAX,
187                usize::MAX,
188            ),
189            MemoryDomainBudgets::unbounded(),
190            MemoryEventPolicy::ignore_all(),
191            default_image_cache_policy,
192            persistent_cache_enabled,
193        )
194    }
195
196    pub const fn persistent_cache(mut self, enabled: bool) -> Self {
197        self.persistent_cache_enabled = enabled;
198        self
199    }
200
201    pub const fn persistent_budget(mut self, bytes: u64) -> Self {
202        self.budget.persistent_bytes = bytes;
203        self
204    }
205
206    pub fn validate(self) -> Result<(), &'static str> {
207        if self.policy_name.is_empty() {
208            return Err("memory policy name must not be empty");
209        }
210        if self.budget.cache_soft_bytes > self.budget.cache_hard_bytes {
211            return Err("memory cache soft budget exceeds hard budget");
212        }
213        if self.budget.max_parallel_large_tasks == 0 {
214            return Err("memory policy must allow at least one large task");
215        }
216        if self.budget.max_encoded_resource_bytes > self.budget.transient_hard_bytes {
217            return Err("encoded resource limit exceeds transient hard budget");
218        }
219        if self.budget.max_decoded_resource_bytes > self.budget.transient_hard_bytes {
220            return Err("decoded resource limit exceeds transient hard budget");
221        }
222        if self.domains.encoded_image_bytes != 0
223            && self.domains.encoded_image_bytes < self.budget.max_encoded_resource_bytes
224        {
225            return Err("encoded image domain budget is below the resource limit");
226        }
227        if self.domains.decoded_image_bytes != 0
228            && self.domains.decoded_image_bytes < self.budget.max_decoded_resource_bytes
229        {
230            return Err("decoded image domain budget is below the resource limit");
231        }
232        if self.default_image_cache_policy == ImageCachePolicy::ApplicationDefault {
233            return Err("application default image policy must be concrete");
234        }
235        Ok(())
236    }
237
238    pub const fn event_action(self, event: MemoryEvent) -> MemoryAction {
239        self.events.action_for(event)
240    }
241
242    pub fn domain_budget(self, domain: CacheDomain) -> usize {
243        match domain {
244            CacheDomain::Persistent => self.budget.persistent_bytes.min(usize::MAX as u64) as usize,
245            domain => self.domains.budget_for(domain),
246        }
247    }
248}
249
250impl MemoryAction {
251    pub const fn trim(scope: CacheScope, target_bytes: usize) -> Self {
252        Self::Trim {
253            scope,
254            target_bytes,
255        }
256    }
257}