A clear explanation of the event loop for novices.

500 views
Skip to first unread message

52mid...@gmail.com

unread,
Jan 11, 2017, 1:04:06 PM1/11/17
to fltk.general
I'm new to FLTK and GUI coding, but have a background in embedded code for microcontrollers. Typically, after setting things up, such code enters a MainLoop in which a set of flags are repeatedly tested, and subroutines executed if they're set. At the same time, hardware interrupts occur in the background, with CPU registers pushed and popped on the stack to ensure that processes don't clobber each other.

I'm now facing a new situation in which these simple, clear concepts have been replaced by a welter of possibilities that I don't yet understand. There are awakes, handlers, checks, idles, timeouts and more that are somehow part of what I assume is a more complex version of my familiar MainLoop, but I've no idea how they interact. In addition, multiple "threads" are now possible, I assume because of the multicore CPUs now in use.

I'm writing an FLTK GUI for a "PC Oscilloscope" using a microcontroller connected by a USB serial link. When I set a timeout to call "do_scan", it triggers a stream of serial ADC data that is used to rewrite the GUI oscilloscope screen. However, once the timeout is running, any click of a widget freezes the code, presumably because the widget callback clobbers the asynchronously timed blocking read of the serial port and subsequent screen redraw.

Although the FLTK docn describes each of the above event loop components, it (necessarily) assumes an understanding of how the loop itself operates, something I don't have.

Can anyone recommend other docn or literature that I can use to clarify my understanding of this, and how to structure events so that independent asynchronous activities do not clobber each other?

Sincere thanks for any attempt to answer what I know is a complex question.

Ian MacArthur

unread,
Jan 11, 2017, 5:03:59 PM1/11/17
to fltkg...@googlegroups.com

> On 11 Jan 2017, at 18:04, 52midnight wrote:
>
> I'm new to FLTK and GUI coding, but have a background in embedded code for microcontrollers. Typically, after setting things up, such code enters a MainLoop in which a set of flags are repeatedly tested, and subroutines executed if they're set. At the same time, hardware interrupts occur in the background, with CPU registers pushed and popped on the stack to ensure that processes don't clobber each other.

There’s too much to deal with on one post, but that’s never stopped me trying in the past.

First off, note that a lot of what desktop systems do is “different” from what you might do on an embedded micro, so try not to get to hung up on what you would have done before.

The desktop model is (if you discount threading, anyway!) arguably simpler than the embedded model. But if it is unfamiliar...


>
> I'm now facing a new situation in which these simple, clear concepts have been replaced by a welter of possibilities that I don't yet understand. There are awakes, handlers, checks, idles, timeouts and more that are somehow part of what I assume is a more complex version of my familiar MainLoop, but I've no idea how they interact. In addition, multiple "threads" are now possible, I assume because of the multicore CPUs now in use.

Setting threads aside for now (that’s another story) most GUI code, like fltk, is basically a single thread of execution. There’s only one GPU after all, so its only really safe for one thread to access it at a time (in many cases, not all!)

The fltk model is that we use callbacks to handle events (mouse clicks, timers firing, whatever) and the fltk core, which you don’t see in your code, is trundling away “in the background”, as it were, waiting for these events to happen.
So most of the time, it just goes: “Did anything happen? Oh, the mouse clicked at that coordinate - that's button X, better call its callback. Done that. Did anything else happen? No. OK, do any widgets need redrawn? No. Sleep for a bit. Repeat.”

The crux here is that your callback ought to be short; start, do something, return. All the time a callback is running, the fltk core is blocked so *no* other events or redrawing can happen.

So if you start a callback that sits for a long time reading from a serial port, nothing else will happen.

On the other hand, if you read “a few” bytes from the UART FIFO and stuff them into (say) a circular buffer, and return, then the fltk core can continue and all may be well.
Needless to say, in this model, the read of the UART can not be blocking, or you are dead in the water before you even start.

Once the circular buffer if “full enough” the timer callback can call redraw() on some display widget you have written, and the fltk core will then wake that widget up to consume the data from the buffer and draw your scope trace.

You want to be triggering the callback that empties the UART FIFO “often enough” to prevent it from ever filling, but never spending more than (say) a few 10’s of milliseconds actually in the callback function (any callback that runs more than 100ms or so will cause user-discernible stalling and juddering.)

That’s the basic single-thread model, and is probably adequate for a basic scope.


However, if you want to get fancy, then I’d spawn a worker thread whose only job was to poll the UART FIFO regularly and stuff whatever it reads into the circular buffer. There would be NO GUI elements in the worker thread at all and it would run independently of, and fully asynchronous to, the main GUI thread. In this model, the read of the UART *might* be a blocking read, though even here I’d caution against it.

In the main GUI thread, something (probably a timer) polls the circular buffer, and when it decides there is “enough” data it triggers radraw() on the display widget, which then empties the circular buffer, draws the picture, and returns.

That’s about all there is, really, though the devil is in the details of course.


Did any of that make any sense?


>
> I'm writing an FLTK GUI for a "PC Oscilloscope" using a microcontroller connected by a USB serial link. When I set a timeout to call "do_scan", it triggers a stream of serial ADC data that is used to rewrite the GUI oscilloscope screen. However, once the timeout is running, any click of a widget freezes the code, presumably because the widget callback clobbers the asynchronously timed blocking read of the serial port and subsequent screen redraw.

See notes above...


>
> Although the FLTK docn describes each of the above event loop components, it (necessarily) assumes an understanding of how the loop itself operates, something I don't have.
>
> Can anyone recommend other docn or literature that I can use to clarify my understanding of this, and how to structure events so that independent asynchronous activities do not clobber each other?

I do not have an easy answer for this. Sorry.




Albrecht Schlosser

unread,
Jan 11, 2017, 5:46:46 PM1/11/17
to fltkg...@googlegroups.com
[Note: while I was writing this post Ian's reply arrived and you should
probably read Ian's post before mine. He explained IMO very well how you
can solve your problem on a higher (i.e. application) level than my
description at the end of this post does. My attempt is to answer your
question about the interaction of event processing and GUI behavior on a
more technical (lower) level.]

On 11.01.2017 19:04 52midnight... wrote:
> I'm new to FLTK and GUI coding, but have a background in embedded code
> for microcontrollers. Typically, after setting things up, such code
> enters a MainLoop in which a set of flags are repeatedly tested, and
> subroutines executed if they're set. At the same time, hardware
> interrupts occur in the background, with CPU registers pushed and popped
> on the stack to ensure that processes don't clobber each other.
>
> I'm now facing a new situation in which these simple, clear concepts
> have been replaced by a welter of possibilities that I don't yet
> understand. There are awakes, handlers, checks, idles, timeouts and more
> that are somehow part of what I assume is a more complex version of my
> familiar MainLoop, but I've no idea how they interact.

What you described so far is basically how FLTK works as well. I'll try
to explain more of the details below.

> In addition,
> multiple "threads" are now possible, I assume because of the multicore
> CPUs now in use.

Threads, where mentioned in the FLTK docs, are meant to be what is
generally called threads in programming (aka multithreaded programming),
for instance pthreads, Windows native threads, C++11 threads to name
only a few.

There's one important restriction though in FLTK and likely in all GUI
toolkits that use threads: only the main thread can _safely_ access the
GUI (i.e. open/close windows, change widgets, draw things, etc.). If you
got to the point that you consider using threads you may want to read
the chapter "Advanced FLTK" which is mainly on using threads in FLTK.
http://www.fltk.org/doc-1.3/advanced.html

> I'm writing an FLTK GUI for a "PC Oscilloscope" using a microcontroller
> connected by a USB serial link. When I set a timeout to call "do_scan",
> it triggers a stream of serial ADC data that is used to rewrite the GUI
> oscilloscope screen. However, once the timeout is running, any click of
> a widget freezes the code, presumably because the widget callback
> clobbers the asynchronously timed blocking read of the serial port and
> subsequent screen redraw.

The most important rule with GUI programming (presumably with all GUI
tookits, but definitely with FLTK) is that callbacks must be very short.
Callbacks must never do blocking I/O or anything like this because they
are executed by the main FLTK thread that services all system events and
drawing. Otherwise the GUI may stall and appear unresponsive.

> Although the FLTK docn describes each of the above event loop
> components, it (necessarily) assumes an understanding of how the loop
> itself operates, something I don't have.

