Tools & thanks gpeddler 2 wxLua Simple OO in Lua Code notes


Mixed model
Handlers and closures
Coroutines
The Idle handler
Coroutine notes
Window redrawing

Mixed model

gpeddler 2 is rather incoherent as far as the programming model is concerned: for example MainFrame, Route, Solver and Options are designed in object-oriented fashion (ad described in Simple OO in Lua), Place is treated as a "half-object" (functions are kept separate from data) and HelpFrame is just a hastily-written function with associated static data.

This is by choice: I am not a purist; I try to get the most advantages from different approaches, even if it means shamelessly mixing different programming models in the same program. Some choices are of course influenced by the small size of the application.

In fact, I could have avoided using proper objects in MainFrame (there is only one main frame, after all), but I wanted to try out my OO approach in a realistic situation. Maybe I was also influenced by my experience with C++ frameworks... anyway, this allows multiple copies of the main window to be instantiated, even if that would not be significant for this application.

Speaking of objects, instead of designing the ExhaustiveSolver and EvolutionarySolver classes (as subclasses of Solver), I could have created and modified two instances of Solver. That would have been a readability crime, however, and I would also have lost my example of inheritance.

Handlers and closures

Closures are a powerful feature of Lua, but they are not easy to intuitively grasp at first, especially if your experience is from static languages (as mine mostly is). While writing this program, I encountered a situation where they provide a simple and elegant solution to a problem most programmers will recognize: callbacks in an object-oriented environment. It really helped me understand closures, at least at a first level.

This is a line from Solver.Create(), the Solver class constructor in the file solver.lua, formatted into three lines for convenience:

self.window:ConnectEvent(wx.wxEVT_PAINT,
  function(ev) self:Repaint() end
)


It connects an unnamed event function to the given window; in other terms, every time the window sends a wx.wxEVT_PAINT event, the function built in the second line gets called. This function, in turn, calls the Repaint() function that redesign the window's contents. As the unnamed event handler function is called back from wxWindow when required, that's a "callback".

Well, where's the problem? Let's look into this deceptively simple piece of code to see what happens behind the scenes. Consider the function:

self:Repaint()

It is translated by the Lua compiler to:

self.Repaint(self)

Since the Repaint field of all Solver objects refer to the same function Solver.Repaint(), the above line is equivalent to:

Solver.Repaint(self)

This means that the same Repaint() function is called for different objects. How does the program know which object has to be repainted? By looking at the self variable, of course. If we have two different Solver objects:

solver1 -- a Solver
solver2 -- another Solver

we actually need their respective wx.wxEVT_PAINT handlers to make two different function calls:

solver1:Repaint() -- repaint the first Solver's window
solver2:Repaint() -- repaint the second Solver's window

which is a compact and more efficient way of saying:

solver1.Repaint(solver1)
solver2.Repaint(solver2)

The problem here is not the calling of different functions, but the calling of the same function with a different argument: either solver1 or solver2 in our example. The argument, however, is known now (when the unnamed handler function is created) but will need to be used later (when the unnamed handler function will be called by wxWindows): it has to be saved somewhere.

Here we get to closures (at last). When this expression is executed:

function(ev) self:Repaint() end

The current value of the variable self is used in creating the new unnamed function, and it is remembered inside the function. In other words, this piece of code:

self = solver1
function(ev) self:Repaint() end

creates a function that permanently refers to the value that self had at the time of function creation, as if we had written:

function(ev) solver1:Repaint() end

or, more explicitly:

function(ev) solver1.Repaint(solver1) end

which (by the way) in the case of all objects pointing to the same Repaint() function, means:

function(ev) Solver.Repaint(solver1) end

Since in the constructor Solver.Create() the variable self refers to the current object just created in the constructor itself, our trivial-looking code:

self.window:ConnectEvent(wx.wxEVT_PAINT,
  function(ev) self:Repaint() end
)


will create a different unnamed handler function every time, and then pass it to ConnectEvent(). Each of these functions, when called back from wxWindows, will call Solver.Repaint() with a different argument: the current object (i.e. the value of variable self) at the time the function was created, which is exactly what we want.

In short, closures can be used to build a function at runtime, setting the values that some variables (in our case, self) will have inside the function itself.

Coroutines

gpeddler 2 runs on a single thread, yet it manages to run two solving algorithms in parallel (showing their progress in two different windows) and to respond promptly to user interface commands, all apparently at the same time. It does this by using two different techniques: coroutines and idle events.

Modern operating systems offer preemptive multitasking within a single application: there can be many virtually independent flows of control, usually caused "threads".
Lua, being strictly ANSI-C based, cannot do this in a truly portable way (even if there are provisions for using external thread functionalities). It offers instead, starting from version 5.0, cooperative multitasking built into the language itself under the name of "coroutines".

While threads are completely transparent, i.e. they know nothing  about the existance of other threads, coroutines are not: they must cooperate by executing a short piece of their code and then giving other coroutines an opportunity to run.

A function launched as a coroutine must periodically call coroutine.yield(); this has the effect of suspending its execution, remembering its whole status, and returning to the calling function. A subsequent call to coroutine.resume() continues the execution as if nothing had happened in between.

In gpeddler 2, a solver coroutine is started by Solver:StartSolving()  in the file solver.lua:

