lgui-core 0.2.0

Platform-neutral runtime and UI primitives for LGUI
Documentation
use super::{CacheDomain, CacheScope, ImageCachePolicy, MemoryAction, MemoryEvent};

#[cfg_attr(feature = "diagnostics-serde", derive(serde::Serialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MemoryBudget {
    pub cache_soft_bytes: usize,
    pub cache_hard_bytes: usize,
    pub transient_hard_bytes: usize,
    pub persistent_bytes: u64,
    pub max_encoded_resource_bytes: usize,
    pub max_decoded_resource_bytes: usize,
    pub max_parallel_large_tasks: usize,
}

impl MemoryBudget {
    pub const fn new(
        cache_soft_bytes: usize,
        cache_hard_bytes: usize,
        transient_hard_bytes: usize,
        persistent_bytes: u64,
        max_encoded_resource_bytes: usize,
        max_decoded_resource_bytes: usize,
        max_parallel_large_tasks: usize,
    ) -> Self {
        Self {
            cache_soft_bytes,
            cache_hard_bytes,
            transient_hard_bytes,
            persistent_bytes,
            max_encoded_resource_bytes,
            max_decoded_resource_bytes,
            max_parallel_large_tasks,
        }
    }
}

#[cfg_attr(feature = "diagnostics-serde", derive(serde::Serialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MemoryDomainBudgets {
    pub encoded_image_bytes: usize,
    pub decoded_image_bytes: usize,
    pub svg_bytes: usize,
    pub blur_bytes: usize,
    pub text_bytes: usize,
    pub static_layer_bytes: usize,
    pub scroll_raster_bytes: usize,
    pub gdi_bytes: usize,
    pub d2d_bytes: usize,
    pub skia_bytes: usize,
    pub component_output_bytes: usize,
    pub host_scene_bytes: usize,
    pub diagnostics_bytes: usize,
}

impl MemoryDomainBudgets {
    pub const fn budget_for(self, domain: CacheDomain) -> usize {
        match domain {
            CacheDomain::EncodedImage => self.encoded_image_bytes,
            CacheDomain::DecodedImage => self.decoded_image_bytes,
            CacheDomain::Svg => self.svg_bytes,
            CacheDomain::Blur => self.blur_bytes,
            CacheDomain::Text => self.text_bytes,
            CacheDomain::StaticLayer => self.static_layer_bytes,
            CacheDomain::ScrollRaster => self.scroll_raster_bytes,
            CacheDomain::Gdi => self.gdi_bytes,
            CacheDomain::D2d => self.d2d_bytes,
            CacheDomain::Skia => self.skia_bytes,
            CacheDomain::ComponentOutput => self.component_output_bytes,
            CacheDomain::HostScene => self.host_scene_bytes,
            CacheDomain::Diagnostics => self.diagnostics_bytes,
            CacheDomain::Persistent => 0,
        }
    }

    pub const fn unbounded() -> Self {
        Self {
            encoded_image_bytes: usize::MAX,
            decoded_image_bytes: usize::MAX,
            svg_bytes: usize::MAX,
            blur_bytes: usize::MAX,
            text_bytes: usize::MAX,
            static_layer_bytes: usize::MAX,
            scroll_raster_bytes: usize::MAX,
            gdi_bytes: usize::MAX,
            d2d_bytes: usize::MAX,
            skia_bytes: usize::MAX,
            component_output_bytes: usize::MAX,
            host_scene_bytes: usize::MAX,
            diagnostics_bytes: usize::MAX,
        }
    }
}

#[cfg_attr(feature = "diagnostics-serde", derive(serde::Serialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MemoryEventPolicy {
    pub frame_committed: MemoryAction,
    pub window_hidden: MemoryAction,
    pub window_shown: MemoryAction,
    pub all_windows_hidden: MemoryAction,
    pub session_unmounted: MemoryAction,
    pub renderer_device_lost: MemoryAction,
    pub theme_or_scale_changed: MemoryAction,
    pub moderate_pressure: MemoryAction,
    pub critical_pressure: MemoryAction,
    pub explicit_trim: MemoryAction,
    pub application_shutdown: MemoryAction,
}

