-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0

module Development.IDE.Core.Debouncer
    ( Debouncer
    , registerEvent
    , newAsyncDebouncer
    , noopDebouncer
    ) where

import           Control.Concurrent.Async
import           Control.Concurrent.STM.Stats (atomically, atomicallyNamed)
import           Control.Exception
import           Control.Monad                (join)
import           Data.Foldable                (traverse_)
import           Data.Hashable
import qualified Focus
import qualified StmContainers.Map            as STM
import           System.Time.Extra

-- | A debouncer can be used to avoid triggering many events
-- (e.g. diagnostics) for the same key (e.g. the same file)
-- within a short timeframe. This is accomplished
-- by delaying each event for a given time. If another event
-- is registered for the same key within that timeframe,
-- only the new event will fire.
--
-- We abstract over the debouncer used so we an use a proper debouncer in the IDE but disable
-- debouncing in the DAML CLI compiler.
newtype Debouncer k = Debouncer { Debouncer k -> Seconds -> k -> IO () -> IO ()
registerEvent :: Seconds -> k -> IO () -> IO () }

-- | Debouncer used in the IDE that delays events as expected.
newAsyncDebouncer :: (Eq k, Hashable k) => IO (Debouncer k)
newAsyncDebouncer :: IO (Debouncer k)
newAsyncDebouncer = (Seconds -> k -> IO () -> IO ()) -> Debouncer k
forall k. (Seconds -> k -> IO () -> IO ()) -> Debouncer k
Debouncer ((Seconds -> k -> IO () -> IO ()) -> Debouncer k)
-> (Map k (Async ()) -> Seconds -> k -> IO () -> IO ())
-> Map k (Async ())
-> Debouncer k
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Map k (Async ()) -> Seconds -> k -> IO () -> IO ()
forall k.
(Eq k, Hashable k) =>
Map k (Async ()) -> Seconds -> k -> IO () -> IO ()
asyncRegisterEvent (Map k (Async ()) -> Debouncer k)
-> IO (Map k (Async ())) -> IO (Debouncer k)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO (Map k (Async ()))
forall key value. IO (Map key value)
STM.newIO

-- | Register an event that will fire after the given delay if no other event
-- for the same key gets registered until then.
--
-- If there is a pending event for the same key, the pending event will be killed.
-- Events are run unmasked so it is up to the user of `registerEvent`
-- to mask if required.
asyncRegisterEvent :: (Eq k, Hashable k) => STM.Map k (Async ()) -> Seconds -> k -> IO () -> IO ()
asyncRegisterEvent :: Map k (Async ()) -> Seconds -> k -> IO () -> IO ()
asyncRegisterEvent Map k (Async ())
d Seconds
0 k
k IO ()
fire = do
    IO (IO ()) -> IO ()
forall (m :: * -> *) a. Monad m => m (m a) -> m a
join (IO (IO ()) -> IO ()) -> IO (IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ STM (IO ()) -> IO (IO ())
forall a. STM a -> IO a
atomically (STM (IO ()) -> IO (IO ())) -> STM (IO ()) -> IO (IO ())
forall a b. (a -> b) -> a -> b
$ do
        Maybe (Async ())
prev <- Focus (Async ()) STM (Maybe (Async ()))
-> k -> Map k (Async ()) -> STM (Maybe (Async ()))
forall key value result.
(Eq key, Hashable key) =>
Focus value STM result -> key -> Map key value -> STM result
STM.focus Focus (Async ()) STM (Maybe (Async ()))
forall (m :: * -> *) a. Monad m => Focus a m (Maybe a)
Focus.lookupAndDelete k
k Map k (Async ())
d
        IO () -> STM (IO ())
forall (m :: * -> *) a. Monad m => a -> m a
return (IO () -> STM (IO ())) -> IO () -> STM (IO ())
forall a b. (a -> b) -> a -> b
$ (Async () -> IO ()) -> Maybe (Async ()) -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ Async () -> IO ()
forall a. Async a -> IO ()
cancel Maybe (Async ())
prev
    IO ()
fire
asyncRegisterEvent Map k (Async ())
d Seconds
delay k
k IO ()
fire = IO () -> IO ()
forall a. IO a -> IO a
mask_ (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
    Async ()
a <- ((forall a. IO a -> IO a) -> IO ()) -> IO (Async ())
forall a. ((forall a. IO a -> IO a) -> IO a) -> IO (Async a)
asyncWithUnmask (((forall a. IO a -> IO a) -> IO ()) -> IO (Async ()))
-> ((forall a. IO a -> IO a) -> IO ()) -> IO (Async ())
forall a b. (a -> b) -> a -> b
$ \forall a. IO a -> IO a
unmask -> IO () -> IO ()
forall a. IO a -> IO a
unmask (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
        Seconds -> IO ()
sleep Seconds
delay
        IO ()
fire
        STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ k -> Map k (Async ()) -> STM ()
forall key value.
(Eq key, Hashable key) =>
key -> Map key value -> STM ()
STM.delete k
k Map k (Async ())
d
    do
        Maybe (Async ())
prev <- String -> STM (Maybe (Async ())) -> IO (Maybe (Async ()))
forall a. String -> STM a -> IO a
atomicallyNamed String
"debouncer" (STM (Maybe (Async ())) -> IO (Maybe (Async ())))
-> STM (Maybe (Async ())) -> IO (Maybe (Async ()))
forall a b. (a -> b) -> a -> b
$ Focus (Async ()) STM (Maybe (Async ()))
-> k -> Map k (Async ()) -> STM (Maybe (Async ()))
forall key value result.
(Eq key, Hashable key) =>
Focus value STM result -> key -> Map key value -> STM result
STM.focus (Focus (Async ()) STM (Maybe (Async ()))
forall (m :: * -> *) a. Monad m => Focus a m (Maybe a)
Focus.lookup Focus (Async ()) STM (Maybe (Async ()))
-> Focus (Async ()) STM ()
-> Focus (Async ()) STM (Maybe (Async ()))
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* Async () -> Focus (Async ()) STM ()
forall (m :: * -> *) a. Monad m => a -> Focus a m ()
Focus.insert Async ()
a) k
k Map k (Async ())
d
        (Async () -> IO ()) -> Maybe (Async ()) -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ Async () -> IO ()
forall a. Async a -> IO ()
cancel Maybe (Async ())
prev

-- | Debouncer used in the DAML CLI compiler that emits events immediately.
noopDebouncer :: Debouncer k
noopDebouncer :: Debouncer k
noopDebouncer = (Seconds -> k -> IO () -> IO ()) -> Debouncer k
forall k. (Seconds -> k -> IO () -> IO ()) -> Debouncer k
Debouncer ((Seconds -> k -> IO () -> IO ()) -> Debouncer k)
-> (Seconds -> k -> IO () -> IO ()) -> Debouncer k
forall a b. (a -> b) -> a -> b
$ \Seconds
_ k
_ IO ()
a -> IO ()
a