Closed jmickelin closed 7 years ago
Sorry for the late reply.
At the moment, Threepenny uses a homegrown variant of FRP. I intend to replace it with Reactive Banana at some point, but this also means that I won't be adding any new functions to the Reactive.Threepenny API, as they will be taken over anyway.
Fortunately, I think you can solve your particular problem by using newEvent
. The idea is to construct a Behavior from this event, and to execute the IO action and pass the result to the handler whenever the timer ticks. Something like this:
fromPoll :: MonadIO m => IO Int -> m (Behavior Int)
fromPoll m = liftIO $ do
a <- m
(e, h) <- newEvent
onTimer $ m >>= h
stepper a e
Does this help?
Hi, thanks for the reply!
I see the principle, but what would onTimer
be here? I can't seem to find such a function in the library, and when trying to define it on my own I run into problems because onEvent (tick someTimer)
and pals return values wrapped inside UI
and not IO
as needed.
You should be able to use the register
function from Reactive.Threepenny
, like this
register (tick someTimer) $ m >>= h
The onEvent
function would work as well, though you would have to play around with liftIO
.
Oh, must've missed that one. Yep, that works! Thanks!
Glad I could help. :smile: Closing.
Coming back to this today, I noticed that while it works fine for timers with small intervals, it breaks down with large ones. The cut-off point seems to be at 2147484 ms (around 35 minutes 47 seconds), after which the IO action keeps running without any delay at all.
Minimal example:
import Control.Monad
import Control.Monad.IO.Class
import Graphics.UI.Threepenny.Core
import qualified Graphics.UI.Threepenny as UI
import Reactive.Threepenny
fromPoll :: MonadIO m => UI.Timer -> IO a -> m (Behavior a)
fromPoll pollTimer m = liftIO $ do
d <- m
(e, h) <- newEvent
register (UI.tick pollTimer) $
\_ -> m >>= h
stepper d e
seconds, minutes, hours, days :: Int
seconds = 1000 -- 1000 milliseconds
minutes = 60 * seconds
hours = 60 * minutes
days = 24 * hours
setup :: Window -> UI ()
setup window = void $ do
t <- UI.timer # set UI.interval (35 * minutes + 47 * seconds + 484)
_ <- fromPoll t $
do
putStrLn $ "spam"
UI.start t
main :: IO ()
main = startGUI defaultConfig setup
I'm also noticing some kind of intermittent fault for smaller values, where the action is run exactly twice, but I can't reproduce it reliably.
Do you have any idea what could be causing this? Should I file a separate bug?
I'm using the version from the lts-8.0
snapshot on Stackage.
System information:
$ uname -a
Linux datamaskinen 4.9.8-1-ARCH #1 SMP PREEMPT Mon Feb 6 13:18:39 CET 2017 i686 GNU/Linux
Will try it on a 64-bit machine as soon as I can (though 2147484 feels a bit too small to cause any overflows anyway).
I believe is overflowing 2^31 in miliseconds. 2147484000 is quite near the Max integer value.
You're right, I hadn't noticed that. But the argument to set UI.interval
should already be in milliseconds, according to the documentation.
Wait, I found the culprit! Turns out it converts it to microseconds for use with threadDelay
, making it overflow as you suggested.
-- | Create a new timer
timer :: UI Timer
timer = liftIO $ do
tvRunning <- newTVarIO False
tvInterval <- newTVarIO 1000
(tTick, fire) <- newEvent
forkIO $ forever $ do
atomically $ do
b <- readTVar tvRunning
when (not b) retry
wait <- atomically $ readTVar tvInterval
fire ()
threadDelay (wait * 1000) -- <-- Here's the problem
let tRunning = fromTVar tvRunning
tInterval = fromTVar tvInterval
return $ Timer {..}
I guess I'll just have to stay below 35 minutes on my 32-bit machine, then...
Thanks!
I guess I'll just have to stay below 35 minutes on my 32-bit machine, then...
I guess we could modify timer
to make multiple calls to threadDelay when wait
gets too big.
Is there an analogue of Reactive Banana's
fromChanges
orfromPoll
functions in threepenny-gui, or otherwise some other way to turn anIO a
into aBehavior a
?I want to read data from a network resource at regular intervals, but I currently resort to using
on
with a timer and a callback function that manually has to run the IO action and update every affectedElement
. That doesn't really feel like FRP, so I think I must be missing something. What's the right way to do this?