Core Game Loops in Nim with Unhygenic Templates
Created on 2022-07-21T19:07:46-05:00
Offers a template which handles the core inner loop of a game engine. Uses unhygenic symbol injection to insert functions that may be called within a limited scope. Those functions are themselves templates meaning the entire rig is short-hand which is compiled as the loops would have been written in C anyway.
## Things essential for any game out there. This module also has a few
## submodules for things that didn't fit into any other category:
##
## - ``rapid/game/tilemap`` – tilemap with collision detection
import std/monotimes
import std/times
template runGameWhile*(cond: bool, body: untyped,
updateFreq: float32 = 60): untyped =
## Starts a fixed timestep game loop.
## There are two important templates available inside the loop:
##
## - ``update: `` – anything inside the `` runs at a constant
## tick rate of ``updateFreq``
## - ``draw : `` – calculates the interpolation coefficient
## `` and passes it to the ``
##
## There are also a few variables available inside the loop:
##
## - ``time: float64`` – time elapsed since the start of the loop, in seconds
## - ``delta: float64`` – time elapsed since the last frame, in seconds
## - ``secondsPerUpdate: float32`` – time between updates
##
## **Example:**
##
## .. code-block:: nim
##
## import std/os
##
## runGameWhile true: # the condition is usually ``not win.closeRequested``
## echo (time: time, delta: delta)
##
## # process input here
##
## update:
## # tick your entities here
##
## draw step:
## # draw your entities here
##
## sleep(20) # this is fulfilled by ``frame.finish()``, but we don't have
## # a window in this example
block:
var
startTime = getMonoTime()
previousTime = getMonoTime()
lag: float32 = 0.0
while cond:
let
currentTime = getMonoTime()
delta {.inject.} =
float32(inMilliseconds(currentTime - previousTime).float64 * 0.001)
previousTime = currentTime
lag += delta
let
time {.inject.} =
inMilliseconds(currentTime - startTime).float64 * 0.001
secondsPerUpdate {.inject.} = 1'f32 / updateFreq
template update(updateBody: untyped): untyped {.inject.} =
block:
while lag >= secondsPerUpdate:
updateBody
lag -= secondsPerUpdate
template draw(stepName, drawBody: untyped): untyped {.inject.} =
block:
let stepName {.inject.} = lag / secondsPerUpdate
drawBody
body
when isMainModule:
import std/os
runGameWhile true:
echo (time: time, delta: delta)
update:
echo "running update"
draw step:
echo "drawing with step ", step
sleep(20)