packages feed

brillo-2.0.0: Brillo/Internals/Interface/Game.hs

{-# LANGUAGE RankNTypes #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}

{-# HLINT ignore "Use camelCase" #-}
{-# HLINT ignore "Use list comprehension" #-}

module Brillo.Internals.Interface.Game (
  playWithBackendIO,
  Event (..),
)
where

import Brillo.Data.Color
import Brillo.Data.Picture
import Brillo.Data.ViewPort
import Brillo.Internals.Interface.Animate.State qualified as AN
import Brillo.Internals.Interface.Animate.Timing
import Brillo.Internals.Interface.Backend
import Brillo.Internals.Interface.Callback qualified as Callback
import Brillo.Internals.Interface.Common.Exit
import Brillo.Internals.Interface.Event
import Brillo.Internals.Interface.Simulate.Idle
import Brillo.Internals.Interface.Simulate.State qualified as SM
import Brillo.Internals.Interface.ViewState.Reshape
import Brillo.Internals.Interface.Window
import Brillo.Rendering
import Data.IORef
import System.Mem


playWithBackendIO ::
  forall world a.
  (Backend a) =>
  -- | Initial state of the backend
  a ->
  -- | Display mode.
  Display ->
  -- | Background color.
  Color ->
  -- | Number of simulation steps to take for each second of real time.
  Int ->
  -- | The initial world.
  world ->
  -- | A function to convert the world to a picture.
  (world -> IO Picture) ->
  -- | A function to handle input events.
  (Event -> world -> IO world) ->
  {-| A function to step the world one iteration.
  It is passed the period of time (in seconds) needing to be advanced.
  -}
  (Float -> world -> IO world) ->
  -- | Whether to use the callback_exit or not.
  Bool ->
  IO ()
playWithBackendIO
  backend
  display
  backgroundColor
  simResolution
  worldStart
  worldToPicture
  worldHandleEvent
  worldAdvance
  withCallbackExit =
    do
      let singleStepTime = 1

      -- make the simulation state
      stateSR <- newIORef $ SM.stateInit simResolution

      -- make a reference to the initial world
      worldSR <- newIORef worldStart

      -- make the initial GL view and render states
      viewSR <- newIORef viewPortInit
      animateSR <- newIORef AN.stateInit
      renderS_ <- initState
      renderSR <- newIORef renderS_

      let displayFun backendRef =
            do
              -- convert the world to a picture
              world <- readIORef worldSR
              picture <- worldToPicture world

              -- display the picture in the current view
              renderS <- readIORef renderSR
              viewPort <- readIORef viewSR

              windowSize <- getWindowDimensions backendRef

              -- render the frame
              displayPicture
                windowSize
                backgroundColor
                renderS
                (viewPortScale viewPort)
                (applyViewPortToPicture viewPort picture)

              -- perform GC every frame to try and avoid long pauses
              performGC

      let callbacks =
            [ Callback.Display (animateBegin animateSR)
            , Callback.Display displayFun
            , Callback.Display (animateEnd animateSR)
            , Callback.Idle
                ( callback_simulate_idle
                    stateSR
                    animateSR
                    (readIORef viewSR)
                    worldSR
                    (const worldAdvance)
                    singleStepTime
                )
            , callback_keyMouse worldSR viewSR worldHandleEvent
            , callback_motion worldSR worldHandleEvent
            , callback_drop worldSR worldHandleEvent
            , callback_reshape worldSR worldHandleEvent
            ]

      let exitCallback =
            if withCallbackExit then [callback_exit ()] else []

      createWindow
        backend
        display
        backgroundColor
        (callbacks ++ exitCallback)
        (\_ -> return ())


-- | Callback for KeyMouse events.
callback_keyMouse ::
  -- | ref to world state
  IORef world ->
  IORef ViewPort ->
  -- | fn to handle input events
  (Event -> world -> IO world) ->
  Callback
callback_keyMouse worldRef viewRef eventFn =
  KeyMouse (handle_keyMouse worldRef viewRef eventFn)


handle_keyMouse ::
  IORef a ->
  t ->
  (Event -> a -> IO a) ->
  KeyboardMouseCallback
handle_keyMouse worldRef _ eventFn backendRef key keyState keyMods pos =
  do
    ev <- keyMouseEvent backendRef key keyState keyMods pos
    world <- readIORef worldRef
    world' <- eventFn ev world
    writeIORef worldRef world'


-- | Callback for Motion events.
callback_motion ::
  -- | ref to world state
  IORef world ->
  -- | fn to handle input events
  (Event -> world -> IO world) ->
  Callback
callback_motion worldRef eventFn =
  Motion (handle_motion worldRef eventFn)


handle_motion ::
  IORef a ->
  (Event -> a -> IO a) ->
  MotionCallback
handle_motion worldRef eventFn backendRef pos =
  do
    ev <- motionEvent backendRef pos
    world <- readIORef worldRef
    world' <- eventFn ev world
    writeIORef worldRef world'


callback_drop ::
  IORef world ->
  (Event -> world -> IO world) ->
  Callback
callback_drop worldRef eventFn =
  Drop (handle_drop worldRef eventFn)


handle_drop ::
  IORef a ->
  (Event -> a -> IO a) ->
  DropCallback
handle_drop worldRef eventFn backendRef paths = do
  ev <- dropEvent backendRef paths
  world <- readIORef worldRef
  world' <- eventFn ev world
  writeIORef worldRef world'


-- | Callback for Handle reshape event.
callback_reshape ::
  IORef world ->
  (Event -> world -> IO world) ->
  Callback
callback_reshape worldRef eventFN =
  Reshape (handle_reshape worldRef eventFN)


handle_reshape ::
  IORef world ->
  (Event -> world -> IO world) ->
  ReshapeCallback
handle_reshape worldRef eventFn stateRef (width, height) =
  do
    world <- readIORef worldRef
    world' <- eventFn (EventResize (width, height)) world
    writeIORef worldRef world'
    viewState_reshape stateRef (width, height)