packages feed

h-raylib-5.5.3.0: src/Raylib/Util.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE TemplateHaskellQuotes #-}

-- | Utility functions that may be useful for an h-raylib application
module Raylib.Util
  ( -- * Bracket functions
    withWindow,
    drawing,
    mode2D,
    mode3D,
    textureMode,
    shaderMode,
    blendMode,
    scissorMode,
    vrStereoMode,

    -- * Game loop functions
    raylibApplication,
    whileWindowOpen,
    whileWindowOpen_,
    whileWindowOpen0,

    -- * Resource management
    WindowResources,
    Closeable (close),
    managed,

    -- * Miscellaneous
    cameraDirectionRay,
    setMaterialShader,
    inGHCi,
    inWeb,
    Freeable (..),
  )
where

import Control.Monad (void)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Raylib.Core (beginBlendMode, beginDrawing, beginMode2D, beginMode3D, beginScissorMode, beginShaderMode, beginTextureMode, beginVrStereoMode, closeWindow, endBlendMode, endDrawing, endMode2D, endMode3D, endScissorMode, endShaderMode, endTextureMode, endVrStereoMode, initWindow, setTargetFPS, windowShouldClose)
import Raylib.Internal (WindowResources, Closeable (..), managed)
import Raylib.Internal.Foreign (Freeable (..))
import Raylib.Types
  ( BlendMode,
    Camera2D,
    Camera3D (camera3D'position, camera3D'target),
    Material (material'shader),
    Model (model'materials),
    Ray (Ray),
    RenderTexture,
    Shader,
    VrStereoConfig,
  )
import Raylib.Util.Math (Vector (vectorNormalize, (|-|)))
import Control.Exception (bracket, bracket_)

#ifdef WEB_FFI

import Foreign (Ptr, castPtrToStablePtr, castStablePtrToPtr, deRefStablePtr, freeStablePtr, newStablePtr)
import Language.Haskell.TH (Body (NormalB), Callconv (CCall), Clause (Clause), Dec (ForeignD, FunD, SigD), DecsQ, Exp (AppE, VarE), Foreign (ExportF), Name, Pat (VarP), Q, Type (AppT, ArrowT, ConT, TupleT), mkName, ppr, reifyType)
import Language.Haskell.TH.Syntax (Name (Name), OccName (OccName))

#else

import Language.Haskell.TH (Name, DecsQ, Type (AppT, ConT, ArrowT, TupleT), Q, reifyType, mkName, ppr, Dec (SigD, FunD), Clause (Clause), Body (NormalB), Exp (VarE, AppE))
import Language.Haskell.TH.Syntax (Name (Name), OccName (OccName))

#endif



-- | NOTE: Only for native targets. If your program is intended to
--         run on the web, use `raylibApplication` instead.
withWindow ::
  -- | Window width
  Int ->
  -- | Window height
  Int ->
  -- | Window title
  String ->
  -- | Target FPS
  Int ->
  (WindowResources -> IO b) ->
  IO b
withWindow w h title fps = bracket (liftIO $ initWindow w h title <* setTargetFPS fps) (liftIO . closeWindow . Just)

drawing :: IO b -> IO b
drawing = bracket_ (liftIO beginDrawing) (liftIO endDrawing)

mode2D :: Camera2D -> IO b -> IO b
mode2D camera = bracket_ (liftIO (beginMode2D camera)) (liftIO endMode2D)

mode3D :: Camera3D -> IO b -> IO b
mode3D camera = bracket_ (liftIO (beginMode3D camera)) (liftIO endMode3D)

textureMode :: RenderTexture -> IO b -> IO b
textureMode rt = bracket_ (liftIO (beginTextureMode rt)) (liftIO endTextureMode)

shaderMode :: Shader -> IO b -> IO b
shaderMode shader = bracket_ (liftIO (beginShaderMode shader)) (liftIO endShaderMode)

blendMode :: BlendMode -> IO b -> IO b
blendMode bm = bracket_ (liftIO (beginBlendMode bm)) (liftIO endBlendMode)

scissorMode :: Int -> Int -> Int -> Int -> IO b -> IO b
scissorMode x y width height = bracket_ (liftIO (beginScissorMode x y width height)) (liftIO endScissorMode)

vrStereoMode :: VrStereoConfig -> IO b -> IO b
vrStereoMode config = bracket_ (liftIO (beginVrStereoMode config)) (liftIO endVrStereoMode)

-- | Gets the direction of a camera as a ray.
cameraDirectionRay :: Camera3D -> Ray
cameraDirectionRay camera = Ray (camera3D'position camera) (vectorNormalize $ camera3D'target camera |-| camera3D'position camera)

-- | Creates a raylib application using the given program functions. Supports
--   both native and web targets, so it is recommended for all programs. If
--   your program is intended only for native use, you may manually write a
--   @main@ function.
--
--   On a native (non-web) target, it simply creates a @main@ function that
--   uses the startup, mainLoop, shouldClose, and teardown functions. When
--   building with @platform-web@ enabled, it creates four @foreign export@
--   statements (@startup@, @mainLoop@, @shouldClose@, and @teardown@), which
--   will be called through the browser.
--
--   See @raygui-suite@ for an example of how to use it.
raylibApplication ::
  -- | The startup function, should be of type @IO AppState@
  Name ->
  -- | The mainLoop function, should be of type @AppState -> IO AppState@
  Name ->
  -- | The shouldClose function, should be of type @AppState -> IO Bool@
  Name ->
  -- | The teardown function, should be of type @AppState -> IO ()@
  Name ->
  DecsQ

#ifdef WEB_FFI

raylibApplication
  startup@(Name (OccName sun) _)
  mainLoop@(Name (OccName mln) _)
  shouldClose@(Name (OccName scn) _)
  teardown@(Name (OccName tdn) _) =
    do
      assertTypes startup mainLoop shouldClose teardown

      let _startupN = mkName ('_' : sun)
          _startupT = ConT ''IO `AppT` (ConT ''Ptr `AppT` TupleT 0)
          _startupS = SigD _startupN _startupT -- _startup :: IO (Ptr ())
          _startupF =
            -- _startup = startup >>= createStablePtr
            FunD
              _startupN
              [Clause [] (NormalB ((VarE '(>>=) `AppE` VarE startup) `AppE` VarE 'createStablePtr)) []]

      let _mainLoopN = mkName ('_' : mln)
          _mainLoopPtrN = mkName "ptr"
          _mainLoopT = (ArrowT `AppT` (ConT ''Ptr `AppT` TupleT 0)) `AppT` (ConT ''IO `AppT` (ConT ''Ptr `AppT` TupleT 0))
          _mainLoopS = SigD _mainLoopN _mainLoopT -- _mainLoop :: Ptr () -> IO (Ptr ())
          _mainLoopF =
            -- _mainLoop ptr = popStablePtr ptr >>= mainLoop >>= createStablePtr
            FunD
              _mainLoopN
              [ Clause
                  [VarP _mainLoopPtrN]
                  (NormalB ((VarE '(>>=) `AppE` ((VarE '(>>=) `AppE` (VarE 'popStablePtr `AppE` VarE _mainLoopPtrN)) `AppE` VarE mainLoop)) `AppE` VarE 'createStablePtr))
                  []
              ]

      let _shouldCloseN = mkName ('_' : scn)
          _shouldClosePtrN = mkName "ptr"
          _shouldCloseT = (ArrowT `AppT` (ConT ''Ptr `AppT` TupleT 0)) `AppT` (ConT ''IO `AppT` ConT ''Bool)
          _shouldCloseS = SigD _shouldCloseN _shouldCloseT -- _shouldClose :: Ptr () -> IO Bool
          _shouldCloseF =
            -- _shouldClose ptr = readStablePtr ptr >>= P.shouldClose
            FunD
              _shouldCloseN
              [ Clause
                  [VarP _shouldClosePtrN]
                  (NormalB ((VarE '(>>=) `AppE` (VarE 'readStablePtr `AppE` VarE _shouldClosePtrN)) `AppE` VarE shouldClose))
                  []
              ]

      let _teardownN = mkName ('_' : tdn)
          _teardownPtrN = mkName "ptr"
          _teardownT = (ArrowT `AppT` (ConT ''Ptr `AppT` TupleT 0)) `AppT` (ConT ''IO `AppT` TupleT 0)
          _teardownS = SigD _teardownN _teardownT -- _teardown :: Ptr () -> IO ()
          _teardownF =
            -- _teardown ptr = popStablePtr ptr >>= teardown
            FunD
              _teardownN
              [ Clause
                  [VarP _teardownPtrN]
                  (NormalB ((VarE '(>>=) `AppE` (VarE 'popStablePtr `AppE` VarE _teardownPtrN)) `AppE` VarE teardown))
                  []
              ]
      
      return
        [ _startupS,
          _startupF,
          _mainLoopS,
          _mainLoopF,
          _shouldCloseS,
          _shouldCloseF,
          _teardownS,
          _teardownF,
          ForeignD (ExportF CCall "startup" _startupN _startupT),
          ForeignD (ExportF CCall "mainLoop" _mainLoopN _mainLoopT),
          ForeignD (ExportF CCall "shouldClose" _shouldCloseN _shouldCloseT),
          ForeignD (ExportF CCall "teardown" _teardownN _teardownT)
        ]

createStablePtr :: a -> IO (Ptr ())
createStablePtr val = castStablePtrToPtr <$> newStablePtr val

readStablePtr :: Ptr () -> IO a
readStablePtr ptr = deRefStablePtr $ castPtrToStablePtr ptr

popStablePtr :: Ptr () -> IO a
popStablePtr ptr = do
  let sptr = castPtrToStablePtr ptr
  val <- deRefStablePtr sptr
  freeStablePtr sptr
  return val

#else

raylibApplication startup mainLoop shouldClose teardown = do
  assertTypes startup mainLoop shouldClose teardown

  return
    [
      -- main :: IO ()
      SigD main (ConT ''IO `AppT` TupleT 0),
      -- main = runRaylibProgram startup mainLoop shouldClose teardown
      FunD main [Clause [] (NormalB ((((VarE 'runRaylibProgram `AppE` VarE startup) `AppE` VarE mainLoop) `AppE` VarE shouldClose) `AppE` VarE teardown)) []]
    ]
  where main = mkName "main"

runRaylibProgram :: IO a -> (a -> IO a) -> (a -> IO Bool) -> (a -> IO ()) -> IO ()
runRaylibProgram startup mainLoop shouldClose teardown = do
  st <- startup
  helper st
  where helper s = shouldClose s >>= (\toClose -> if toClose then teardown s else mainLoop s >>= helper)

#endif

assertTypes :: Name -> Name -> Name -> Name -> Q ()
assertTypes startup mainLoop shouldClose teardown = do
  sut <- reifyType startup
  state <-
    case sut of
      m `AppT` st ->
        if m == ConT ''IO
          then return st
          else typeErr startup (ConT ''IO `AppT` ConT (mkName "AppState")) sut
      _ -> typeErr startup (ConT ''IO `AppT` ConT (mkName "AppState")) sut

  mlt <- reifyType mainLoop
  assertType mainLoop ((ArrowT `AppT` state) `AppT` (ConT ''IO `AppT` state)) mlt

  sct <- reifyType shouldClose
  assertType shouldClose ((ArrowT `AppT` state) `AppT` (ConT ''IO `AppT` ConT ''Bool)) sct

  tdt <- reifyType teardown
  assertType teardown ((ArrowT `AppT` state) `AppT` (ConT ''IO `AppT` TupleT 0)) tdt

assertType :: Name -> Type -> Type -> Q ()
assertType n expected actual = if expected == actual then return () else typeErr n expected actual

typeErr :: Name -> Type -> Type -> a
typeErr (Name (OccName n) _) expected actual =
  error (n ++ " was not the expected type\n\nexpected " ++ show (ppr expected) ++ "\n\ngot " ++ show (ppr actual) ++ "\n")

-- | Calls the game loop every frame as long as the window is open.
--   For larger projects, instead of using this function, consider
--   making a custom game loop for flexibility.
--
--   NOTE: Only for native targets. If your program is intended to
--         run on the web, use `raylibApplication` instead.
whileWindowOpen ::
  (MonadIO m) =>
  -- | The game loop. Its only argument should be the current application state, and it should return a new state.
  (a -> m a) ->
  -- | The initial application state.
  a ->
  -- | The application state after the last frame.
  m a
whileWindowOpen f state = do
  newState <- f state
  shouldClose <- liftIO windowShouldClose
  if shouldClose
    then return newState
    else whileWindowOpen f newState

-- | Same as `whileWindowOpen`, but discards the final state.
whileWindowOpen_ ::
  (MonadIO m) =>
  (a -> m a) ->
  a ->
  m ()
whileWindowOpen_ f state = void (whileWindowOpen f state)

-- | Same as `whileWindowOpen`, but without application state.
whileWindowOpen0 ::
  (MonadIO m) =>
  m () ->
  m ()
whileWindowOpen0 f = whileWindowOpen (const f) ()

-- | Sets the shader of a material at a specific index (WARNING: This will fail
-- if the index provided is out of bounds).
setMaterialShader ::
  -- | The model to operate on
  Model ->
  -- | The index of the material
  Int ->
  -- | The shader to use
  Shader ->
  -- | The modified model
  Model
setMaterialShader model matIdx shader = model {model'materials = setIdx mats matIdx newMat}
  where
    mats = model'materials model
    newMat = (mats !! matIdx) {material'shader = shader}
    setIdx l i v = take i l ++ [v] ++ drop (i + 1) l

-- | True if the program is running in GHCi
inGHCi :: Bool

#ifdef GHCI
inGHCi = True
#else
inGHCi = False
#endif

-- | True if the program is running in the web
inWeb :: Bool

#ifdef WEB_FFI
inWeb = True
#else
inWeb = False
#endif