waterfall-cad-0.2.2.0: src/Waterfall/Internal/Finalizers.hs
{-|
Module: Waterfall.Internal.Finalizers
These functions exist because the underlying `opencascade-hs` library,
makes heavy use of `Data.Acquire` from `resourcet` to handle memory management.
However `waterfall-cad` does not (at the highest level) keep values in the `Acquire` monad.
(This is required to support functions like `Waterfall.Solids.volume`, which return pure Haskell primitives.)
-}
module Waterfall.Internal.Finalizers
( unsafeFromAcquire
, fromAcquire
, fromAcquireMay
, toAcquire
) where
import System.IO.Unsafe (unsafePerformIO)
import Control.Monad.Trans.Resource (runResourceT, unprotect)
import Data.Acquire (Acquire, mkAcquire, allocateAcquire)
import System.Mem.Weak (addFinalizer)
import Control.Monad.Primitive (touch)
import Control.Monad.IO.Class (liftIO)
import Data.Maybe (fromMaybe)
-- | Convert a resource in the `Data.Acquire.Acquire` monad to a value in IO
-- the `free` action of the resource is called when the underlying value goes out of scope of the Haskell garbage collection
-- so may run at an unpredictable time.
fromAcquire :: Acquire a -> IO a
fromAcquire a = runResourceT $ do
(releaseKey, v) <- allocateAcquire a
release <- fromMaybe (pure ()) <$> unprotect releaseKey
liftIO $ addFinalizer v release
return v
-- | variant of `fromAcquire` which registers the finalizer on the _value_ in a `Maybe`
-- as opposed to the maybe itself
-- this is useful for wrapping IO actions that return the type `IO (Maybe a)` where the `Maybe` will often be finalized well before the value
fromAcquireMay :: Acquire (Maybe a) -> IO (Maybe a)
fromAcquireMay a = runResourceT $ do
(releaseKey, v) <- allocateAcquire a
release <- fromMaybe (pure ()) <$> unprotect releaseKey
case v of
Nothing -> liftIO $ Nothing <$ release
Just v' -> do
liftIO $ addFinalizer v' release
return . Just $ v'
-- | Converting to a value in the `Data.Acquire.Acquire` monad, to a raw value.
-- Analagous to calling `unsafePerformIO` to extract a value in the `IO` monad.
-- The same constraints as apply to `unsafePerformIO` apply to this method.
-- That is, it should only be used on "philosophically pure" actions.
--
-- The `free` action of the resource is called when the underlying value goes out of scope of the Haskell garbage collection,
-- so may run at an unpredictable time.
{-# NOINLINE unsafeFromAcquire #-}
unsafeFromAcquire :: Acquire a -> a
unsafeFromAcquire = unsafePerformIO . fromAcquire
-- | Add a pure value (which may or may not have been generated by `unsafeFromAcquire`) back into the Acquire monad.
-- Using this action _should_ prevent the underlying value from going out of GC scope untill the resource is freed.
toAcquire :: a -> Acquire a
toAcquire value = mkAcquire (pure value) touch