sequential-storage 7.2.0

A crate for storing data in flash with minimal erase cycles.
Documentation
use core::{fmt::Debug, num::NonZeroU32, ops::Range};

use embedded_storage_async::nor_flash::NorFlash;

use crate::{
    NorFlashExt, PageState, calculate_page_address, calculate_page_index, item::ItemHeader,
};

pub(crate) trait PagePointersCache: Debug {
    fn first_item_after_erased(&self, page_index: usize) -> Option<u32>;
    fn first_item_after_written(&self, page_index: usize) -> Option<u32>;

    fn notice_item_written<S: NorFlash>(
        &mut self,
        flash_range: Range<u32>,
        item_address: u32,
        item_header: &ItemHeader,
    );
    fn notice_item_erased<S: NorFlash>(
        &mut self,
        flash_range: Range<u32>,
        item_address: u32,
        item_header: &ItemHeader,
    );

    fn notice_page_state(&mut self, page_index: usize, new_state: PageState);
    fn invalidate_cache_state(&mut self);
}

// Use NoneZeroU32 because we never store 0's in here (because of the first page marker)
// and so Option can make use of the niche so we save bytes
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) struct CachedPagePointers<'a> {
    after_erased_pointers: &'a mut [Option<NonZeroU32>],
    after_written_pointers: &'a mut [Option<NonZeroU32>],
}

impl Debug for CachedPagePointers<'_> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "{{ after_erased_pointers: [")?;
        for (i, val) in self.after_erased_pointers.iter().enumerate() {
            if i > 0 {
                write!(f, ", ")?;
            }

            if let Some(val) = val {
                write!(f, "{:?}", val.get())?;
            } else {
                write!(f, "?")?;
            }
        }
        write!(f, "], after_written_pointers: [")?;
        for (i, val) in self.after_written_pointers.iter().enumerate() {
            if i > 0 {
                write!(f, ", ")?;
            }

            if let Some(val) = val {
                write!(f, "{:?}", val.get())?;
            } else {
                write!(f, "?")?;
            }
        }
        write!(f, "] }}")?;

        Ok(())
    }
}

impl<'a> CachedPagePointers<'a> {
    pub const fn new(
        after_erased_pointers: &'a mut [Option<NonZeroU32>],
        after_written_pointers: &'a mut [Option<NonZeroU32>],
    ) -> Self {
        Self {
            after_erased_pointers,
            after_written_pointers,
        }
    }
}

impl PagePointersCache for CachedPagePointers<'_> {
    fn first_item_after_erased(&self, page_index: usize) -> Option<u32> {
        self.after_erased_pointers
            .get(page_index)
            .copied()
            .flatten()
            .map(NonZeroU32::get)
    }

    fn first_item_after_written(&self, page_index: usize) -> Option<u32> {
        self.after_written_pointers
            .get(page_index)
            .copied()
            .flatten()
            .map(NonZeroU32::get)
    }

    fn notice_item_written<S: NorFlash>(
        &mut self,
        flash_range: Range<u32>,
        item_address: u32,
        item_header: &ItemHeader,
    ) {
        let page_index = calculate_page_index::<S>(flash_range, item_address);

        let next_item_address = item_header.next_item_address::<S>(item_address);

        // We only care about the furthest written item, so discard if this is an earlier item
        if let Some(first_item_after_written) = self.first_item_after_written(page_index) {
            if next_item_address <= first_item_after_written {
                return;
            }
        }

        if let Some(after_written_pointer) = self.after_written_pointers.get_mut(page_index) {
            *after_written_pointer = NonZeroU32::new(next_item_address);
        }
    }

    fn notice_item_erased<S: NorFlash>(
        &mut self,
        flash_range: Range<u32>,
        item_address: u32,
        item_header: &ItemHeader,
    ) {
        let page_index = calculate_page_index::<S>(flash_range.clone(), item_address);

        // Either the item we point to or the first item on the page
        let next_unerased_item = self.first_item_after_erased(page_index).unwrap_or_else(|| {
            calculate_page_address::<S>(flash_range, page_index) + S::WORD_SIZE as u32
        });

        if item_address == next_unerased_item {
            if let Some(after_erased_pointer) = self.after_erased_pointers.get_mut(page_index) {
                *after_erased_pointer =
                    NonZeroU32::new(item_header.next_item_address::<S>(item_address));
            }
        }
    }

    fn notice_page_state(&mut self, page_index: usize, new_state: PageState) {
        if new_state.is_open() {
            // This page was erased
            if let Some(after_erased_pointer) = self.after_erased_pointers.get_mut(page_index) {
                *after_erased_pointer = None;
            }
            if let Some(after_written_pointer) = self.after_written_pointers.get_mut(page_index) {
                *after_written_pointer = None;
            }
        }
    }

    fn invalidate_cache_state(&mut self) {
        for pointers in self.after_erased_pointers.iter_mut() {
            *pointers = None;
        }
        for pointers in self.after_written_pointers.iter_mut() {
            *pointers = None;
        }
    }
}

#[derive(Debug, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub(crate) struct UncachedPagePointers;

impl PagePointersCache for UncachedPagePointers {
    fn first_item_after_erased(&self, _page_index: usize) -> Option<u32> {
        None
    }

    fn first_item_after_written(&self, _page_index: usize) -> Option<u32> {
        None
    }

    fn notice_item_written<S: NorFlash>(
        &mut self,
        _flash_range: Range<u32>,
        _item_address: u32,
        _item_header: &ItemHeader,
    ) {
    }

    fn notice_item_erased<S: NorFlash>(
        &mut self,
        _flash_range: Range<u32>,
        _item_address: u32,
        _item_header: &ItemHeader,
    ) {
    }

    fn notice_page_state(&mut self, _page_index: usize, _new_state: PageState) {}

    fn invalidate_cache_state(&mut self) {}
}