packages feed

sdl3-bindgen-sys-0.0.0.1: runtime/HsBindgen/Runtime/FLAM.hs

{-# LANGUAGE MagicHash #-}

-- We capitalize module names, but use camelCase/PascalCase in code:
--
-- - in types names:    FlamFoo, FooFlamBar
-- - in variable names: flamFoo, fooFlamBar

-- | Intended for qualified import.
--
-- @
-- import HsBindgen.Runtime.FLAM (WithFlam)
-- import HsBindgen.Runtime.FLAM qualified as FLAM
-- @
module HsBindgen.Runtime.FLAM (
  -- * Definitions
  Offset (..),
  NumElems (..),
  WithFlam (..),

  -- * Exceptions
  FlamLengthMismatch (..),
) where

import Control.Exception (Exception, throwIO)
import Data.Kind (Type)
import Data.Vector.Storable qualified as VS
import Data.Vector.Storable.Mutable qualified as VSM
import Foreign (Ptr, Storable)
import Foreign qualified
import GHC.Exts (Proxy#, proxy#)

import HsBindgen.Runtime.Marshal

{-------------------------------------------------------------------------------
  Definitions
-------------------------------------------------------------------------------}

-- | The offset of the FLAM to the beginning of the underlying data structure in
--   bytes
class Offset elem aux | aux -> elem where
  offset :: Proxy# aux -> Int

-- | The number of elements 'elem' of the FLAM contained in structure 'aux'
class (Offset elem aux) => NumElems elem aux | aux -> elem where
  numElems :: aux -> Int

-- | Data structure with flexible array member
data WithFlam elem aux = WithFlam
  { -- Underlying data structure without FLAM
    aux :: !aux
  , -- We use the word "flam" for the flexible array member of the struct.
    -- We use the word "vector" to refer to its Haskell representation (as a
    -- vector).
    flam :: {-# UNPACK #-} !(VS.Vector elem)
  }
  deriving stock (Show)

instance
  (Storable aux, Storable elem, NumElems elem aux)
  => ReadRaw (WithFlam elem aux)
  where
  readRaw = peek

instance
  (Storable aux, Storable elem, NumElems elem aux)
  => WriteRaw (WithFlam elem aux)
  where
  writeRaw = poke

{-------------------------------------------------------------------------------
  Peek and poke
-------------------------------------------------------------------------------}

-- | Peek structure with flexible array member.
peek
  :: forall aux elem
   . (Storable aux, Storable elem, NumElems elem aux)
  => Ptr (WithFlam elem aux) -> IO (WithFlam elem aux)
peek ptrStruct = do
  aux <- Foreign.peek (ptrToAux ptrStruct)
  let Size{sizeNumElems, sizeNumBytes} = flamSize aux
  vector <- VSM.unsafeNew sizeNumElems
  Foreign.withForeignPtr (fst (VSM.unsafeToForeignPtr0 vector)) $ \ptrVectorElems -> do
    Foreign.copyBytes ptrVectorElems (ptrToFlam ptrStruct) sizeNumBytes
  vector' <- VS.unsafeFreeze vector
  return (WithFlam aux vector')

-- | Poke structure with flexible array member.
poke
  :: forall aux elem
   . (Storable aux, Storable elem, NumElems elem aux)
  => Ptr (WithFlam elem aux) -> WithFlam elem aux -> IO ()
poke ptrStruct (WithFlam aux vector)
  | sizeNumElems /= VS.length vector =
      throwIO $ FlamLengthMismatch sizeNumElems (VS.length vector)
  | otherwise = do
      Foreign.poke (ptrToAux ptrStruct) aux
      VS.unsafeWith vector $ \ptrVectorElems -> do
        Foreign.copyBytes (ptrToFlam ptrStruct) ptrVectorElems sizeNumBytes
 where
  Size{sizeNumElems, sizeNumBytes} = flamSize aux

{-------------------------------------------------------------------------------
  Exceptions
-------------------------------------------------------------------------------}

-- | Exception thrown when 'writeRaw' detects a FLAM length mismatch
data FlamLengthMismatch = FlamLengthMismatch
  { flamLengthStruct :: Int
  , flamLengthProvided :: Int
  }
  deriving stock (Show)

instance Exception FlamLengthMismatch

{-------------------------------------------------------------------------------
  Internal helpers
-------------------------------------------------------------------------------}

ptrToAux :: Ptr (WithFlam elem aux) -> Ptr aux
ptrToAux = Foreign.castPtr

ptrToFlam
  :: forall elem aux
   . (Offset elem aux)
  => Ptr (WithFlam elem aux) -> Ptr elem
ptrToFlam ptrStruct = Foreign.plusPtr ptrStruct (offset (proxy# @aux))

-- Internal.
data Size = Size
  { sizeNumElems :: Int
  , sizeNumBytes :: Int
  }

flamSize
  :: forall (elem :: Type) aux
   . (NumElems elem aux, Storable elem)
  => aux -> Size
flamSize aux =
  Size
    { sizeNumElems
    , sizeNumBytes = sizeNumElems * Foreign.sizeOf (undefined :: elem)
    }
 where
  sizeNumElems = numElems aux