Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider moving boilerplate into its own module/crate? #24

Open
embeddedt opened this issue Jun 19, 2020 · 10 comments
Open

Consider moving boilerplate into its own module/crate? #24

embeddedt opened this issue Jun 19, 2020 · 10 comments
Labels
enhancement New feature or request question Further information is requested

Comments

@embeddedt
Copy link
Member

I'm reading the examples to gain an understanding of how this works and I notice that the boilerplate for calling lv_task_handler and lv_tick_inc is duplicated across many of the examples. It might be worth moving this to a common crate where it can be reused.

https://github.com/rafaelcaricio/lvgl-rs/blob/b85226aadac7b9eeed6bebcb34f2008ea803eaa4/examples/gauge.rs#L60-L107

@embeddedt embeddedt changed the title Consider moving boilerplate into it's own module/crate? Consider moving boilerplate into its own module/crate? Jun 19, 2020
@rafaelcaricio
Copy link
Collaborator

@embeddedt True, that is a valid concern. I just think we need to focus on showing how to use the library. If we start designing too much the examples, it will be difficult for people to understand what is going on.

Do you have an example in C that uses LVGL without threads? I was thinking we could remove this Mutex from the examples and thus simplify all the examples.

@embeddedt
Copy link
Member Author

embeddedt commented Jun 19, 2020

None of the example code itself uses threads (as the library is not threaded), however, most of the platform implementations run lv_tick_inc using a thread or an interrupt, to make sure it doesn't block lv_task_handler.

For nearly all the platforms, lv_task_handler gets run in a while(1) loop inside main.

Does Rust have a function that returns the number of milliseconds since system startup? If so, we can use that, and not even bother calling lv_tick_inc. That way, the Rust examples can work identically to the C examples.

I just think we need to focus on showing how to use the library. If we start designing too much the examples, it will be difficult for people to understand what is going on.

Exactly. I know that the code is really just calling lv_task_handler and lv_tick_inc, but there is lots happening in order to do that that I don't really understand yet.

OT: Can you explain why you need to create an arc to call lv_task_handler? Is this some type of syntactic sugar?

let threaded_ui = Arc::new(Mutex::new(ui));
threaded_ui.lock().unwrap().task_handler();

@rafaelcaricio
Copy link
Collaborator

I just updated the one example to remove the Mutex and Arc completely (7466815). I think I wrote that way initially due to my lack of experience using the LVGL library before. 🤦

An Arc in Rust means Atomic Reference Counter, it is a way to share a reference to a variable with multiple threads. Better described at https://doc.rust-lang.org/std/sync/struct.Arc.html

@embeddedt
Copy link
Member Author

That definition of Arc makes a lot more sense. I thought it was this type of arc. 🙂

I've installed Rust on my computer so hopefully I can give this a try at some point and see how it compares to using C directly.

I just have two more suggestions.

@rafaelcaricio
Copy link
Collaborator

I've updated all examples removing the threads. 👍

On the other suggestions, it's complicated...

  • Strings in Rust are a long topic, that's why you will find many string types. From std and from other crates ( https://docs.rs/cstr_core/0.2.0/cstr_core/struct.CStr.html , https://japaric.github.io/heapless/heapless/struct.String.html ). Each have their perks and use cases. In case of embedded Rust, we cannot use anything under std, since it is built upon the OS abstractions. We work here in #[no_std] Rust universe, so we are restricted to the Rust core. I'm thinking of ways to improve this part. Maybe creating our own String type that uses the lv_mem_* mechanism to manipulate the string bytes. All we need is to append \0 character at the end of the string, Rust str don't contain \0 at the end.
  • This is very interesting to know, it is totally possible to expose a Rust function to LVGL with those requirements. BUT, as I mentioned in the previous point, we don't have access to std in embedded Rust. What I can think of here is a way to enable users of lvgl-rs to register themselves a pure Rust function to be called to calculate time. I think it's a valid feature to add. Do you mind opening a new ticket just for this one?

Thanks for your comments! This helps a lot. 😃

@embeddedt
Copy link
Member Author

I'm thinking of ways to improve this part. Maybe creating our own String type that uses the lv_mem_* mechanism to manipulate the string bytes. All we need is to append \0 character at the end of the string, Rust str don't contain \0 at the end.

In my opinion, the best thing to do here is to make sure that a string literal "just works" (TM) without needing any extra wrapper around it in the user's code.

@rafaelcaricio
Copy link
Collaborator

Yes, that is what I meant. Rust "string literals" are static strings, so to append a nul byte at the end I need to, internally, have a sort of dynamic array and copy the static string data from Rust in there and add a \0 at the end so it is a valid C string. Thus the internal CStr type just for that, it wouldn't be exposed by the crate to external users.

@embeddedt
Copy link
Member Author

I see. This is an unfortunate limitation, because it makes passing strings to C quite inefficient. It would be nice if the Rust compiler had a flag that would make it generate the strings with \0 on the end automatically.

@rafaelcaricio
Copy link
Collaborator

Yeah, that doesn't exist. Best thing is to let users pass a instance of cstr_core::CStr so no need to copy as it already contains the nul byte at the end.

@rafaelcaricio
Copy link
Collaborator

rafaelcaricio commented Jun 3, 2021

Now see a better way to deal with this. If the users the the lib enable alloc we can support native Rust strings (&str and String, and others) by generating a method that accepts impl AsRef<str> object. We would have always a method that accepts a ready CStr and another would be available if the user enables alloc feature of LVGL-rs.

fn set_text_cstr(&mut self, text: impl AsRef<cstr_core::CStr>) -> Result<(), Error> { 
    // Calls LVGL FFI 
}

#[cfg(feature = "alloc")]
fn set_text(&mut self, text: impl AsRef<str>) -> Result<(), Error> { 
    // Converts to a CStr here to have the `\0` at the end
    self.set_text_cstr(&CString::new(text.as_ref())?.as_c_str())
}

If the alloc feature is enabled, then the LVGL-rs API would be much nicer to call with strings.

loading_lbl.set_text("Loading...")?;

let text = String::new("Click me!");
button_lbl.set_text(text)?;

@nia-e nia-e added enhancement New feature or request question Further information is requested labels Mar 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants