tweedegolf / sequential-storage

A crate for storing data in flash memory with minimal need for erasing pages
Apache License 2.0
99 stars 10 forks source link

Potential improvements #31

Closed HaoboGu closed 7 months ago

HaoboGu commented 7 months ago

Hi there! I just tried this lib and everything worked well, thanks for the great work! According to my experience, there are two possible improvements which might make the lib better:

  1. support multiple stored types in map: right now each storage range could be used to save only one type that impls StorageItem. I have a relatively complex config struct, now I have to save the entire struct everytime, which is not so flash efficient. If multiple storage types are allowed, I could separate saving unit to smaller parts, which saves both flash space and life.

  2. add a method to remove all saved items in a range: this is not possible if the keys are not recorded somehow. The only way is to call flash.erase instead.

I have to admit that the suggestions comes from my limited (but successful!) usage. If there is other design consideration, pls point it out:D

diondokter commented 7 months ago

Hey, thanks for your suggestions! Glad to hear it works for you :)

  1. The intended purpose for map is to implement it on e.g. an enum. The discriminant would then be the key. This would be more efficient because every variant can be saved separately. What is possible is to use multiple types (even though the docs tell you not to!). What's important is that all the types have the same key definition and implement the deserialize_key_only function the same. This works because the full deserialize function is only called when we know we've got the correct key we're looking for. But I've not tested this usecase, I just know it should be possible.

Best way is to probably go with the enum approach.

  1. Yeah, you're supposed to erase the flash yourself. I guess I could add a function for that... I'll make an issue for it so I remember later I might want to add that.

Thanks for reaching out!

HaoboGu commented 7 months ago

I tried to use enum, but had some problems that I don't know how to solve. Here is a simple example of my code:

enum MyConfig {
    Config1(u8),
    Config2(u8),
}

impl StorageItem for Configs {
    type Key = u8;
    type Error = ();
    fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
        match self {
            Configs::Config1(e) => buffer[0] = *e,
            Configs::Config2(e) => buffer[0] = *e,
        }
        Ok(1)
    }

    fn deserialize_from(buffer: &[u8]) -> Result<Self, Self::Error>
    where
        Self: Sized,
    {
        // >>> How can I know the saved type?
        // Config::Config1 and Config::Config2 have same length
    }
}
diondokter commented 7 months ago

@HaoboGu That's what the key is for. You'll have to encode it into the buffer yourself.

enum MyConfig {
    Config1(u8),
    Config2(u8),
}

impl StorageItem for MyConfig {
    type Key = u8;
    type Error = ();
    fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
        match self {
            MyConfig::Config1(e) => {
                buffer[0] = 0;
                buffer[1] = *e;
            }
            MyConfig::Config2(e) => buffer[0] = {
                buffer[0] = 1;
                buffer[1] = *e;
            }
        }
        Ok(2)
    }

    fn deserialize_from(buffer: &[u8]) -> Result<Self, Self::Error>
    where
        Self: Sized,
    {
        match buffer[0] {
            0 => MyConfig::Config1(buffer[1]),
            1 => MyConfig::Config2(buffer[1]),
        }
    }

    fn key(&self) -> Self::Key {
        match self {
            MyConfig::Config1(e) => 0,
            MyConfig::Config2(e) => 1,
        }
    }
}
HaoboGu commented 7 months ago

yeah, that makes sense. thanks a lot!