2019-05-06 00:46:40 +03:00
|
|
|
{-
|
|
|
|
# Behn
|
|
|
|
|
|
|
|
This provides a timer. To use this,
|
|
|
|
|
|
|
|
- Create a new timer with `init`.
|
|
|
|
- Use `doze` to start the timer.
|
|
|
|
- Call `wait` to wait until the timer fires.
|
|
|
|
|
|
|
|
Then, `wait` will return when the specified time has come.
|
|
|
|
|
|
|
|
- If the specified time was in the past, `wait` will return immediately.
|
|
|
|
- If a timer is set again, the old timer will not fire. The new time
|
|
|
|
replaces the old one.
|
|
|
|
- If a timer is unset (with `doze _ Nothing`), the timer will not fire
|
|
|
|
until a new time has been set.
|
|
|
|
|
|
|
|
## Implementation Notes
|
|
|
|
|
|
|
|
We use `tryPutMVar` when the timer fires, so that things will continue
|
|
|
|
to work correctly if the user does not call `wait`. If a timer fires
|
|
|
|
before `wait` is called, `wait` will return immediatly.
|
|
|
|
|
|
|
|
To handle race conditions, the MVar in `bState` is used as a lock. The
|
|
|
|
code for setting a timer and the thread that runs when the timer fires
|
|
|
|
(which causes `wait` to return) both take that MVar before acting.
|
|
|
|
|
|
|
|
So, if the timer fires conncurently with a call to `doze`,
|
|
|
|
then one of those threads will get the lock and the other will wait:
|
|
|
|
|
|
|
|
- If the `doze` call gets the lock first, it will kill the timer thread
|
|
|
|
before releasing it.
|
|
|
|
- If the timer gets the the lock first, it will fire (causeing `wait`
|
|
|
|
to return) first, and then `doze` action will wait until that finishes.
|
2019-05-08 23:51:04 +03:00
|
|
|
|
|
|
|
## TODO
|
|
|
|
|
|
|
|
`threadDelay` has low accuracy. Consider using
|
|
|
|
`GHC.Event.registerTimeout` instead. It's API is very close to what
|
|
|
|
we want for this anyways.
|
2019-05-06 00:46:40 +03:00
|
|
|
-}
|
|
|
|
|
2019-05-08 21:47:20 +03:00
|
|
|
module Urbit.Behn (Behn, init, wait, doze) where
|
2019-05-06 00:46:40 +03:00
|
|
|
|
2019-05-08 21:47:20 +03:00
|
|
|
import Prelude hiding (init)
|
|
|
|
import Control.Lens
|
2019-05-06 00:46:40 +03:00
|
|
|
|
2019-05-08 21:47:20 +03:00
|
|
|
import Data.LargeWord
|
|
|
|
import Control.Concurrent.MVar
|
2019-05-06 00:46:40 +03:00
|
|
|
|
2019-05-08 21:47:20 +03:00
|
|
|
import Control.Concurrent.Async (Async, async, cancel, asyncThreadId)
|
|
|
|
import Control.Concurrent (threadDelay, killThread)
|
2019-05-08 23:51:04 +03:00
|
|
|
import Control.Monad (void, when)
|
2019-05-08 21:47:20 +03:00
|
|
|
import Data.Time.Clock.System (SystemTime(..), getSystemTime)
|
|
|
|
import Urbit.Time (Wen)
|
2019-05-06 00:46:40 +03:00
|
|
|
|
2019-05-08 21:47:20 +03:00
|
|
|
import qualified Control.Concurrent.Async as Async
|
|
|
|
import qualified Urbit.Time as Time
|
2019-05-06 00:46:40 +03:00
|
|
|
|
|
|
|
|
|
|
|
-- Behn Stuff ------------------------------------------------------------------
|
|
|
|
|
|
|
|
data Behn = Behn
|
2019-05-08 21:47:20 +03:00
|
|
|
{ bState :: MVar (Maybe (Wen, Async ()))
|
|
|
|
, bSignal :: MVar Wen
|
2019-05-06 00:46:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
init :: IO Behn
|
|
|
|
init = do
|
|
|
|
st <- newMVar Nothing
|
|
|
|
sig <- newEmptyMVar
|
|
|
|
pure (Behn st sig)
|
|
|
|
|
2019-05-08 21:47:20 +03:00
|
|
|
wait :: Behn -> IO Wen
|
2019-05-06 00:46:40 +03:00
|
|
|
wait (Behn _ sig) = takeMVar sig
|
|
|
|
|
2019-05-08 21:47:20 +03:00
|
|
|
startTimerThread :: Behn -> Wen -> IO (Async ())
|
2019-05-06 00:46:40 +03:00
|
|
|
startTimerThread (Behn vSt sig) time =
|
|
|
|
async $ do
|
2019-05-08 21:47:20 +03:00
|
|
|
now <- Time.now
|
2019-05-08 23:51:04 +03:00
|
|
|
Time.sleepUntil time
|
2019-05-08 21:47:20 +03:00
|
|
|
takeMVar vSt
|
|
|
|
void $ tryPutMVar sig time
|
|
|
|
putMVar vSt Nothing
|
2019-05-06 00:46:40 +03:00
|
|
|
|
2019-05-08 21:47:20 +03:00
|
|
|
doze :: Behn -> Maybe Wen -> IO ()
|
2019-05-06 00:46:40 +03:00
|
|
|
doze behn@(Behn vSt sig) mNewTime = do
|
|
|
|
takeMVar vSt >>= \case Nothing -> pure ()
|
|
|
|
Just (_,timer) -> cancel timer
|
|
|
|
|
|
|
|
newSt <- mNewTime & \case
|
2019-05-08 21:47:20 +03:00
|
|
|
Nothing -> pure (Nothing :: Maybe (Wen, Async ()))
|
2019-05-06 00:46:40 +03:00
|
|
|
Just time -> do timer <- startTimerThread behn time
|
|
|
|
pure (Just (time, timer))
|
|
|
|
|
|
|
|
void (putMVar vSt newSt)
|