Observer in place of PubSub in v9
Introduce the problem
In my project I made an extensive use of LVGL's PubSub facilities. The main benefit was that the publisher and the subscriber didn't have to know about each other, which worked out awesomely in my particular application.
As far as I can tell, Observer is supposed to supplant the above functionality in v9. First, I'd like to know if I got that right - because I'm really unclear on this. If I did get that intention right, is it possible to leave the functionality there? The new Observer pattern implementation seems to be all good; it's just that PubSub is a different pattern, and I'd like to see if it's implementation could be kept.
Proposal
Add Observer functionality while leaving the Pub Sub functionality there as well.
Thank you for the feedback!
Yes, my intention was to replace lv_msg with lv_observer as it's more powerful and flexible.
The main benefit was that the publisher and the subscriber didn't have to know about each other, which worked out awesomely in my particular application.
I think they still need to know each other as the message ID needs to be shared. In case of the observer pattern the message ID could be the subject. What do you think?
Thanks a lot for a fast response!
First, I'd like to acknowledge the fact that for the general type of examples presented in the Observer section on the documentation website the Observer pattern as implemented is more powerful and more convenient comparing with the PubSub as implemented.
Otherwise, my take on what you brought up is as follows:
It's true that there must be some sort of coupling, some glue between the place where a state changes and where other components get to know about that state change. In the PubSub either of these two places need to know about the data bus, queue, whatever you'd like to call it. In the Observer the two need to know about each other.
What this means for my app, just as an example, is this: with the pub sub I can have one header file with an enum of different messages (because they need to be unique). Then to send a sensor input from some peripheral part of the system to some deep GUI element all I do is add this header to both of those files and the infrastucture handles the rest.
With the Observer to accomplish a similar kind of thing I'd need to share subjects themselves through headers, make them global objects. This is undesirable, from my perspective, for 2 reasons:
- More global state;
- You have to declare the variable as
externin the header and then also define it in.cfile.
So, overall, my take is that in your examples, where the observation or pub/subbing happens within a file, the Observer shines. At "longer distances" Pub/Sub shines.
Thanks for sharing your use case.
More global state; You have to declare the variable as extern in the header and then also define it in .c file.
I thought these are something simple and acceptable for anyone. It's interesting to see that if something might be ok for me, it raises concerns in others.
So for what could be an acceptable boilerplate is to define a common enum for the message IDs, right?
FYI, as an instant workaround you can just copy lv_msg.c/h from v8 next to lvgl. They should work minimal changes.
Thank you.
I noted the workaround you offered, thank you.
More global state; You have to declare the variable as extern in the header and then also define it in .c file.
I thought these are something simple and acceptable for anyone. It's interesting to see that if something might be ok for me, it raises concerns in others.
You are right in saying what I described above is doable; and it's not hard to do per se. Experience breeds knowledge saying that in most cases non-constant global state is a potential threat to the architecture and maintainability, more so in the long run. It may turn out all good and fine, or might not - so avoiding it altogether as possible is a safer strategy, in my opinion. There are some decent arguments in this thread.
So for what could be an acceptable boilerplate is to define a common enum for the message IDs, right?
I don't know what others do, but that's what I did. Since the IDs must be unique, enum seems a perfect candidate. In your question I could see some (suspicion?) of my presenting an XY problem, which is good if you think that. In my opinion, looking at the actual root cause of a problem is always good. Otherwise, I am going to offer you check out this article, especially the sections preceding "Structure". I believe it's a very pertinent read, eloquently written, which could possibly give a little better perspective on the two patterns we have been discussing.
We need some feedback on this issue.
Now we mark this as "stale" because there was no activity here for 14 days.
Remove the "stale" label or comment else this will be closed in 7 days.
Have you had a chance to look at any of what I mentioned in the last post?
Sorry for the delay and thank your for the articles. It's always great to see the things you know/feel from experience written in an explicit way.
I understand you concern about the global state issue. It's really not that fortunate is some cases, however in case of UIs it's really handy to set the global state of the app, and the UI will be update accordingly automatically.
Anyway, I really see that a stateless pub-sub approach can be a better fit in some designs. However, it's pretty hard to use with the widgets. And if it doesn't play well with the widgets, probably it shouldn't be part of the UI library.
We had a similar discussion about the state machines. They are good and useful, no doubt, but in a UI library we don't really need them. In other words, people can use any state machine implementation and use LVGL in it, but LVGL shouldn't have a state machine library internally.
Thank you very much for your response.
I definitely see the new Observer plays well with widgets. Now, if you will, could you please briefly explain what didn't play with widgets in PubSub? I am asking specifically because I made it work, and it seemed fine to me, so I am wondering whether I just can't see benefits that are actually there (of using the new Observer).
Thank you.
I haven't got the chance to try Observer now, but previously in NuttX we use a lot of uORB. And it's adopted from PX4.
https://docs.px4.io/main/en/middleware/uorb.html
uORB on NuttX can be used across tasks, even across different physical CPU. It uses a extern global struct, but in an ID way. See below example.
/**
* Declare the uORB metadata for a topic (used by code generators). */
#define ORB_DECLARE(name) extern const struct orb_metadata g_orb_##name
/* Generates a pointer to the uORB metadata structure for a given topic.*/
#define ORB_ID(name) &g_orb_##name
ORB_DECLARE(sensor_accel);
/* For subscribers */
/* Read from fd when new data is published. */
int fd = orb_subscribe(ORB_ID(sensor_accel));
/* For publishers */
/* advertise */
pfd0 = orb_advertise(ORB_ID(sensor_accel), NULL);
/* publish test data */
orb_publish(ORB_ID(sensor_accel), pfd0, &t);
Hope this can help.
@XuNeo I think we have something very similar in LVGL now.
We need some feedback on this issue.
Now we mark this as "stale" because there was no activity here for 14 days.
Remove the "stale" label or comment else this will be closed in 7 days.
So then I am concluding the benefit is that there are functions allowing more directly tie the widgets to the publishers?
I'm sorry, I missed this comment:
Now, if you will, could you please briefly explain what didn't play with widgets in PubSub?
I faced 3 main limitations.
Let's say you subscribe to a message with:
void * lv_msg_subsribe_obj(uint32_t msg_id, lv_obj_t * obj, void * user_data);
After that you need to add an LV_EVENT_MSG_RECEIVED event callback to the widget.
Let's say this a label widget and it depends on 2 messages:
- a measured value (msg1)
- a limit, above it the label should be red (msg2).
The main problem is that in the event_cb you receive all events from all message IDs. In order to distinguish them you need to know the message IDs too. However if it's a component and you already have multiple instances of it, where do you store the 2 message IDs in each label? With the observers you can register a different callback for each subject you are interested in.
For the second problem imagine that you have 2 other message for the label:
- the value
- the unit
You need to know both the value and the unit to set the current text of the label. But if you have received only the unit now, how do you get the value. In case of the subjects you can create a subject group to connect the related subjects.
The third problem is that, when the widget is created you need to know the current value to initialize it. But how do you get by knowing only the message ID? You need to have a custom API to get is somehow. But it's not trivial and well... it's custom. You need to handle it. The subjects store the last value, so have access to it at any time.
Thanks so much. Now I see where you are coming from. Those just weren't things I perceived as a problem in my own design, but I can see how they can be real issues in general. So if the new solution is more embrasive, then that's great. I'll probably slowly migrate over then.
I'm happy that my explanation could shed some light on the underlying issues.