views:

97

answers:

2

I am using WxHaskell to graphically show the state of a program that advertises state updates using TCP (which I decode using Data.Binary). When an update is received, I want to update the display. So I want the GUI to update its display asynchronously. I know that processExecAsync runs a command line process asynchronously, but I don't think this is what I want.

+1  A: 

This is rough code using transactional variables (i.e. software transactional memory). You could use an IORef, MVar, or numerous other constructs.

main = do
    recvFunc <- initNetwork
    cntTV <- newTVarIO 0
    forkIO $ threadA recvFunc cntTV
    runGUI cntTV 0

Above you start the program, initialize the network and a shared variable cntTV

threadA recvCntFromNetwork cntTVar = forever $ do
    cnt <- recvCntFromNetwork
    atomically (writeTVar cntTVar cnt)

threadA receives data from the network and writes the new value of the counter to the shared variable.

runGUI cntTVar currentCnt = do
    counter <- initGUI
    cnt <- atomically $ do
        cnt <- readTVar cntTVar
        if (cnt == currentCnt)
            then retry
            else return cnt
    updateGUICounter counter cnt
    runGUI cntTVar cnt

runGUI reads the shared variable and if there is a change will update the GUI counter. FYI, the runGUI thread won't wake up on retry until cntTVar is modified, so this isn't a CPU hogging polling loop.

In this code I've assumed you have functions named updateGUICounter, initGUI, and initNetwork. I advise you use Hoogle to find the location of any other functions you don't already know and learn a little about each module.

TomMD
Thanks for your answer. This is sort of what I had in mind. Unfortunately, we need to call the wxHaskell function `start` which starts the event loop. If we run `start runGUI` then the event loop will never be started. And if we `forkIO` the STM stuff then the GUI is not updated (at least when I tried).
Alex
A: 

I have come up with a kind of hack that seems to work. Namely, use an event timer to check an update queue:

startClient :: IO (TVar [Update])
startClient = /*Connect to server, 
                listen for updates and add to queue*/

gui :: TVar [Update] -> IO ()
gui trdl = do
  f <- frame [text := "counter", visible := False]
  p <- panel f []
  st <- staticText p []
  t <- timer f [interval := 10, on command := updateGui st]
  set f [layout := container p $ fill $ widget st, clientSize := (sz 200 100), visible := True]
 where
   updateGui st = do
             rdl <- atomically $ readTVar trdl
             atomically $ writeTVar trdl []
             case rdl of
               [] -> return ()
               dat : dl -> set st [text := (show dat)]

main :: IO ()
main = startClient >>= start gui

So a client listens for the updates on the TCP connection, adds them to a queue. Every 10ms, an event is raised whose action is to check this queue and show the latest update in a static text widget.

If you have a better solution, please let me know!

Alex
From what I can tell there isn't any reason gui can't retry on the TVar and do this async (but yes, wx breaks seemingly any time the thread blocks). I also tried inverting the concept and writing `\str -> set st [text := str]` into the TVar then having `startClient` call this to update the GUI, but that seems to block indefinitely on `set`. WX has proven rather frustrating so I think I'll stick with GTK as a result.
TomMD