lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]



On Apr 15, 2009, at 8:36 AM, Mike Pall wrote:

James Snyder wrote:
Good point. What about situations where you have control over what the coroutine consists of. One model we're thinking of is using this to deal
with hardware events (button presses, changes in ADC signal level,
etc..).

One of the lessons learned from GUI frameworks is that controllers
should be reactive and not active.

It turns out that an active controller (using a coroutine or
thread) cannot adequately represent the inherent complexity in UI
handling. It's tempting to use a coroutine/thread for this, but in
general you have many inputs that interfere with linear control
flow in complex ways. You'll end up with a mess of event-driven
(i.e. yield to scheduler) and polled APIs.

Even a simple use case like "record a signal in regular intervals
until a button is pressed" turns out to be tricky. Implemented
with a coroutine this would probably look like this:

local function recorder_coro(adc, button, rate)
 local t = {}
 for value in adc:sample(rate) do     -- Iterator yields to scheduler.
   t[#t+1] = value
   if button:pressed() then break end -- Polled? Queued event?
 end
end

As you can see, the check for the pressed button doesn't integrate
nicely. A button press is really an event -- you might miss some
events if you're just polling the button state (if the sample rate
is very low). Ok, so let's queue button press events from an
interrupt. But then you get in trouble if multiple consumers want
to receive the same event. And that still doesn't handle the case
of an early abort (the loop is never entered until the end of a
sampling interval).

Ok, so let's teach the scheduler to wait on multiple events, like
"next sample available OR button pressed". But then you need to
find out which event triggered -- this thoroughly messes up the
nice and easy API. And simple boolean scheduling decisions just
don't suffice for the more complex cases. And so on ...

But there's a better way:

A reactive controller is purely event-driven. It can be
implemented Erlang-style as a coroutine with a central event
dispatch loop. But since Lua has decent support for closure-based
or OO-style programming, it's more comfortable to decompose the
controller into invidivual event handlers. Something like:

local function recorder_start(adc, button, rate)
 local t = {}
 adc:onsample(function(value) t[#t+1] = value end)
 button:onpress(function() adc:stop() ... process data ... end)
 adc:start(rate)
end

For a more flexible design, one could borrow the Qt-Style
signals/slots model, e.g.:

local recorder = {}
function recorder:sample(value) self[#self+1] = value end
function recorder:stop() ... process data ... end
function recorder:start(adc, button, rate)
 attach(adc.sample, recorder, "sample")
attach(button.press, adc, "stop") -- adc:stop() detaches receivers, too.
 attach(button.press, recorder, "stop")
 adc:start(rate)
end

All the control logic is now inside the individual controllers,
where it belongs. Writing a scheduler for an event-driven model is
much simpler. A first cut of a trivial scheduler could just poll
all event sources and call the associated handlers. Later it can
be converted into an interrupt-driven design (to save power). No
change to the controllers should be necessary.

I strongly suggest to _first_ collect several typical use cases of
varying complexity. Then write pseudo-code for the controllers
using the different paradigms. Find out which framework best
handles your needs. Only then design the framework and decide on
the implementation details (such as the use of coroutines).


Thanks for these details as well. I certainly didn't give a lot of detail on what I was thinking of in that original email. I wasn't thinking of using a large quantity of coroutines to internally handle multiple types of behavior (collect adc samples until a button is pressed), and was thinking more along the lines of what you suggested in the latter portion of the message, where one attaches or registers functions, closures or coroutines to deal with different events and there's some sort of centralized dispatcher that periodically rolls through event sources and then calls/notifies registered handlers at the beginning of an event loop, and then that dispatcher yields to allow other code to run in the flow of a main program.

Point taken with the design approach. The design should be hashed out and mocked before getting into these lower level details. On appearance I like the Qt signals/slots approach, though I'll have to see whether that would really work.

--
James Snyder
Biomedical Engineering
Northwestern University
jbsnyder@fanplastic.org
http://fanplastic.org/key.txt
ph: (847) 448-0386

Attachment: PGP.sig
Description: This is a digitally signed message part