Outcurses: Difference between revisions

Line 234: Line 234:
</pre>
</pre>


In growlight, the tablet-level object is the adapter, detailed at the first level by the block device. In omphalos, it is the interface, detailed at the first level by L2 hosts. Both divide callbacks between a removal event and an update, while neither requires a distinct creation event.
In growlight, the tablet-level object is the adapter, detailed at the first level by the block device. In omphalos, it is the interface, detailed at the first level by L2 hosts. Both divide callbacks between a removal event and an update, while neither requires a distinct creation event. Both are driven by a combination of external events (resulting in callback of their cores, leading to invocation of one of the registered UI callbacks) and UI events, primarily keypresses. In both cases, a thread is devoted to a <tt>wgetch()</tt> loop (the main thread in growlight, and a side thread spawned by <tt>main()</tt> in omphalos). In both cases, almost every key handler and callback lock and unlock a UI-global lock as their first and last actions. Before unlocking, there is almost always a call to <tt>update_panels()</tt> followed by <tt>doupdate()</tt>. Most actions <b>do not</b> result in an entire screen redraw from scratch. Instead, they typically redraw at the tablet level (<tt>werase()</tt> of the tablet's panel followed by full redraw), and move existing tablets around using the panels API.
 
Both UIs keep O(N) structure corresponding to the N tablets, and further O(N) structure for each sublevel of information used. Growlight's <tt>adapterstate</tt> and <tt>blockobj</tt> correspond to (and contain pointers to) the core's <tt>controller</tt> and <tt>device</tt>, respectively; omphalos has <tt>iface_state</tt> and <tt>l2obj</tt> all the way to <tt>l4obj</tt>. Both have their own definition of a <tt>reelbox</tt> which corresponds to the outcurses tablet. In a general design, of course, we cannot assume typed pointers to dynamic program objects, but avoiding a lookup on every UI event remains desirable. We can sacrifice typing with a void pointer, but there is a more fundamental concern of locking.
 
Imagine that there are two major locks in the program: a UI lock and a core lock (the core's locking might be much more complicated, but from our perspective, a simple BKL we can't directly access is sufficient for modeling). UI events are assumed to take the UI lock as their first action, then modify the UI. Core events are assumed to take the core lock as their first action, and might modify the UI. Modifying the UI requires taking the UI lock. If the UI events thus ever need take the core lock (indirectly, via callbacks into the core), there is a potential classic [https://en.wikipedia.org/wiki/Deadlock ABBA deadlock]. Unless the UI intends to copy all data it might ever need present, it is likely to require the core lock.
 
Growlight resolved this by requiring ncurses events to take the core lock before taking the ncurses lock. This requires cooperation from the core and a simplistic locking scheme, and serializes most of the program against the UI, but does resolve the problem. Omphalos appears not to lock the core from within the UI. Instead, the UI carries around significant copied data—enough to establish the number of lines an interface can require at any given time, though not enough to redraw the interface details. At some point, the omphalos UI takes a deep breath and dereferences a pointer into unlocked application-side data. This can be made to work only if the UI copies all metadata necessary for structuring the output, AND can rely on the core neither moving nor deleting
data to which the UI points without taking the UI lock. In omphalos, for instance, receipt of an <tt>RTM_DELLINK</tt> [[netlink]] message indicating that an interface has been deleted is handled thus:
 
<ul>
<li>processing the <tt>RTM_DELLINK</tt> to extract the interface index</li>
<li>acquiring the core lock</li>
<li>finding the interface specified by this index</li>
<li>removing the interface from the lookup structure</li>
<li>acquiring the UI lock (via <tt>interface_removed_callback</tt>)</li>
<li>removing the <tt>iface_state</tt> from its circular list of all ifaces</li>
<li>removing the <tt>reelbox</tt> from its own circular list of visible ifaces</li>
<li>destroying the panel, reelbox, and iface_state</li>
<li>repairing the screen by filling in this hole, possibly making other ifaces visible</li>
<li>releasing the UI lock</li>
<li>destroying the interface</li>
</ul>
 
Not only is this a tremendous amount of (possibly blocking!) work done while holding *multiple* locks, but it is complicated for the application programmer (what goes away when?), difficult to reason about what needs to be copied into the UI's state (and this process does not self-recover if the two ever diverge), wastes memory and time with said copies


==Panel animations==
==Panel animations==