
The problem seems to boil down to this: - write a thread that performs an IO action whenever the contents of a set of TVars changes. Providing this functionality as part of STM seems to be wrong, because it relies on a particular implementation of STM (or a simulation of that behaviour). It would be difficult to incorporate this in the semantics, because the semantics doesn't currenly say anything about the change-detecting optimisation - so the behaviour of the new combinator would have to be specified explicitly. Another purely STM solution that you didn't mention is this: - have one TVar that records the "something changed" condition; every write to a TVar in the widget tree also sets this flag to true; the updater thread waits for the "something changed" flag to become True, calculates the screen redraw, then sets the flag to False again. - The difficulty then is optimising away the redraws when only hidden parts of the widget tree have changed - you could do this by having a "visible" flag on every widget, set by the updater thread. If an update occurs to a hidden widget, the changed flag is not set. If a widget changes such that it might have become visible, it must set the changed flag. I know this is an extra TVar per widget and more operations for each update, but I think it's the right thing. There are no unnecessary traversals of the widget tree, and redraws happen no more often than necessary. Cheers, Simon On 03 July 2005 20:17, John Meacham wrote:
I am attempting to use the STM to rewrite the screen handling code in ginsu, which frankly, I am a little embarassed of :)
The basic needs are: - fully concurrent - all curses calls must go through a single thread (otherwise it gets real complicated) - atomic updates to multiple parts of the screen at once. - efficient update mechanism, screen _must not_ be needlessly redrawn.
So, my idea was basically,
have a screen update thread, this is the only one that will make curses calls. everything else communicates with the screen by modifying TVars holding data values like so
data Widget = HBox { subWidgets :: [TVar Widget] } | Text { body :: TVar String }
and the screen is represented by a top level 'TVar Widget'
This gives the first three requirements very nicely, you can atomically update several widgets or tex boxes, and everything is very concurrent in a good way.
The problem comes in when trying to implement an efficient update mechanism, the problem is that I can't figure out how to wait on only the relevant changed TVars.
The structure of my curses thread would look like (essentially):
cursesThread head = do atomically $ do tv <- readTVar head doit <- render tv -- returns an IO action which draws the screen, calling readTVar on subwidgets. return doit doit -- What to put here to block on relevant changes? cursesThread head
The problem is that many TVars may contribute to the final screen, one can change a TVar several widgets down and the screen should be updated. In addition, the screen should not be redrawn if a TVar for an offscreen or hidden widget changes. other threads should be able to update state without worrying about the UI.
Now, i can think of a couple things, neither of which I like very much.
- create a wrapper monad around STM that accumulates a list of TVars read. This is just unappealing for a variety of reasons. - have render return some sort of keys to compare values too and retry if they are all equal to their old values. this would double the work, one pass to draw it, another pass to figure out we need to block. it would also really complicate the code.
Now, the other reason these options are undesirable, is that the STM mechanism collects the exact information I need. AFAIK there is just no way to get at it. the STM must keep a log of all TVars read in order to know what to wait on when retrying. What I need is a way to somehow atomically commit (assuming the transaction succeeds) and then be able to block on the depended on TVars
This could be done with the addition of a simple primitive:
- | behave exactly like 'atomically' except after the transaction succeeeds, run the action on the result, and then block until one of the TVars read by the transaction changes.
atomicallyBlock :: STM a -> (a -> IO b) -> IO b
I think this could be very useful in general, but perhaps there is something I am not seeing? I am still figuring out idioms to use with STM.
John
participants (1)
-
Simon Marlow