/hugs98-Nov2003/fptools/libraries/GLUT/Graphics/UI/GLUT/Callbacks/Registration.hs

https://github.com/gitpan/Language-Haskell · Haskell · 162 lines · 90 code · 35 blank · 37 comment · 5 complexity · a432df684d117e8e034234ef1dce5453 MD5 · raw file

  1. -- #hide
  2. --------------------------------------------------------------------------------
  3. -- |
  4. -- Module : Graphics.UI.GLUT.Callbacks.Registration
  5. -- Copyright : (c) Sven Panne 2003
  6. -- License : BSD-style (see the file libraries/GLUT/LICENSE)
  7. --
  8. -- Maintainer : sven_panne@yahoo.com
  9. -- Stability : provisional
  10. -- Portability : portable
  11. --
  12. --------------------------------------------------------------------------------
  13. module Graphics.UI.GLUT.Callbacks.Registration (
  14. CallbackType(..), registerForCleanup, setCallback
  15. ) where
  16. --------------------------------------------------------------------------------
  17. import Control.Monad ( liftM, when )
  18. import Data.FiniteMap ( FiniteMap, emptyFM, lookupFM, addToFM, delFromFM )
  19. import Data.IORef ( IORef, newIORef, readIORef, writeIORef, modifyIORef )
  20. import Foreign.C.Types ( CInt, CUInt )
  21. import Foreign.Ptr ( FunPtr, nullFunPtr, freeHaskellFunPtr )
  22. import System.IO.Unsafe ( unsafePerformIO )
  23. import Graphics.Rendering.OpenGL.GL.StateVar ( HasGetter(get) )
  24. import Graphics.UI.GLUT.Window ( Window, currentWindow )
  25. --------------------------------------------------------------------------------
  26. -- No timer callback here, because they are one-shot and "self destroy"
  27. data CallbackType
  28. = DisplayCB | OverlayDisplayCB | ReshapeCB
  29. | KeyboardCB | KeyboardUpCB | MouseCB
  30. | MotionCB | PassiveMotionCB | CrossingCB
  31. | VisibilityCB | WindowStatusCB | SpecialCB
  32. | SpecialUpCB | SpaceballMotionCB | SpaceballRotateCB
  33. | SpaceballButtonCB | ButtonBoxCB | DialsCB
  34. | TabletMotionCB | TabletButtonCB | JoystickCB
  35. | MenuStatusCB | IdleCB
  36. deriving ( Eq, Ord, Show )
  37. isGlobal :: CallbackType -> Bool
  38. isGlobal MenuStatusCB = True
  39. isGlobal IdleCB = True
  40. isGlobal _ = False
  41. --------------------------------------------------------------------------------
  42. -- To uniquely identify a particular callback, the associated window is needed
  43. -- for window callbacks.
  44. data CallbackID = CallbackID (Maybe Window) CallbackType
  45. deriving ( Eq, Ord, Show )
  46. getCallbackID :: CallbackType -> IO CallbackID
  47. getCallbackID callbackType = do
  48. maybeWindow <- if isGlobal callbackType
  49. then return Nothing
  50. else liftM Just $ get currentWindow
  51. return $ CallbackID maybeWindow callbackType
  52. --------------------------------------------------------------------------------
  53. -- This seems to be a common Haskell hack nowadays: A plain old global variable
  54. -- with an associated mutator. Perhaps some language/library support is needed?
  55. {-# notInline theCallbackTable #-}
  56. theCallbackTable :: IORef (CallbackTable a)
  57. theCallbackTable = unsafePerformIO (newIORef emptyCallbackTable)
  58. getCallbackTable :: IO (CallbackTable a)
  59. getCallbackTable = readIORef theCallbackTable
  60. modifyCallbackTable :: (CallbackTable a -> CallbackTable a) -> IO ()
  61. modifyCallbackTable = modifyIORef theCallbackTable
  62. --------------------------------------------------------------------------------
  63. type CallbackTable a = FiniteMap CallbackID (FunPtr a)
  64. emptyCallbackTable :: CallbackTable a
  65. emptyCallbackTable = emptyFM
  66. lookupInCallbackTable :: CallbackID -> IO (Maybe (FunPtr a))
  67. lookupInCallbackTable callbackID =
  68. liftM (flip lookupFM callbackID) getCallbackTable
  69. deleteFromCallbackTable :: CallbackID -> IO ()
  70. deleteFromCallbackTable callbackID =
  71. modifyCallbackTable (flip delFromFM callbackID)
  72. addToCallbackTable :: CallbackID -> FunPtr a -> IO ()
  73. addToCallbackTable callbackID funPtr =
  74. modifyCallbackTable (\table -> addToFM table callbackID funPtr)
  75. --------------------------------------------------------------------------------
  76. -- Another global mutable variable: The list of function pointers ready to be
  77. -- freed by freeHaskellFunPtr
  78. {-# notInline theCleanupList #-}
  79. theCleanupList :: IORef [FunPtr a]
  80. theCleanupList = unsafePerformIO (newIORef [])
  81. getCleanupList :: IO [FunPtr a]
  82. getCleanupList = readIORef theCleanupList
  83. setCleanupList :: [FunPtr a] -> IO ()
  84. setCleanupList = writeIORef theCleanupList
  85. --------------------------------------------------------------------------------
  86. -- And yet another mutable (write-once) variable: A function pointer to a
  87. -- callback which frees all function pointers on the cleanup list.
  88. {-# notInline theScavenger #-}
  89. theScavenger :: IORef (FunPtr TimerCallback)
  90. theScavenger = unsafePerformIO (newIORef =<< makeTimerCallback (\_ -> do
  91. cleanupList <- getCleanupList
  92. mapM_ freeHaskellFunPtr cleanupList
  93. setCleanupList []))
  94. getScavenger :: IO (FunPtr TimerCallback)
  95. getScavenger = readIORef theScavenger
  96. -- More or less copied from Global.hs to avoid mutual dependencies
  97. type TimerCallback = CInt -> IO ()
  98. foreign import ccall "wrapper" makeTimerCallback ::
  99. TimerCallback -> IO (FunPtr TimerCallback)
  100. foreign import CALLCONV unsafe "glutTimerFunc" glutTimerFunc ::
  101. CUInt -> FunPtr TimerCallback -> CInt -> IO ()
  102. --------------------------------------------------------------------------------
  103. -- Here is the really cunning stuff: If an element is added to the cleanup list
  104. -- when it is empty, register an immediate callback at GLUT to free the list as
  105. -- soon as possible.
  106. registerForCleanup :: FunPtr a -> IO ()
  107. registerForCleanup funPtr = do
  108. oldCleanupList <- getCleanupList
  109. setCleanupList (funPtr : oldCleanupList)
  110. when (null oldCleanupList) $ do
  111. scavenger <- getScavenger
  112. glutTimerFunc 0 scavenger 0
  113. --------------------------------------------------------------------------------
  114. setCallback :: CallbackType -> (FunPtr a -> IO ()) -> (b -> IO (FunPtr a))
  115. -> Maybe b -> IO ()
  116. setCallback callbackType registerAtGLUT makeCallback maybeCallback = do
  117. callbackID <- getCallbackID callbackType
  118. maybeOldFunPtr <- lookupInCallbackTable callbackID
  119. case maybeOldFunPtr of
  120. Nothing -> return ()
  121. Just oldFunPtr -> do registerForCleanup oldFunPtr
  122. deleteFromCallbackTable callbackID
  123. case maybeCallback of
  124. Nothing -> registerAtGLUT nullFunPtr
  125. Just callback -> do newFunPtr <- makeCallback callback
  126. addToCallbackTable callbackID newFunPtr
  127. registerAtGLUT newFunPtr