mirror of
https://github.com/srid/ema.git
synced 2024-12-01 15:13:36 +03:00
Move Data.LVar to separate repo
This commit is contained in:
parent
c86aad162a
commit
48c92c3204
@ -2,7 +2,7 @@ cabal-version: 2.4
|
|||||||
name: ema
|
name: ema
|
||||||
version: 0.1.0.0
|
version: 0.1.0.0
|
||||||
license: AGPL-3.0-only
|
license: AGPL-3.0-only
|
||||||
copyright: 2020 Sridhar Ratnakumar
|
copyright: 2021 Sridhar Ratnakumar
|
||||||
maintainer: srid@srid.ca
|
maintainer: srid@srid.ca
|
||||||
author: Sridhar Ratnakumar
|
author: Sridhar Ratnakumar
|
||||||
category: Web
|
category: Web
|
||||||
@ -44,6 +44,7 @@ library
|
|||||||
, directory
|
, directory
|
||||||
, filepath
|
, filepath
|
||||||
, http-types
|
, http-types
|
||||||
|
, lvar
|
||||||
, monad-logger
|
, monad-logger
|
||||||
, monad-logger-extras
|
, monad-logger-extras
|
||||||
, neat-interpolation
|
, neat-interpolation
|
||||||
@ -98,7 +99,6 @@ library
|
|||||||
ViewPatterns
|
ViewPatterns
|
||||||
|
|
||||||
exposed-modules:
|
exposed-modules:
|
||||||
Data.LVar
|
|
||||||
Ema
|
Ema
|
||||||
Ema.CLI
|
Ema.CLI
|
||||||
|
|
||||||
|
17
flake.lock
17
flake.lock
@ -31,6 +31,22 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lvar": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1619458516,
|
||||||
|
"narHash": "sha256-4nuuDSKPWDIdjmZDxne9EDJvVcivPRaPRZ92dmhjb0Q=",
|
||||||
|
"owner": "srid",
|
||||||
|
"repo": "lvar",
|
||||||
|
"rev": "e68489533fcb67b7404c945b443bb58cdc23b710",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "srid",
|
||||||
|
"repo": "lvar",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1618909122,
|
"lastModified": 1618909122,
|
||||||
@ -51,6 +67,7 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
|
"lvar": "lvar",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
url = "github:edolstra/flake-compat";
|
url = "github:edolstra/flake-compat";
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
|
lvar = {
|
||||||
|
url = "github:srid/lvar";
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
outputs = inputs@{ self, nixpkgs, flake-utils, ... }:
|
outputs = inputs@{ self, nixpkgs, flake-utils, ... }:
|
||||||
flake-utils.lib.eachSystem [ "x86_64-linux" "x86_64-darwin" ] (system:
|
flake-utils.lib.eachSystem [ "x86_64-linux" "x86_64-darwin" ] (system:
|
||||||
@ -21,7 +25,9 @@
|
|||||||
inherit name returnShellEnv;
|
inherit name returnShellEnv;
|
||||||
root = ./.;
|
root = ./.;
|
||||||
withHoogle = false;
|
withHoogle = false;
|
||||||
overrides = self: super: with pkgs.haskell.lib; { };
|
overrides = self: super: with pkgs.haskell.lib; {
|
||||||
|
lvar = self.callCabal2nix "lvar" inputs.lvar { };
|
||||||
|
};
|
||||||
modifier = drv:
|
modifier = drv:
|
||||||
pkgs.haskell.lib.addBuildTools drv (with pkgs.haskellPackages;
|
pkgs.haskell.lib.addBuildTools drv (with pkgs.haskellPackages;
|
||||||
[
|
[
|
||||||
|
142
src/Data/LVar.hs
142
src/Data/LVar.hs
@ -1,142 +0,0 @@
|
|||||||
{-# LANGUAGE DeriveAnyClass #-}
|
|
||||||
|
|
||||||
-- | @LVar@ is like @Control.Concurrent.STM.TMVar@ but with a capability for
|
|
||||||
-- listening to its changes.
|
|
||||||
module Data.LVar
|
|
||||||
( -- * Types
|
|
||||||
LVar,
|
|
||||||
ListenerId,
|
|
||||||
|
|
||||||
-- * Creating a LVar
|
|
||||||
new,
|
|
||||||
empty,
|
|
||||||
|
|
||||||
-- * Modifying a LVar
|
|
||||||
get,
|
|
||||||
set,
|
|
||||||
modify,
|
|
||||||
|
|
||||||
-- * Listening to a LVar
|
|
||||||
addListener,
|
|
||||||
listenNext,
|
|
||||||
removeListener,
|
|
||||||
)
|
|
||||||
where
|
|
||||||
|
|
||||||
import Control.Exception (throw)
|
|
||||||
import qualified Data.Map.Strict as Map
|
|
||||||
import Prelude hiding (empty, get, modify)
|
|
||||||
|
|
||||||
-- A mutable variable (like @TMVar@), changes to which can be listened to from
|
|
||||||
-- multiple threads.
|
|
||||||
data LVar a = LVar
|
|
||||||
{ -- | A value that changes over time
|
|
||||||
lvarCurrent :: TMVar a,
|
|
||||||
-- | Subscribers listening on changes to the value
|
|
||||||
lvarListeners :: TMVar (Map ListenerId (TMVar ()))
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListenerId = Int
|
|
||||||
|
|
||||||
-- | Create a new @LVar@ with the given initial value
|
|
||||||
new :: forall a m. MonadIO m => a -> m (LVar a)
|
|
||||||
new val = do
|
|
||||||
LVar <$> newTMVarIO val <*> newTMVarIO mempty
|
|
||||||
|
|
||||||
-- | Like @new@, but there is no initial value. A @get@ will block until an
|
|
||||||
-- initial value is set using @set@ or @modify@
|
|
||||||
empty :: MonadIO m => m (LVar a)
|
|
||||||
empty =
|
|
||||||
LVar <$> newEmptyTMVarIO <*> newTMVarIO mempty
|
|
||||||
|
|
||||||
-- | Get the value of the @LVar@
|
|
||||||
get :: MonadIO m => LVar a -> m a
|
|
||||||
get v =
|
|
||||||
atomically $ readTMVar $ lvarCurrent v
|
|
||||||
|
|
||||||
-- | Set the @LVar@ value; active listeners are automatically notifed.
|
|
||||||
set :: MonadIO m => LVar a -> a -> m ()
|
|
||||||
set v val = do
|
|
||||||
atomically $ do
|
|
||||||
let var = lvarCurrent v
|
|
||||||
isEmptyTMVar var >>= \case
|
|
||||||
True -> putTMVar var val
|
|
||||||
False -> void $ swapTMVar var val
|
|
||||||
notifyListeners v
|
|
||||||
|
|
||||||
-- | Modify the @LVar@ value; active listeners are automatically notified.
|
|
||||||
modify :: MonadIO m => LVar a -> (a -> a) -> m ()
|
|
||||||
modify v f = do
|
|
||||||
atomically $ do
|
|
||||||
curr <- readTMVar (lvarCurrent v)
|
|
||||||
void $ swapTMVar (lvarCurrent v) (f curr)
|
|
||||||
notifyListeners v
|
|
||||||
|
|
||||||
notifyListeners :: LVar a -> STM ()
|
|
||||||
notifyListeners v' = do
|
|
||||||
subs <- readTMVar $ lvarListeners v'
|
|
||||||
forM_ (Map.elems subs) $ \subVar -> do
|
|
||||||
tryPutTMVar subVar ()
|
|
||||||
|
|
||||||
data ListenerDead = ListenerDead
|
|
||||||
deriving (Exception, Show)
|
|
||||||
|
|
||||||
-- | Create a listener for changes to the @LVar@, as they are set by @set@ or
|
|
||||||
-- @modify@ from this time onwards.
|
|
||||||
--
|
|
||||||
-- You must call @listenNext@ to get the next updated value (or current value if
|
|
||||||
-- there is one).
|
|
||||||
--
|
|
||||||
-- Returns a @ListenerId@ that can be used to stop listening later (via
|
|
||||||
-- @removeListener@)
|
|
||||||
addListener ::
|
|
||||||
MonadIO m =>
|
|
||||||
LVar a ->
|
|
||||||
m ListenerId
|
|
||||||
addListener v = do
|
|
||||||
atomically $ do
|
|
||||||
subs <- readTMVar $ lvarListeners v
|
|
||||||
let nextIdx = maybe 1 (succ . fst) $ Map.lookupMax subs
|
|
||||||
notify <-
|
|
||||||
tryReadTMVar (lvarCurrent v) >>= \case
|
|
||||||
Nothing -> newEmptyTMVar
|
|
||||||
-- As a value is already available, send that as first notification.
|
|
||||||
--
|
|
||||||
-- NOTE: Creating a TMVar that is "full" ensures that we send a current
|
|
||||||
-- (which is not empty) value on @listenNext@).
|
|
||||||
Just _ -> newTMVar ()
|
|
||||||
void $ swapTMVar (lvarListeners v) $ Map.insert nextIdx notify subs
|
|
||||||
pure nextIdx
|
|
||||||
|
|
||||||
-- | Listen for the next value update (since the last @listenNext@ or
|
|
||||||
-- @addListener@). Unless the @LVar@ was empty when @addListener@ was invoked,
|
|
||||||
-- the first invocation of @listenNext@ will return the current value even if
|
|
||||||
-- there wasn't an update. Therefore, the *first* call to @listenNext@ will
|
|
||||||
-- *always* return immediately, unless the @LVar@ is empty.
|
|
||||||
--
|
|
||||||
-- Call this in a loop to listen on a series of updates.
|
|
||||||
--
|
|
||||||
-- Throws @ListenerDead@ if called with a @ListenerId@ that got already removed
|
|
||||||
-- by @removeListener@.
|
|
||||||
listenNext :: MonadIO m => LVar a -> ListenerId -> m a
|
|
||||||
listenNext v idx = do
|
|
||||||
atomically $ do
|
|
||||||
lookupListener v idx >>= \case
|
|
||||||
Nothing ->
|
|
||||||
-- FIXME: can we avoid this by design?
|
|
||||||
throw ListenerDead
|
|
||||||
Just listenVar -> do
|
|
||||||
takeTMVar listenVar
|
|
||||||
readTMVar (lvarCurrent v)
|
|
||||||
where
|
|
||||||
lookupListener :: LVar a -> ListenerId -> STM (Maybe (TMVar ()))
|
|
||||||
lookupListener v' lId = do
|
|
||||||
Map.lookup lId <$> readTMVar (lvarListeners v')
|
|
||||||
|
|
||||||
-- | Stop listening to the @LVar@
|
|
||||||
removeListener :: MonadIO m => LVar a -> ListenerId -> m ()
|
|
||||||
removeListener v lId = do
|
|
||||||
atomically $ do
|
|
||||||
subs <- readTMVar $ lvarListeners v
|
|
||||||
whenJust (Map.lookup lId subs) $ \_sub -> do
|
|
||||||
void $ swapTMVar (lvarListeners v) $ Map.delete lId subs
|
|
Loading…
Reference in New Issue
Block a user