简体   繁体   中英

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?

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).

The question doesn't make sense for pure functions since Haskell does lazy evaluation. A function will be called when its results are needed, not any earlier, and not later.

However, if you have some IO operation you want delayed, you can in GHC use the threadDelay function from Control.Concurrent . Your setTimeout would then be implemented as

setTimeout :: IO () -> Int -> IO ThreadId

setTimeout ioOperation ms =
  forkIO $ do
    threadDelay (ms*1000)
    ioOperation

This is an old question, but I'm not satisfied with the answers (and none have been marked as correct), as there's too much focus on the reimplementing setTimeout function, which I think is a misinterpretation of what setTimeout is used for in making games with javascript.

Can I assume you're wanting a replacement for this javascript pattern?

function update() { 
    undefined;
    setInterval(update, 1000)
}

Game loops outside of Javascript are not usually written using setTimeout , setTimeout is a work around from the asynchronous nature of JS in the browser. as an aside, it's not recommended to use setTimeout for time critical visual code, requestAnimationFrame is the optimal function for this.

The idiomatic method for programming a game loop is a simple loop, pseudo haskell code would look like this:

draw :: World -> IO ()
draw w = do
    undefined
    glSwapBuffers

updateWorld :: World -> World
updateWorld w = undefined

loop World -> Time -> IO ()
loop world time = do
    inputs <- getInputs
    time' <- getTime
    let delta = time' - time
    let world' = updateWorld delta world inputs
    draw world'
    loop world' time'

setup :: IO World
setup = do
    undefined -- window setup etc
    world <- undefined
    return world

main :: IO ()
main = do
    w <- setup
    t <- getTime
    loop w t

Your draw function should call glSwapBuffers (or equivalent), which on most devices will block until vsync, that'll maintain a smooth fps, But do not assume that all computers have vsync enabled, and do not assume that all monitors are 60hz, 144hz and variable frame rate technology is now prevalent in the gamer community.

A loop like this will also safely degrade if it cannot make the 16.67ms window (unlike anything using forkIO to build a loop), though haskell's microthreads, make it not insane to program using them, but make sure use of them is thread safe, for example uses something like STM .

the clock or time packages will provide the getTime function.

Zeta's mkGame is more functional style (as it takes functions as arguments), and can be easily adapted to my code.

Also note, libraries like sdl, provide their own loop and input, and timekeeping methods, which may simplify (or complicate) your code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM