haskellgloss

Is it possible to update the viewport irregularly in haskell `gloss`?


I am trying to work with gloss to draw a dynamic object. The object's state changes based on its previous state.

As far as I understand I can draw using simulate or play.

Both methods need FPS defined.

I would like to redraw immediately after updating the state of the object.

Can I redraw irregularly (without FPS defined in advance)?

Edit:

Tried to use animateIO with the following code:

module Main where

import Control.Concurrent
import Graphics.Gloss
import Graphics.Gloss.Interface.IO.Animate
import System.Clock

controllerCallback :: MVar TimeSpec -> Controller -> IO ()
controllerCallback m c = do
  t <- getTime Monotonic
  -- putMVar m t
  threadDelay 100000
  putStrLn "State updated"
  controllerSetRedraw c

-- controllerCallback m c -- uncommenting it causes the app hang

main :: IO ()
main = do
  m <- newEmptyMVar
  animateIO
    (InWindow "Tree" (500, 650) (20, 20))
    black
    (picture m)
    (controllerCallback m)

picture :: MVar TimeSpec -> Float -> IO Picture
picture m time = do
  -- t <- takeMVar m
  t <- getTime Monotonic
  let tt = realToFrac t / 1000000000
  return $
    Rotate (sin tt) $
      Color green $
        Polygon [(30, 0), (15, 300), (-15, 300), (-30, 0)]

It works, but the controllerCallback is called once and when I uncomment the commented MVar lines - the app hangs.

I also tried to make a cycle within controller callback (see the commented line above) - but the app hangs as well.

What I am doing wrong?


Solution

  • I haven't tested this, but it looks like the IO-based animation interface should support this smoothly. The Controller type isn't documented on that page, but on the display page, and suggests that the animation runs in its own thread:

    The provided picture generating action will be invoked, and the display redrawn in two situation: 1) We receive a display event, like someone clicks on the window. 2) When controllerSetRedraw has been set, some indeterminate time between the last redraw, and one second from that.

    (Presumably the timeout for the animation primitive is lower than one second.)

    So: in your Controller callback, write a loop that updates your state (in an MVar, say, or an IORef if you're feeling cheeky) and then calls controllerSetRedraw. In your rendering callback, read the current state from the reference and draw it.

    You may like evaluate: when writing to an MVar or IORef, it is possible to write a thunk and get the evaluation done in the wrong thread. You may also need to explicitly link with the threaded runtime; do this by adding -threaded to your ghc-options if you are using cabal or to the command line directly if you are using ghc itself.

    Here's a bare-bones thing to try:

    import Control.Monad
    import Control.Concurrent
    import Control.Concurrent.MVar
    import Graphics.Gloss.Interface.IO.Animate
    
    data X
    initialState :: X
    updateState :: X -> X
    renderState :: X -> Picture
    
    initialState = undefined
    updateState = undefined
    renderState = undefined
    
    control :: MVar X -> Controller -> IO ()
    control ref c = void . forkIO . forever $ do
      x <- takeMVar ref
      x' <- evaluate (updateState x)
      putMVar ref x'
      controllerSetRedraw c
    
    main :: IO ()
    main = do
      ref <- newMVar initialState
      animateIO FullScreen black (\_ -> renderState <$> readMVar ref) (control ref)