summaryrefslogtreecommitdiff
path: root/src/Debounce.hs
blob: 7e9ccabe1f1e6b3141e0679f36f60c74130d9971 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
{-# LANGUAGE OverloadedStrings #-}
module Debounce (
  Debounce,
  makeDebounce, debounceClick,
) where

import Control.Concurrent
import Control.Monad (when, void)
import Data.IORef
import Data.Text (Text)
-- import Data.Text qualified as T

-- import AtomicPrint


-- delay in microseconds; current waiter
data Debounce = Debounce Text Int (IO ()) (IORef State)

data State = Idle  -- ^ no event recently
           | Waiting  -- ^ recent event, debouncing
           | Running  -- ^ delay expired, running action
           | RunningRerun  -- ^ still running action but another event appeared in the mean time
  deriving (Show)

-- State transition diagram:
--
--      ,---------> IDLE
--     |              |
--     |        click |,-----. click
--     |              V      |
--     |           WAITING >-'
--     |              | ^.______________
--     |  threadDelay |                 \ (completed)
--     |              V                  |
--     '---------< RUNNING -------> RUNNINGRERUN >-,
--   (completed)            click            ^.___/ click
--
-- In Idle, no debounceWait thread is running.
-- In Waiting, debounceWait is in its threadDelay.
-- In Running & RunningRerun, debounceWait is in its 'action'.
-- There is always <=1 debounceWait thread at a time.

makeDebounce :: Text -> Double -> IO () -> IO Debounce
makeDebounce description secs action = Debounce description (round (secs * 1e6)) action <$> newIORef Idle

debounceClick :: Debounce -> IO ()
debounceClick deb@(Debounce _descr _ _ ref) = do
  (gowait, _origstate) <- atomicModifyIORef' ref $ \st -> case st of
    Idle -> (Waiting, (True, st))
    Waiting -> (Waiting, (False, st))
    Running -> (RunningRerun, (False, st))
    RunningRerun -> (RunningRerun, (False, st))

  -- atomicPrint $ "debounce[" <> _descr <> "] @ " <> T.show _origstate

  when gowait $
    debounceWait deb

-- Precondition: current state is Waiting
debounceWait :: Debounce -> IO ()
debounceWait deb@(Debounce _descr delay action ref) =
  void . forkIO $ do
    threadDelay delay
    atomicModifyIORef' ref $ \case
      Waiting -> (Running, ())
      st -> error $ "debounce: unexpected " ++ show st ++ ", should be Waiting"
    -- atomicPrint $ "debounce[" <> _descr <> "] running"
    action
    gowait <- atomicModifyIORef' ref $ \case
      Running -> (Idle, False)
      RunningRerun -> (Waiting, True)
      st -> error $ "debounce: unexpected " ++ show st ++ ", should be Running(Rerun)"
    -- atomicPrint $ "debounce[" <> _descr <> "] done, " <> (if gowait then "again" else "now idle")
    when gowait $
      debounceWait deb