7

How can I issue multiple calls to SDL.pollEvent :: IO Event until the output is SDL.NoEvent and collect all the results into a list?

In imperative terms something like this:

events = []
event = SDL.pollEvent
while ( event != SDL.NoEvent ) {
        events.add( event )
        event = SDL.pollEvent
}
Flow
  • 23,572
  • 15
  • 99
  • 156
user341228
  • 195
  • 6

5 Answers5

4

You could use something like:

takeWhileM :: (a -> Bool) -> IO a -> IO [a]
takeWhileM p act = do
  x <- act
  if p x
    then do
      xs <- takeWhileM p act
      return (x : xs)
    else
      return []

Instead of:

do
  xs <- takeWhileM p act
  return (x : xs)

you can also use:

liftM (x:) (takeWhileM p act) yielding:

takeWhileM :: (a -> Bool) -> IO a -> IO [a]
takeWhileM p act = do
  x <- act
  if p x
    then liftM (x:) (takeWhileM p act)
    else return []

Then you can use: takeWhileM (/=SDL.NoEvent) SDL.pollEvent

Peaker
  • 2,354
  • 1
  • 14
  • 19
  • I'd suggest `takeUntilM :: Monad m => (a -> Bool) -> m a -> m [a]` (with appropriate `return [x]` when `p x` is `false`) to avoid information loss (especially from IO monads). That may look normal when it just `SDL.NoEvent` but it might be wrong for `Left "system crash" :: Either String a`. – ony May 16 '10 at 17:49
  • Oh, and you probably want to build lazy list, so need to use `interleaveIO` from `System.Unsafe` (or something like that). I.e. something like `liftM (x:) (interleaveIO (unsafeTakeUntilM p act))` – ony May 16 '10 at 17:56
  • 1
    A lot of variants of `takeWhileM`: http://stackoverflow.com/questions/1133800/haskell-monadic-takewhile/1138153#1138153 – kennytm May 16 '10 at 18:47
  • `spanM` looks nice. So `curry (liftM fst . uncurry (flip (flip spanM . repeat)))` :) – ony May 16 '10 at 19:07
  • 1
    @peaker: imho using monadic lists is more modular – yairchu May 16 '10 at 20:44
  • 2
    @ony: Why lazy? This is polling events in the main loop of an SDL application. Logically, it's better to think of SDL pushing events to the program, not the program pulling events on demand. In fact, I think SDL will fall over if you don't clear the event queue quickly enough. Sometimes laziness doesn't make sense. – C. A. McCann May 16 '10 at 22:42
  • 1
    @ony: lazy-IO is bug prone. But if one would still insist on doing it, I don't think that creating a lazy `IO` version of `takeWhileM` is a nice and modular approach. Transforming to a lazy list should be done as a separate step from `takeWhile`. this could be achieved using monadic lists. (shameless plug for my own answer below) – yairchu May 16 '10 at 23:38
  • i actually want poll all events away from at the start of a gameloop iteration, so the IO stuff doesn't interfere with the functional game logic – user341228 May 17 '10 at 00:37
  • @camccann: I don't think that leaving general memory overflow (because of list growing faster than consumers process it) resolving to the Haskell system is a good idea. I guess, SDL will have more specific way of handling event queue overflow (reducing frames, dropping interval timeouts, skipping mouse positions, beeping on key pressings etc). I agree that lazy list bug prone and it's better to do some kind of `mapSDLEvents_ :: (Event -> IO ()) -> IO ()` or `foldSDLEvents :: (a -> Event -> IO a) -> a -> IO a`. @yairchu: yes `spanM :: (a -> Bool) -> [m a] -> m ([a], [m a])` is good for that. – ony May 17 '10 at 04:55
4

James Cook was so kind to extend monad-loops with this function:

unfoldWhileM  :: Monad  m => (a -> Bool) -> m a -> m [a]

used with SDL:

events <- unfoldWhileM (/= SDL.NoEvent) SDL.pollEvent
Gerold Meisinger
  • 4,500
  • 5
  • 26
  • 33
2

You can use monadic lists:

import Control.Monad.ListT (ListT)
import Control.Monad.Trans.Class (lift) -- transformers, not mtl
import Data.List.Class (takeWhile, repeat, toList)
import Prelude hiding (takeWhile, repeat)

getEvents :: IO [Event]
getEvents = 
    toList . takeWhile (/= NoEvent) $ do
        repeat ()
        lift pollEvent :: ListT IO Event

ListT from the "List" package on hackage.

yairchu
  • 23,680
  • 7
  • 69
  • 109
  • 1
    @peaker: `repeat () :: ListT IO ()` is an infinite IO-monadic list containing values that don't matter (`()`). then we `(>>)` it with `lift pollEvent` so that for each element of the infinite list we `pollEvent`. `takeWhile` makes it a finite monadic list and then `toList` makes it `:: IO [Event]`. – yairchu May 16 '10 at 15:54
  • That seems a bit weird.. Maybe makes more sense to use something like "repeatM (lift pollEvent)" ? – Peaker May 17 '10 at 06:29
  • @peaker: yeah, `repeatM pollEvent`. I added `repeatM` to the github tree and it will be there in the next hackage version – yairchu May 18 '10 at 07:00
1

Using these stubs for Event and pollEvent

data Event = NoEvent | SomeEvent
  deriving (Show,Eq)

instance Random Event where
  randomIO = randomRIO (0,1) >>= return . ([NoEvent,SomeEvent] !!)

pollEvent :: IO Event
pollEvent = randomIO

and a combinator, borrowed and adapted from an earlier answer, that stops evaluating the first time the predicate fails

spanM :: (Monad m) => (a -> Bool) -> m a -> m [a]
spanM p a = do
  x <- a
  if p x then do xs <- spanM p a
                 return (x:xs)
         else return [x]

allows this ghci session, for example:

*Main> spanM (/= NoEvent) pollEvent 
[SomeEvent,SomeEvent,NoEvent]
Community
  • 1
  • 1
Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
0

i eventually stumbled over this code snippet in an actual SDL game from hackage

getEvents :: IO Event -> [Event] -> IO [Event]
getEvents pEvent es = do
  e <- pEvent
  let hasEvent = e /= NoEvent
  if hasEvent
   then getEvents pEvent (e:es)
   else return (reverse es)

thanks for your answers btw!

user341228
  • 195
  • 6
  • 1
    If that is so popular approach to pull out all events waiting in queue in one shot without processing it, than why SDL API doesn't provide it directly? That may help to avoid some sync. overhead for thread-safe queue. – ony May 17 '10 at 13:06