impl MemoryEventPolicy {
    pub const fn action_for(self, event: MemoryEvent) -> MemoryAction {
        match event {
            MemoryEvent::FrameCommitted => self.frame_committed,
            MemoryEvent::WindowHidden => self.window_hidden,
            MemoryEvent::WindowShown => self.window_shown,
            MemoryEvent::AllWindowsHidden => self.all_windows_hidden,
            MemoryEvent::SessionUnmounted => self.session_unmounted,
            MemoryEvent::RendererDeviceLost => self.renderer_device_lost,
            MemoryEvent::ThemeOrScaleChanged => self.theme_or_scale_changed,
            MemoryEvent::ModeratePressure => self.moderate_pressure,
            MemoryEvent::CriticalPressure => self.critical_pressure,
            MemoryEvent::ExplicitTrim => self.explicit_trim,
            MemoryEvent::ApplicationShutdown => self.application_shutdown,
        }
    }

    pub const fn ignore_all() -> Self {
        Self {
            frame_committed: MemoryAction::None,
            window_hidden: MemoryAction::None,
            window_shown: MemoryAction::None,
            all_windows_hidden: MemoryAction::None,
            session_unmounted: MemoryAction::None,
            renderer_device_lost: MemoryAction::None,
            theme_or_scale_changed: MemoryAction::None,
            moderate_pressure: MemoryAction::None,
            critical_pressure: MemoryAction::None,
            explicit_trim: MemoryAction::None,
            application_shutdown: MemoryAction::None,
        }
    }
}

#[cfg_attr(feature = "diagnostics-serde", derive(serde::Serialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MemoryOptions {
    pub policy_name: &'static str,
    pub budget: MemoryBudget,
    pub domains: MemoryDomainBudgets,
    pub events: MemoryEventPolicy,
    pub default_image_cache_policy: ImageCachePolicy,
    pub persistent_cache_enabled: bool,
}

impl MemoryOptions {
    pub const fn new(
        policy_name: &'static str,
        budget: MemoryBudget,
        domains: MemoryDomainBudgets,
        events: MemoryEventPolicy,
        default_image_cache_policy: ImageCachePolicy,
        persistent_cache_enabled: bool,
    ) -> Self {
        Self {
            policy_name,
            budget,
            domains,
            events,
            default_image_cache_policy,
            persistent_cache_enabled,
        }
    }

    pub const fn unbounded(
        default_image_cache_policy: ImageCachePolicy,
        persistent_cache_enabled: bool,
    ) -> Self {
        Self::new(
            "unbounded",
            MemoryBudget::new(
                usize::MAX,
                usize::MAX,
                usize::MAX,
                u64::MAX,
                usize::MAX,
                usize::MAX,
                usize::MAX,
            ),
            MemoryDomainBudgets::unbounded(),
            MemoryEventPolicy::ignore_all(),
            default_image_cache_policy,
            persistent_cache_enabled,
        )
    }

    pub const fn persistent_cache(mut self, enabled: bool) -> Self {
        self.persistent_cache_enabled = enabled;
        self
    }

    pub const fn persistent_budget(mut self, bytes: u64) -> Self {
        self.budget.persistent_bytes = bytes;
        self
    }

    pub fn validate(self) -> Result<(), &'static str> {
        if self.policy_name.is_empty() {
            return Err("memory policy name must not be empty");
        }
        if self.budget.cache_soft_bytes > self.budget.cache_hard_bytes {
            return Err("memory cache soft budget exceeds hard budget");
        }
        if self.budget.max_parallel_large_tasks == 0 {
            return Err("memory policy must allow at least one large task");
        }
        if self.budget.max_encoded_resource_bytes > self.budget.transient_hard_bytes {
            return Err("encoded resource limit exceeds transient hard budget");
        }
        if self.budget.max_decoded_resource_bytes > self.budget.transient_hard_bytes {
            return Err("decoded resource limit exceeds transient hard budget");
        }
        if self.domains.encoded_image_bytes != 0
            && self.domains.encoded_image_bytes < self.budget.max_encoded_resource_bytes
        {
            return Err("encoded image domain budget is below the resource limit");
        }
        if self.domains.decoded_image_bytes != 0
            && self.domains.decoded_image_bytes < self.budget.max_decoded_resource_bytes
        {
            return Err("decoded image domain budget is below the resource limit");
        }
        if self.default_image_cache_policy == ImageCachePolicy::ApplicationDefault {
            return Err("application default image policy must be concrete");
        }
        Ok(())
    }

    pub const fn event_action(self, event: MemoryEvent) -> MemoryAction {
        self.events.action_for(event)
    }

    pub fn domain_budget(self, domain: CacheDomain) -> usize {
        match domain {
            CacheDomain::Persistent => self.budget.persistent_bytes.min(usize::MAX as u64) as usize,
            domain => self.domains.budget_for(domain),
        }
    }
}

impl MemoryAction {
    pub const fn trim(scope: CacheScope, target_bytes: usize) -> Self {
        Self::Trim {
            scope,
            target_bytes,
        }
    }
}