packages feed

vulkan-utils-0.5.11.0: src/Vulkan/Utils/RefCounted.hs

{-| Lightweight reference counter that runs a release action when the count
hits zero. Useful for keeping a Vulkan object alive across an unknown number
of in-flight frames — bump the count when a frame starts using it, drop the
count when the frame retires, and the object is destroyed promptly after the
last frame finishes.
-}
module Vulkan.Utils.RefCounted
  ( RefCounted
  , newRefCounted
  , releaseRefCounted
  , takeRefCounted
  , resourceTRefCount
  ) where

import Control.Exception (mask, throwIO)
import Control.Monad
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.Trans.Resource (MonadResource, allocate_)
import Data.IORef
import GHC.IO.Exception (IOErrorType (UserError), IOException (IOError))

-- | A 'RefCounted' will perform the specified action when the count reaches 0
data RefCounted = RefCounted
  { rcCount :: IORef Int
  , rcAction :: IO ()
  }

-- | Create a counter with a value of 1
newRefCounted :: (MonadIO m) => IO () -> m RefCounted
newRefCounted rcAction = do
  rcCount <- liftIO $ newIORef 1
  pure RefCounted{..}

{- | Decrement the value, the action will be run promptly and in
this thread if the counter reached 0.
-}
releaseRefCounted :: (MonadIO m) => RefCounted -> m ()
releaseRefCounted RefCounted{..} = liftIO $ mask $ \_ ->
  atomicModifyIORef' rcCount (\c -> (pred c, pred c)) >>= \case
    0 -> rcAction
    n
      | n < 0 ->
          liftIO . throwIO $
            IOError
              Nothing
              UserError
              ""
              "Ref counted value decremented below 0"
              Nothing
              Nothing
    _ -> pure ()

-- | Increment the counter by 1
takeRefCounted :: (MonadIO m) => RefCounted -> m ()
takeRefCounted RefCounted{..} =
  liftIO $ atomicModifyIORef' rcCount (\c -> (succ c, ()))

-- | Hold a reference for the duration of the 'MonadResource' action
resourceTRefCount :: (MonadResource f) => RefCounted -> f ()
resourceTRefCount r = void $ allocate_ (takeRefCounted r) (releaseRefCounted r)