• Announcement: Lua.org now officially recommends this forum as a meeting place for the Lua community

The game loop (2 Viewers)

Hi all. Hope you're all doing well despite this damn Covid thing.

BTW, inspired by a recent question appeared on the Lua subreddit I just wrote a runnable example/tutorial on how to write a
game loop with GLFW, using the MoonGLFW bindings.

Since the dreaded 'game loop' is something against which people sooner or later bang their heads, I think it deserves a thread.

Any comment is welcome, as well as alternative examples using other technologies/libraries, philosophical discussions or whatever.

(Disclaimer: I am not a game developer, just a tinkerer, so if you are one and you find my example naive, please be forgiving!)

Lua:
-- A game loop example. Shows how GLFW can be used to implement a game loop from which
-- the multiple tasks that make up the game (graphics rendering, physics updates, AI
-- updates, etc) are executed at different rates.

local glfw = require("moonglfw")

-- Create the window where the game will run:
local window = glfw.create_window(320, 240, "Game loop skeleton")

local function update_counter(ups)
-- Returns a function n=f(dt) that, given a variable delta time dt, computes how many
-- updates must be performed in order to have ups updates per second. Any reminder time
-- is accounted for in the subsequent call(s).
   local fixed_dt = 1/ups
   local n, tot_dt, remainder = 0, 0, 0
   return function(dt)
      tot_dt = dt + remainder
      n, remainder = tot_dt//fixed_dt, tot_dt%fixed_dt
      return n
   end
end

-- Create a separate update counter for each game task, specifying the rate at
-- which we want the task to execute:
local n_graphics_updates = update_counter(30) -- render 30 frames per second
local n_physics_updates = update_counter(180) -- update physics 180 times per second
local n_ai_updates = update_counter(60)       -- update AI 60 times per second

local last_t, current_t, dt
last_t = glfw.get_time()

-- Enter the game loop:
while not glfw.window_should_close(window) do
   -- 1) Compute the time elapsed since the last iteration (the 'delta time' dt).
   -- This dt is variable. That is, it differs from iteration to iteration because it depends
   -- both on how much computation we do, and on the scheduling of our application from the OS.
   current_t = glfw.get_time()
   dt = current_t - last_t
   last_t = current_t
   -- print(string.format("dt = %.3f ms", dt*1000))

   -- 2) Poll for any input event (from keyboard, mouse, joysticks, gamepads, etc)
   -- that may have occurred since the last iteration.
   -- This just records the input events, which we will process later at step 3
   -- (unless we registered event callbacks, in which case they are executed now).
   glfw.poll_events()
  
   -- 3) Process input. Here we just check if the user pressed ESC, but of course a real
   -- game will have much more inputs. Note that here we do not use an update counter,
   -- so input processing will occur at the maximum (and variable) possible rate.
   if glfw.get_key(window, 'escape')=='press' then
      glfw.set_window_should_close(window, true)
   end
   -- ... process any other input ...
  
   -- 4) Perform the game tasks (here we just print that we are performing them).
   -- We perform each task as many times as needed to keep up with the desired rate.
   for i=1, n_physics_updates(dt) do
      print(current_t, "updating physics")
   end
   for i=1, n_ai_updates(dt) do
      print(current_t, "updating AI")
   end
   for i=1, n_graphics_updates(dt) do
      print(current_t, "rendering frame")
   end
   -- ... whatever else ...
end
 

GavinW

Newcomer
Creator of RiscLua
Joined
Oct 21, 2020
Messages
41
Reaction score
17
Age
82
Location
UK
Website
www.wra1th.plus.com
Sorry, I never heard of GLFW, but I thought you might be entertained by the following barebones sketch of how to write one of those games where the player moves from room to room (or cavern to cavern, planet to planet, ... ). Each room is represented by a function of no arguments, which returns the next room to be visited, or nil in the case of rooms where the game stops. Running the game amounts to
Lua:
local run = function ( f ) while f do f = f ( ) end end
run (start)
where start is an initial room. Rooms typically, will get input from the user, modify some global state and have conditional return statements. However it may be preferable to let rooms be tables, with an act method:
Lua:
local run = function (room) while (room) do room = room:act ( ) end end
Rooms can then have attributes, such as names, and you can implement trace functions that remember the names of rooms visited. Possibilities abound.
 
Last edited:

stetre

Member
Rank: I
Joined
Jan 8, 2020
Messages
61
Reaction score
43
Location
Italy
Website
github.com
Hi, Gavin.

GLFW ( GLFW - An OpenGL library ) is a popular library for the creation and handling of windows and related OpenGL contexts (and Vulkan contexts as well), and the handling of input events (from keyboards, mice, joysticks, gamepads, etc). It abstracts those operations so the programmer can use a consistent API across different platforms. If you are familiar with OpenGL, you can see it as a modern replacement for the old GLUT library.

Your barebones sketch is interesting, but with such a solution you would have to do the rendering and input handling in each function's 'while' loop. Maybe you were thinking more about text-based games?

For graphics games that have to render tot frames per second and continuously handle input events, an event-driven solution is usually more suitable. A barebones sketch could be like:

Lua:
local current_room = initial_room

-- The main loop
while running do
  get_input()
  current_room()
  render()
end

Here the 'current_room' variable contains a function that performs the game logic depending on which room the player is currently in, e.g.

Lua:
local function initial_room( )
  -- ...
end

local function room1( )
  -- ...
end

local function room2( )
  -- ...
end

Such functions must not block, i.e. they must return as soon as possible to the main loop (allowing it to detect subsequent input and render), and their internal logic is expected to change the 'current_room' variable. For example, the 'initial_room' function may at some point see that the position of the player has moved past a door, and change 'current_room' to 'room1', so that at the next iteration of the loop the 'room1' function would be executed instead.
 

GavinW

Newcomer
Creator of RiscLua
Joined
Oct 21, 2020
Messages
41
Reaction score
17
Age
82
Location
UK
Website
www.wra1th.plus.com
Thanks. That is very clear. In RISC OS, where multitasking is cooperative and the task manager is also the window manager, programs that interact with the GUI are essentially coroutines of the task manager. They have to be written round a polling loop, and their behaviour is determined by a table of handlers, indexed by the responses from the task manager.
 
Last edited:

dinsdale247

Moderator
Staff member
Community Patron
Creator of WinLua
Joined
Nov 17, 2020
Messages
93
Reaction score
32
Location
Victoria BC
Website
winlua.net
Very cool. Can you show us how to open a window? Move something around the window with a mouse?
 

stetre

Member
Rank: I
Joined
Jan 8, 2020
Messages
61
Reaction score
43
Location
Italy
Website
github.com
The example actually creates a 320 x240 window, in the line:

Lua:
local window = glfw.create_window(320, 240, "Game loop skeleton")

This is just a blank window though. To draw something into it we have to use a rendering API such as OpenGL or Vulkan,
for which (and more) I also wrote bindings that you can find here. We draw in the window in much the same way we would
do it in C, only using Lua calls instead of C calls. The topic of rendering is huge and I won't get into it (it won't fit in a thread,
leat alone a post!) but if you are not familiar with it you can find plenty of examples and references in the '/examples' directories
of the bindings.

Among those examples, maybe the more complete from a game development point of view are the demo's that come with MoonChipmunk, the bindings to the Chipmunk2D physics library. Among other things they also show how to grab something
and move it around with a mouse (in a 2D rendered world).
 
Top