-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
[v6.0] Callback conventions #1036
Comments
I think |
(cc @kisvegabor) |
I'm a little late to this party, but I've read up on the prior discussions referenced above. Apologies in advance for being a dissenting voice, but it feels like there is the potential for a lot of complexity, overhead, and constraints to be added on the LVGL side in order to support automated code generation for bindings and python/micropython in general. When I see suggestions, such as adding macros to reference count micropython objects, requiring fixed struct member order and naming, adding different user_data depending on the callback, it gives me pause. All of these changes restrict the code, and make future development more difficult and constrained. Some will also increase the resource load. I just wanted to raise this because while those that are developing the bindings are understandably vocal in their requirements and @kisvegabor has been extremely accommodating in making changes, there are likely a lot of current C users of LVGL that don't follow these issues and are thus silent in these discussions. There are both less and more resource intensives GUI libraries out there, but LGVL sits in a somewhat unique position, running on MCUs with very limited resources, and is very cleanly structured and documented. We need to remember that the gradual creep of too much complexity and overhead has the potential to push it out of that niche. Apologies again for the intrusion. This is not the place for an extended discussion, nor would I like to start one. Just wish to remind everyone to remain mindful of these possibilities. |
@canardos As a C developer myself, I can definitely understand where you're coming from.
This was actually for Python itself, not Micropython. Micropython doesn't need that complexity.
Can you point me to that suggestion? I don't see why that would be necessary for a high-level language binding. |
Got it, but I think the point remains. Regarding the struct ordering, I don't recall exactly where I saw it mentioned. There have been a lot of discussions and it's possible I'm misremembering function/param naming/param order, but as mentioned, I don't really want to get into a discussion around individual changes, just suggest that we remain mindful of striking the right balance between flexibility and added complexity/overhead. I'll bow out of this discussion here. Keep up the good work all :) |
@canardos I just want to mention that LittlevGL version v5.3 already supports micropython. It required very small modifications to LittlevGL sources in order to add this support, and all these modifications were backward compatible and invisible to the user. I would like to make the discticntion between Python and MicroPython requirements, I think it's important.
I agree with your point, and still I think that micropython fits LittlevGL well. It is targeted to the same audience (MCUs with very limited resources) as LVGL. You are right that there was a lot of discussion about adding conventions and changing the API prototypes, but most of them were related to the Python binding, not micropython. Examples are reference counting and special naming conventions to identify who allocates memory (caller or callee), all of which are not needed for micropython. The only exception, and really the only big feature that is planned for micropython on LVGL v6.0 is the Generic Callbacks support. It would add a lot of flexibility for micropython and we'll make sure that the cost on C is minimal - this is why I opened this issue in the first place! I would like to identify which callbacks are important for micropython. Those which are not, will not change. For those which are important, I hope the change will be minimal, and will not add penalty in resources or performance. Regarding the C style, if anything, it will make it more clear as we would need to use a clear and consistent convention for callback definitions. In general, I think |
@amirgon Thank you for this list. It's extremely useful. I fixed some the issue but some need to be discussed.
I moved
I updated them.
I added user data to
Groups already have user data. What was the problem here?
No user data in the fonts right now. The fonts are being reworked and will take care of adding a user data.
It's a generic prototype compatible with the majority of
User data might be used in case of
These are obsolete. I deleted them.
A global user data should be fine as discussed earlier. |
I can't find
What about
These callbacks are defined like this: typedef struct _lv_group_t
{
...
lv_group_style_mod_func_t style_mod;
lv_group_style_mod_func_t style_mod_edit; Can we name them
That's fine, but if we want to support this, we need to define a new convention that would identify these cases. Addition problem:
void lv_obj_set_event_cb(lv_obj_t * obj, lv_event_cb_t cb); The user_data name is derived from the parameter name So I suggest changing the callback function parameter name on functions that receive function pointers, to be consistent with the corresponding user_data name.
Another option is to change the convention to allow the script find the user_data according to the type name in certain cases, but I think it's less favorable because it's better to keep the callback conventions as simple as possible and not allow multiple "options" that could cause confusion. |
@amirgon |
I hope I fixed all the mentioned things. See 514e2ba |
@kisvegabor Going over the changes, I can see many of these issues are resolved. Thanks!
void (*task)(void *user_data); |
I still need to examine it.
It should be compatible with LittlevGL API functions so we can't add user data here.
I like this approach. With this, the pointer to the created task ( |
I'm not so sure how we can support this on Micropython. Could you explain how it is meant to be used?
A few things were not clear to me, I've added comments on 5243d23. |
animations lv_anim_t a;
a.var = obj;
a.start = 10;
a.end = 100;
a.exec_cb = (lv_anim_exec_cb_t)lv_obj_set_y;
a.path_cb = lv_anim_path_linear;
a.ready_cb = NULL;
a.act_time = 0;
a.time = 200;
a.playback = 0;
a.playback_pause = 0;
a.repeat = 0;
a.repeat_pause = 0;
a.user_data = NULL;
lv_anim_create(&a); However, I don't how it can be used in MicroPython. Shall we improve something here? task
I replayed in the commit. |
@kisvegabor Is there a strict naming convention for those functions that are assigned to If there is, we can support this in Micropython by providing access to the addresses of each of these functions from Micropython.
What we are missing in this case is a list of pointers to functions that can be assigned to a.exec_cb = lv.obj_set.y Where |
Not only So the two cases:
I don't see a good solution right now. Do you have any ideas? |
@kisvegabor Yes, I think this is solvable.
For the built in functions, their names should obey some naming convention. Another option, instead of a naming convention, is using an enum in the same manner we solved the Symbols issue. (an enum that would list all functions that could be used with
We can simply add another inline function like this: typedef void (*lv_anim_custom_exec_cb_t)(lv_anim_t *, int32_t);
inline void lv_anim_set_custom_exec_cb(lv_anim_t *a, lv_anim_custom_exec_cb_t exec_cb)
{
a->var = NULL;
a->exec_cb = (lv_anim_exec_cb_t)exec_cb;
} In this case, the script will identify What do you think? Another problem
|
Based on your idea with the inline callback we can add functions for all parameters of lv_anim_init(&a); //Set default values
lv_anim_set_var(&a, var);
lv_anim_set_values(&a, start_val, end_val);
lv_anim_set_time(&a, duration, delay);
lv_anim_set_exec_cb(&a, exec_cb);
lv_anim_set_path_cb(&a, path_cb);
lv_anim_set_ready_cb(&a, ready_cb);
lv_anim_set_playback(&a, enable, wait_time);
lv_anim_set_repeat(&a, enable, wait_time);
lv_anim_set_user_data(&a, user_data; Would it make things easier? lv_obj_animate
|
Would they all be
I agree. |
@kisvegabor It makes sense, although for the binding I only need To solve the issue we need to handle the
Any of these solutions is good for me.
@embeddedt they can be static inline functions. The script parses them the same way as any other function. (The problem is only with constructs that are consumed by the preprocessor, like defines and comments)
@embeddedt Do you agree to @kisvegabor's second solution, to remove the whole |
It's fine to remove |
In summary, I will add these
Using the |
@kisvegabor No. If we only have user_data, we can only use custom functions, but not assign a function directly. That's because for function assignment we need to expose the C function pointer in micropython instead of performing an actual call. That's the distinction I made above between If we don't provide a built-in function support, the user would always have to define a custom function to delegate the call. That's why I suggested that we have a naming convention for built-in functions - to support assigning them directly to exec_cb (and path_cb) instead of delegating them. Example of doing the same thing with/without delegation:
a.exec_cb = lv.obj_set.y
def my_cb(obj, new_y):
obj.set_y(new_y)
a.exec_cb = my_cb Another option is to provide a way to extract a C function pointer from every micropython wrapper function. It may be possible, but I'm not sure yet (it may require more memory, for example). This approach is an overkill because we really need to be able to assign only a very small number of functions. Most functions we only call, not assign. |
Got it, thank you.
In general the |
@kisvegabor actually on So - in the rare cases where the convention is not followed, for example in case of |
True. However in these cases, e.g. So in summary
What do you think? |
But the binding needs to know if it's ok or not that callback convention is not followed. What about adding the naming convention in the callback prototype itself? Something like: typedef void (*lv_anim_exec_cb_t)(void *bad_cb, lv_anim_value_t); (you can change The advantage of adding the convention in the callback prototype is that the same prototype is used in both cases - when registering a callback with a function call and when just assigning a function pointer on a struct. |
I see. In light of that, it should work:
Do you agree? |
@kisvegabor I wish there was a simpler "single" way to represent this, instead of 3 different ways depending on the situation. |
@kisvegabor I haven't looked at it until now, but what about |
They are not following the convention now but it seems it's easy to fix it. I'll do it soon. How about the previous updates? Are they working? |
@kisvegabor I didn't have the chance to check it yet. It requires some changes in the binding script and I'm working on something else right now. I'll check that later and let you know. |
@kisvegabor We would also need |
Done: 51878e6 |
Thanks! |
Me too! |
@kisvegabor There is still some problems with marking exclusions (as you added in bceb46b)
The others seems ok. |
I fixed them here d84e977 |
@kisvegabor https://github.com/littlevgl/lvgl/blob/ca1f21ad04ed3da251369a199dd6ec619c35c881/src/lv_objx/lv_list.c#L177
The problem is that Micropython binding assumes that every event registration also sets In this case, an event is registered, but the associated The problem is not limited to lv_list.
A possible solution is to set For example, in the lv_obj_set_event_cb(liste, event_cb);
lv_obj_set_user_data(liste, list->user_data); Is this a valid solution? Can we apply this to all the cases above? |
Thinking about this again... there is a problem with my suggestion: So we probably need to think about some other solution to this problem. |
Does really every element of the list is problematic? Most of them seems valid to me. If |
@kisvegabor You are right, it's problematic only when the callback is delegated from the user. So going over the list I think that we have a problem at:
In both these cases, the callback is given as a parameter, and then passed on to But I noticed that in both those cases, the delegating function ( btn_close = list1.add(lv.SYMBOL.CLOSE, "Close", None)
btn_close.set_event_cb(my_close_callback); How about requiring the user to always do it like this? just remove the callback parameter from the delegating function. |
I created the "complex" functions because usually the only thing you want to set for a list element or window control button is its event function. I'd like to keep these functions at least in C. I think it's the same case than However So IMO the solution would be to have two functions but still don't know how to name them. @embeddedt @amirgon |
I think it's better to just remove the callback parameter. It's not that much extra typing to type |
@kisvegabor, I agree with @embeddedt . |
I also don't have a better idea, in lack of good names. So I removed the event parameters. I also reneamed |
@amirgon |
@kisvegabor At the moment, I don't see any additional problems! 😄 |
Awesome! Hopefully, no API breaking issues will turn out. |
As discussed on #557, #785, #316 and #935, callbacks must have a well defined structure that would allow an automatic script (for [micro]python bindings) find the related
user_data
of each callback.A
user_data
is needed when more than one callback instance can be present for a function callback, and we need to correlate the right callback object of a higher level language with the C callback functions that was just fired.A convention that we agreed upon was:
user_data
user_data
will be named according to the callback name (<callback>_user_data
)I checked and this convention solves some cases, but not all cases.
Here is a list of callbacks which currently do not follow this convention:
I would like to ask your opinion:
user_data
for each of these functions? In some cases (like logging) a single global callback is enough so we don't need auser_data
per callback (or we can manage it globally, internally in the binding code)user_data
on each of these callbacks? Can we use the existing convention (and pass a struct as first parameter) or do we need some new convention (like passing theuser_data
itself to the callback)?The text was updated successfully, but these errors were encountered: