Created
April 19, 2025 20:27
-
-
Save jmiskovic/820397f850ca5d48b70f43b6d2236cf4 to your computer and use it in GitHub Desktop.
Stabilize the LÖVR physics and state updates by running them at consistent intervals regardless of frame rate
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- require this file to have reduced input lag and game logic that runs consistently | |
| -- inspired by https://gafferongames.com/post/fix_your_timestep/ | |
| -- note: this decouples update() from draw(); update() can run multiple times per frame | |
| local m = {} | |
| m.update_delta = 1 / 60 | |
| function lovr.run() | |
| if lovr.timer then lovr.timer.step() end | |
| if lovr.load then lovr.load(arg) end | |
| local current_time = 0.0 | |
| local accumulator = 0.0 | |
| local t = 0.0 | |
| local previous_state = {} | |
| local current_state = {} | |
| return function() | |
| local dt = m.update_delta | |
| local window | |
| if lovr.graphics then -- get window pass earlier than LÖVR default to reduce input lag | |
| window = lovr.graphics.getWindowPass() | |
| end | |
| if lovr.system then lovr.system.pollEvents() end | |
| if lovr.event then | |
| for name, a, b, c, d in lovr.event.poll() do | |
| if name == 'restart' then return 'restart', lovr.restart and lovr.restart() | |
| elseif name == 'quit' and (not lovr.quit or not lovr.quit(a)) then return a or 0 | |
| elseif name ~= 'quit' and lovr.handlers[name] then lovr.handlers[name](a, b, c, d) end | |
| end | |
| end | |
| if lovr.timer then lovr.timer.step() end | |
| if lovr.headset and lovr.headset.isActive() then lovr.headset.update() end | |
| local new_time = lovr.timer.getTime() | |
| local frame_time = new_time - current_time | |
| if frame_time > 0.25 then frame_time = 0.25 end | |
| current_time = new_time | |
| accumulator = accumulator + frame_time | |
| while accumulator >= dt do | |
| previous_state = current_state | |
| if lovr.update then lovr.update(dt, t) end | |
| t = t + dt | |
| accumulator = accumulator - dt | |
| end | |
| local alpha = accumulator / dt | |
| local state = {} | |
| for k, v in pairs(current_state) do | |
| state[k] = v * alpha + previous_state[k] * (1.0 - alpha) | |
| end | |
| if lovr.graphics then | |
| local headset = lovr.headset and lovr.headset.getPass() | |
| if headset and (not lovr.draw or lovr.draw(headset, state)) then headset = nil end | |
| if window and (not lovr.mirror or lovr.mirror(window, state)) then window = nil end | |
| lovr.graphics.submit(headset, window) | |
| lovr.graphics.present() | |
| end | |
| if lovr.headset then lovr.headset.submit() end | |
| if lovr.math then lovr.math.drain() end | |
| end | |
| end | |
| return m |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment