Change lvgl to use driver references instead of copy

When I started to port lvgl I took the naive approach to subclass its C structures in C++ so that my display driver class doesn’t have to replicate information such as resolutions etc, and callbacks from lvgl could directly be remapped into member functions.

But it turns out that lvgl copies the driver structure to its own dynamically allocated owner in lv_disp_drv_register(). The same is true for other driver types. So my question is, what’s the benefit of copying the structure instead of only saving a reference to it? Because there’s plenty of drawbacks:

  1. Waste of RAM: the driver structure contents should be constant, which would allow the whole structure to be allocated to flash. Instead heap memory is wasted to copy this information to the driver owner object.
  2. No possibility to append the structures with extra fields: as the callbacks pass the driver structure pointer, it would be needed that additional fields can be added at the end of the structure, in case the same driver is used with multiple instances. With the copy approach this is impossible. Instead the only available option is to enable LV_USE_USER_DATA, and point the user_data field to any additional data the driver needs. Enabling this however magnifies the first problem, as now all lv_obj_t objects also have an additional user_data field, wasting even more RAM.

Minimizing the RAM usage should be of upmost priority for a project that refers to itself as lightweight, so I hope this request is picked up soon.

In the past we’ve found that users let static structures go out of scope, causing all sorts of hidden bugs down the road. However I would agree that it saves RAM to not copy them.

The software shouldn’t be built on the assumptions that the developers who use it are dummies. That should be enforced through good examples and templates, and through the use of assert()s or similar solutions.

:smiley:
Actually, the reason why I’ve decided to copy that struct was really to remove some possible issues - as @embeddedt pointed out.

It can’t be const because lv_disp_drv_init needs to be called on it to set the fields to a default value. Otherwise, you need to set all the fields (even if you are not interested in them) and keep track of new fields in the new versions.

I see, and agree it’s really not practical in C++.

Anyway, it’s possible to make the users to keep the driver static or global.
The advantages and disadvantages are quite equal to me. @embeddedt what do you think?

Nearly all C libraries (as @bku-sue mentioned) assume that the user is competent enough to keep track of object storage themselves, so I would be fine with changing this. My biggest concern is that almost every example showing how to initialize LVGL will now be wrong.

That function call could be replaced by different macros, that provide an initializer list for default values, with different user-supplied parameters for certain fields.

This change will no doubt break all existing driver approaches, so it should be introduced carefully and fully prepared, in a new major release, at the same time updating the code templates. I’d definitely also rename the driver members to driverp or similar, in case some users make use of that field outside of the library.

If @embeddedt also agrees we can make it a pointer in v8 and carefully highlight this change.

The only safe solution I see is to rename enough members/types so that old examples generate a compilation error. That would force the user to take a second look. We can update all the repositories that we control (lvgl, lv_examples, blog, and docs) with the new type name.

I agree. My first idea is adding lv_disp_drv_register_p(&disp_drv) (_p postfix) besides the current lv_disp_drv_register(&disp_drv). It will keep the compatibility but allow the pointer usage as well.

Still we can update the examples to promote the new approach.