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?
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)