reactive-banana-1.3.2.0: src/Reactive/Banana/Prim/Low/Ref.hs
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE UnboxedTuples #-}
{-----------------------------------------------------------------------------
reactive-banana
------------------------------------------------------------------------------}
module Reactive.Banana.Prim.Low.Ref
( -- * Mutable references with 'Unique'
Ref
, getUnique
, new
, equal
, read
, put
, modify'
-- * Garbage collection and weak pointers to 'Ref'
, addFinalizer
, getWeakRef
, WeakRef
, mkWeak
, deRefWeak
, deRefWeaks
, finalize
) where
import Prelude hiding ( read )
import Control.DeepSeq
( NFData (..) )
import Control.Monad
( void )
import Control.Monad.IO.Class
( MonadIO (liftIO) )
import Data.Hashable
( Hashable (..) )
import Data.IORef
( IORef, newIORef, readIORef, writeIORef )
import Data.Maybe
( catMaybes )
import Data.Unique.Really
( Unique, newUnique )
import qualified System.Mem.Weak as Weak
import qualified GHC.Base as GHC
import qualified GHC.IORef as GHC
import qualified GHC.STRef as GHC
import qualified GHC.Weak as GHC
{-----------------------------------------------------------------------------
Ref
------------------------------------------------------------------------------}
-- | A mutable reference which has a 'Unique' associated with it.
data Ref a = Ref
!Unique -- Unique associated to the 'Ref'
!(IORef a) -- 'IORef' that stores the value of type 'a'
!(WeakRef a) -- For convenience, a weak pointer to itself
instance NFData (Ref a) where rnf (Ref _ _ _) = ()
instance Eq (Ref a) where (==) = equal
instance Hashable (Ref a) where hashWithSalt s (Ref u _ _) = hashWithSalt s u
getUnique :: Ref a -> Unique
getUnique (Ref u _ _) = u
getWeakRef :: Ref a -> WeakRef a
getWeakRef (Ref _ _ w) = w
equal :: Ref a -> Ref b -> Bool
equal (Ref ua _ _) (Ref ub _ _) = ua == ub
new :: MonadIO m => a -> m (Ref a)
new a = liftIO $ mdo
ra <- newIORef a
result <- Ref <$> newUnique <*> pure ra <*> pure wa
wa <- mkWeakIORef ra result Nothing
pure result
read :: MonadIO m => Ref a -> m a
read ~(Ref _ r _) = liftIO $ readIORef r
put :: MonadIO m => Ref a -> a -> m ()
put ~(Ref _ r _) = liftIO . writeIORef r
-- | Strictly modify a 'Ref'.
modify' :: MonadIO m => Ref a -> (a -> a) -> m ()
modify' ~(Ref _ r _) f = liftIO $
readIORef r >>= \x -> writeIORef r $! f x
{-----------------------------------------------------------------------------
Weak pointers
------------------------------------------------------------------------------}
-- | Add a finalizer to a 'Ref'.
--
-- See 'System.Mem.Weak.addFinalizer'.
addFinalizer :: Ref v -> IO () -> IO ()
addFinalizer (Ref _ r _) = void . mkWeakIORef r () . Just
-- | Weak pointer to a 'Ref'.
type WeakRef v = Weak.Weak (Ref v)
-- | Create a weak pointer that associates a key with a value.
--
-- See 'System.Mem.Weak.mkWeak'.
mkWeak
:: Ref k -- ^ key
-> v -- ^ value
-> Maybe (IO ()) -- ^ finalizer
-> IO (Weak.Weak v)
mkWeak (Ref _ r _) = mkWeakIORef r
-- | Finalize a 'WeakRef'.
--
-- See 'System.Mem.Weak.finalize'.
finalize :: WeakRef v -> IO ()
finalize = Weak.finalize
-- | Dereference a 'WeakRef'.
--
-- See 'System.Mem.Weak.deRefWeak'.
deRefWeak :: Weak.Weak v -> IO (Maybe v)
deRefWeak = Weak.deRefWeak
-- | Dereference a list of weak pointers while discarding dead ones.
deRefWeaks :: [Weak.Weak v] -> IO [v]
deRefWeaks ws = catMaybes <$> mapM Weak.deRefWeak ws
{-----------------------------------------------------------------------------
Helpers
------------------------------------------------------------------------------}
-- | Create a weak pointer to an 'IORef'.
--
-- Unpacking the constructors (e.g. 'GHC.IORef' etc.) is necessary
-- because the constructors may be unpacked while the 'IORef' is used
-- — so, the value contained therein is alive, but the constructors are not.
mkWeakIORef
:: IORef k -- ^ key
-> v -- ^ value
-> Maybe (IO ()) -- ^ finalizer
-> IO (Weak.Weak v)
mkWeakIORef (GHC.IORef (GHC.STRef r#)) v (Just (GHC.IO finalizer)) =
GHC.IO $ \s -> case GHC.mkWeak# r# v finalizer s of
(# s1, w #) -> (# s1, GHC.Weak w #)
mkWeakIORef (GHC.IORef (GHC.STRef r#)) v Nothing =
GHC.IO $ \s -> case GHC.mkWeakNoFinalizer# r# v s of
(# s1, w #) -> (# s1, GHC.Weak w #)