{-# LANGUAGE NoImplicitPrelude #-} module Imj.Threading ( -- * Threads {- | We use a separate thread to run the game, to be able to catch Ctrl-C related exception, and reset console settings before quitting. It doesn't seem to always work, maybe we should use instead. -} runAndWaitForTermination , Termination(..) , setupCapabilities ) where import Imj.Prelude import qualified Prelude import GHC.Conc(getNumProcessors) import Control.Concurrent( forkFinally , MVar , newEmptyMVar , putMVar , readMVar , setNumCapabilities ) import Control.Exception( SomeException(..) ) import Control.Monad( (>=>) ) -- | Was the thread termination normal or due to an error? data Termination = NormalTermination | AbnormalTermination -- | Runs an IO action in a separate thread, and waits for it to finish, -- returning its result. runAndWaitForTermination :: IO () -> IO Termination runAndWaitForTermination io = do --setupCapabilities -- launch game thread gameThreadTerminated <- myForkIO io -- wait for game thread to finish readMVar gameThreadTerminated -- | Sets the number of capabilities to half the number of processors. -- Not used at the moment since we don't use parallelism too much. setupCapabilities :: IO () setupCapabilities = do nproc <- getNumProcessors let ncap = max 1 $ quot nproc 2 setNumCapabilities ncap -- This function was introduced so that the parent thread can wait on the -- returned MVar to be set to know that the child thread has terminated. -- cf https://hackage.haskell.org/package/base-4.10.0.0/docs/Control-Concurrent.html#g:12 myForkIO :: IO () -> IO (MVar Termination) myForkIO io = do mvar <- newEmptyMVar _ <- forkFinally io (handleTerminationCause >=> putMVar mvar) return mvar handleTerminationCause :: Either SomeException a -> IO Termination handleTerminationCause (Left e) = do Prelude.putStrLn ("From game thread:\n" ++ show e) return AbnormalTermination handleTerminationCause _ = return NormalTermination