Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a Game Event Loop (e.g. setTimeout) in Haskell?

First

Does there exist or how much fuss would it be to implement a setTimeout function in Haskell?

The main idea is

setTimeout someFunction 1000 --someFunction will be called in 1000ms = 1s

I am new to Haskell and probably will not use this; I am learning Haskell for the kick of it.


Second

A game's event loop usually requires you to

  1. Modify your internal game objects based on rules (usually physics)
  2. Update your graphics

Managing time is a great deal (hence setTimeout) here to ensure constant FPS.

How would a pattern like this look on Haskell?

like image 291
Cristian Garcia Avatar asked Dec 26 '22 09:12

Cristian Garcia


1 Answers

Does there exist or how much fuss would it be to implement a setTimeout function in Haskell?

JavaScript's setTimeout and other similar methods like Qt's QTimer usually work within a single event loop in a single thread (not counting web workers or QThread). Modelling that exact behaviour is a little bit hard, although not impossible, with Haskell, as GHC already provides an (internal) implementation.

If you don't care for the actual single-threaded behaviour (which also means that two functions with almost the same timeout can possibly execute at the same time), then you can simply fork a new thread and delay its action:

doLater :: Int -> IO () -> IO ThreadId
doLater ms io = forkIO $ threadDelay (ms * 1000) >> io

For any further thoughts, we would actually need to know more about your specific event loop.

How would a pattern like this look on Haskell?

Very, very generalised, you would need something like this:

mkGame :: World 
          -> (Input -> World -> World) 
          -> (TimeDiff -> World -> World)
          -> (World -> GraphicalRepresentation)
          -> IO ()
mkGame initialWorld handleInput handleProgression drawWorld = undefined

Note that you can probably throw other arguments in there, such as the maximum number of world updates per second.

How could we now model setTimeout? Assume that World is something like:

data World = World { 
     getWorldData    :: WorldData, 
     getCurrentTime  :: TimePoint, -- would get updated by your loop
     getTimeouts     :: Queue TimePoint (World -> World)
}

where Queue is any working priority queue (pick one, there are many, or build your own). Now you can simply set a timeout by using

setTimeout world delay action = world { getTimeouts = timeouts' }
    where timeouts' = insert (getTimeouts world) t action
          t         = delay + getCurrentTime world

-- insert :: Queue p v -> p -> v -> Queue p v

If you want to be able to cancel timeouts, you also need a key on Queue, have a look at GHC.Event.PSQ for inspiration.

Aside from that you can now check whether the time has passed and act accordingly in your game loop by simply going through the queue, and applying all your functions.

This is basically a very simple and crude foundation you can use to inspire your own work on. Depending on what you actually want to do, you might want to have a look at gloss, which already implements a very similar concept, although without timeouts, but in this case you can still add the timeouts and the total time difference in to your world and simply use a fitting update function (the TimeDiff -> World -> World part).

like image 164
Zeta Avatar answered Jan 09 '23 06:01

Zeta