Thanks for the hint. I'll check this and try to enhance the docs if this
is really the case (it's very likely that this is indeed a missing piece).

> Can anyone recommend other docn or literature that I can use to clarify
> my understanding of this, and how to structure events so that
> independent asynchronous activities do not clobber each other?

I can't recommend any related docs except the FLTK documentation chapter
"Handling Events", but I assume you read this already.
http://www.fltk.org/doc-1.3/events.html

> Sincere thanks for any attempt to answer what I know is a complex question.

Okay, I'll try to explain at least the general behavior of the FLTK main
loop, first w/o using any threads. FLTK programs that don't do any
blocking calls will usually not need threads. All GUI and system event
processing and drawing is serialized by the FLTK event loop. A basic
FLTK program just executes Fl::run() and terminates after Fl::run()
returns. See test/hello.cxx and the chapter "FLTK Basics":
http://www.fltk.org/doc-1.3/basics.html

Fl::run() basically executes a loop like this (simplified):

while (Fl::first_window()) {
Fl::wait();
Fl::flush()
}

Fl::first_window() tests if any window is shown and thus the loop
terminates when all windows are closed.

Fl::wait() waits for system events (timers, mouse moves, clicks,
keyboard, whatever) and processes these events. Events are dispatched to
widgets by calling their handle() methods [2]. Events like button clicks
trigger the (per widget defined) callback [1] and thus enable the user's
code to be executed. As said before, this code must not block, or the
GUI event handling will stall.

Fl::wait() processes all system events like timers, mouse, keyboard and
window events like closing, resizing etc.. So if you start a timer with
Fl::add_timeout() etc. the timer callback must also be executed in a
short time (as Ian wrote, a few milliseconds). Fl::wait() returns when
there are no pending system events.

Fl::flush() draws all widgets that have changed or the entire window
after a resize() and such.

If you need threads there are ways to communicate with the main FLTK
thread, but you must take care of locking. Please read the above
mentioned chapter (Advanced FLTK) and feel free to ask if you need more
help.

One note to "idles". Idle callbacks are intended to be executed from the
main loop when the main loop is idle (no pending events). However, they
must (like all callbacks) return after a short time so they don't block
the event loop. They should rarely be necessary and are difficult to
implement. Since they must return shortly they have to know how long
they are running, stop and save their context somewhere, return to the
main loop, and be able to continue when they are called again.

HTH.


[1] callbacks are meant to make it easy to interact with a widget w/o
deriving one's own class. The functionality is limited and can be
fine-tuned by Fl_Widget::when(), but all widgets have at most one
callback. Buttons, for instance, have a callback for mouse clicks, input
widgets have a callback for keyboard events and/or focus changes.

[2] The handle() method can be overridden by deriving a class from a
FLTK widget. This is more complicated but also more powerful.

52mid...@gmail.com

unread,
Jan 11, 2017, 8:55:38 PM1/11/17
to fltk.general
Many thanks to both respondents for the detailed and most informative replies. Not only have you clarified several areas of guess-work, but you've also given me confidence that I'm not too far away from familiar territory.

I'm well used to writing short, quick ISRs (Interrupt Service Routines) for microcontrollers, typically setting flags for later "outside" attention, and hoped that something similar might apply here. Looks like this is correct.

I'm still in the early stages of development, and when I wrote the blocking read into the callback I had a strong suspicion that it would soon be a problem. Correct again, but thanks to you both this suspicion is now a certainty. I've just written a "side" version that reads data from an internal table, and it behaves perfectly, so the serial access (and likely the blocking read) is surely the source of the trouble.

The biggest problem with complex new tools is knowing where to start looking for solutions to problems, and I'm now much more confident that I'm on the right track. I figured that callbacks needed to be treated like ISRs, but wasn't sure how to go about adapting my familiar "row of flags" approach to this new environment.


> try not to get to hung up on what you would have done before.

True; however, it's a familiar place to start until I've developed some new skills.


> Did any of that make any sense?

Sure did!


> (it's very likely that this is indeed a missing piece).

As I remarked elsewhere, the FLTK documentation is superb, but can't cater for everyone, especially not raw beginners. One reason why I posted this is because I guessed that the event loop is an ongoing mystery for many without much GUI coding experience, and I reckoned that others might benefit. I certainly have, so thanks again.
Reply all
Reply to author
Forward
0 new messages