self.solverCo = coroutine.create(self.Solve)
local r, err = coroutine.resume(self.solverCo, self)

The first line creates the coroutine, the second line starts executing its code. A typical function executing as a coroutine has a structure similar to this one:

-- launched as couroutine
--
function doit()
  while not finished do
    -- perform a bit of work
    coroutine.resume()
  end
end

This is a bit more involved in the ExhaustiveSolver:Solve() and EvolutionarySolver:Solve() functions in gpeddler 2, but nethertheless it boils down to just calling coroutine.yield() more or less periodically.
A good compromise should be made here: if the coroutine function yields too often, much time could be lost in task switching (rather than working); on the other hand, if too much time passes between coroutine.yield() calls, the other coroutines will be starved of CPU resources (and the user interface could become unresponsive).

When a coroutine calls coroutine.yield() to stop working and allow the rest of the system to have some CPU time, somebody else has to later call coroutine.resume() to make the coroutine processing continue, or it would wait forever.
For example, there could be a scheduler, that could be as simple as:

while not finished do
  coroutine.resume(coroutine1)
 
coroutine.resume(coroutine1)
  -- etc.
end

gpeddler 2 uses this approach, but adds another multitasking level to handle interface requirements, as described below.

The Idle handler

In an event-driven environment like wxWindows, it is not a good idea to stop responding to events because the program is currently doing something else: events should be served as soon as possible. Windows contents should be promptly redesigned at the system's request, and user feedback should never give an impression of sluggishness.

Therefore, event handling takes precedence; the program must be always ready to serve events on short notice. In other words, all event handler functions should perform their duty and return fairly quickly.

In gpeddler 2 the rest of the work, which is the actual work from the user's point of view, is done while the system (meaning the wxWindows runtime) is idle, i.e. there are no events to serve.
This is the duty of the MainFrame:OnIdle() function, installed by the initialization function MainFrame:Init() that is called automatically when there are no pending events and mainly acts as coroutine scheduler:

function MainFrame:OnIdle(ev)
  self.solver1:ContinueSolving() -- can be called even if not active
  self.solver2:ContinueSolving()
  -- enable/disable interface elements
  self:UpdateInterface()
  ev:RequestMore() -- call me again if still idle
end

It just does a small piece of work for each of the solvers by calling coroutine.resume() for each of them, updates the interface to the current state of the solvers (e.g. by enabling the "Stop" button if a solver is running) and then tells the system to call again this idle function as soon as there will be no pending events. So, in absence of events, the idle function actually runs as if in a loop; on the other hand, if there are events they are immediately handled.

The ContinueSolving() function just call coroutine.resume() and prints out information about any error that could happen inside the coroutine itself.

Coroutine notes

coroutine.yield() and coroutine.resume() can also be used to pass extra information back and forth. gpeddler 2 uses this opportunity to send a 'stop request' to a solver coroutine. The information is sent as second argument of coroutine.resume() by Solver:StopSolving():

local r, err = coroutine.resume(self.solverCo, true)

and received by ExhaustiveSolver:Solve() or EvolutionarySolver:Solve(), both of which run as coroutines:

stopRequest = coroutine.yield() -- true = termination request

(this is actually a bit more complex in EvolutionarySolver:Solve(), but it is basically the same)

The other coroutine communication channel, i.e. passing back information using a coroutine.yield() argument, is not used in this program. It could be used, for example, to inform the coroutine caller about the current status of the coroutine process.

  Some of the most significant drawbacks of using coroutines (as opposed to threads) are:
On the other hand, there are many advantages:

Window redrawing

A program in which three separate flows of control (the user interface and the two solvers) could access the screen at the same time usually tends to have problems. Fortunately, both wxWindows and Lua offer tools to prevent such problems happening.

wxWindows gives the programmer two different ways to access a window for drawing, available through wxLua as wx.wxPaintDC and wx.wxClientDC.
The first one is used when redrawing is required by the system, i.e. inside an OnPaint event handler, while wx.wxClientDC can be freely used from the program to draw as it whishes. This prevents conflicts between predictable drawing performed by the program (i.e. the solvers) and asyncronous drawing executed in response to a window redraw request.

gpeddler 2, however, ignores this opportunity and uses an even cleaner approach: the solvers never draw anything on their window. They just invalidate the window contents to tell the system that it needs to be redrawn. For example, in Solver:EvaluateRoute() in the file solver.lua:

-- request window redrawing with no background drawing
self.window:Refresh(wx.FALSE)

The system, in turn, will call the window repaint handler Solver:Repaint() installed by the constructor Solver.Create():

self.window:ConnectEvent(wx.wxEVT_PAINT, function(ev) self:Repaint() end)

For this approach to work satisfactorily, there should be no perceivable delay between the invalidating call to Window:Refresh() (inside a solver coroutine) and the system-initiated call to the repaint handler. This is achieved by making sure that the solving coroutines do not spend too much time before yielding CPU control by calling coroutine.yield().

Text labels, on the other hand, in this program are just redrawn as needed, leaving to wxWindows the responsibility to avoid possible redrawing conflicts (presumably by the internal use of wxClientDC).
A possible speed and redrawing improvement could have been the use of a buffer bitmap to have flicker-free text labels, but that would have probably been excessive for such a simple program.


Back to start of page