mirror of https://github.com/bjornbytes/lovr.git
314 lines
8.6 KiB
Lua
314 lines
8.6 KiB
Lua
lovr = require 'lovr'
|
|
|
|
local lovr = lovr
|
|
|
|
local conf = {
|
|
version = '0.17.0',
|
|
identity = 'default',
|
|
saveprecedence = true,
|
|
modules = {
|
|
audio = true,
|
|
data = true,
|
|
event = true,
|
|
graphics = true,
|
|
headset = true,
|
|
math = true,
|
|
physics = true,
|
|
system = true,
|
|
thread = true,
|
|
timer = true
|
|
},
|
|
audio = {
|
|
start = true,
|
|
spatializer = nil
|
|
},
|
|
graphics = {
|
|
debug = false,
|
|
vsync = true,
|
|
stencil = false,
|
|
antialias = true,
|
|
shadercache = true
|
|
},
|
|
headset = {
|
|
drivers = { 'openxr', 'webxr', 'desktop' },
|
|
supersample = false,
|
|
seated = false,
|
|
stencil = false,
|
|
antialias = true,
|
|
submitdepth = true,
|
|
overlay = false
|
|
},
|
|
math = {
|
|
globals = true
|
|
},
|
|
window = {
|
|
width = 720,
|
|
height = 800,
|
|
fullscreen = false,
|
|
resizable = false,
|
|
title = 'LÖVR',
|
|
icon = nil
|
|
}
|
|
}
|
|
|
|
function lovr.boot()
|
|
lovr.filesystem = require('lovr.filesystem')
|
|
|
|
-- See if there's a ZIP archive fused to the executable, and set up the fused CLI if it exists
|
|
|
|
local bundle, root = lovr.filesystem.getBundlePath()
|
|
local fused = lovr.filesystem.mount(bundle, nil, true, root)
|
|
local cli = lovr.filesystem.isFile('arg.lua') and assert(pcall(require, 'arg')) and lovr.arg and lovr.arg(arg)
|
|
|
|
-- Implement a barebones CLI if there is no bundled CLI/project
|
|
|
|
if not fused then
|
|
if arg[1] and not arg[1]:match('^%-') then
|
|
for i = 0, #arg do
|
|
arg[i - 1], arg[i] = arg[i], nil
|
|
end
|
|
else
|
|
return function()
|
|
print(table.concat({
|
|
'usage: lovr <source>',
|
|
'<source> can be a Lua file, a folder with a main.lua file, or a zip archive'
|
|
}, '\n'))
|
|
return 1
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Figure out source archive and main module. CLI places source at arg[0]
|
|
|
|
local source, main
|
|
if (cli or not fused) and arg[0] then
|
|
if arg[0]:match('[^/\\]+%.lua$') then
|
|
source = arg[0]:match('[/\\]') and arg[0]:match('(.+)[/\\][^/\\]+$') or '.'
|
|
main = arg[0]:match('[^/\\]+%.lua$')
|
|
else
|
|
source = arg[0]
|
|
main = 'main.lua'
|
|
end
|
|
elseif fused then
|
|
source = bundle
|
|
main = 'main.lua'
|
|
end
|
|
|
|
-- Mount source archive, make sure it's got the main file, and load conf.lua
|
|
|
|
local ok, failure = true, nil
|
|
if source ~= bundle and not lovr.filesystem.mount(source) then
|
|
ok, failure = false, ('Failed to load project at %q\nMake sure the path or archive is valid.'):format(source)
|
|
elseif not lovr.filesystem.isFile(main) then
|
|
local location = source == '.' and '' or (' in %q'):format(source:match('[^/\\]+[/\\]?$'))
|
|
ok, failure = false, ('No %s file found%s.\nThe project may be packaged incorrectly.'):format(main, location)
|
|
else
|
|
lovr.filesystem.setSource(source)
|
|
if lovr.filesystem.isFile('conf.lua') then ok, failure = pcall(require, 'conf') end
|
|
if ok and lovr.conf then ok, failure = pcall(lovr.conf, conf) end
|
|
end
|
|
|
|
lovr._setConf(conf)
|
|
lovr.filesystem.setIdentity(conf.identity, conf.saveprecedence)
|
|
|
|
-- CLI gets a chance to use/modify conf and handle arguments
|
|
|
|
if ok and not failure and cli then ok, failure = pcall(cli, conf) end
|
|
|
|
-- Boot!
|
|
|
|
for module in pairs(conf.modules) do
|
|
if conf.modules[module] then
|
|
local loaded, result = pcall(require, 'lovr.' .. module)
|
|
if not loaded then
|
|
lovr.log(string.format('Could not load module %q: %s', module, result), 'warn')
|
|
else
|
|
lovr[module] = result
|
|
end
|
|
end
|
|
end
|
|
|
|
if lovr.graphics then
|
|
lovr.graphics.initialize()
|
|
end
|
|
|
|
if lovr.system and conf.window then
|
|
lovr.system.openWindow(conf.window)
|
|
end
|
|
|
|
if lovr.headset then
|
|
lovr.headset.start()
|
|
end
|
|
|
|
if not ok and failure then
|
|
error(failure)
|
|
end
|
|
|
|
require(main:sub(1, -5))
|
|
|
|
return lovr.run()
|
|
end
|
|
|
|
function lovr.run()
|
|
if lovr.timer then lovr.timer.step() end
|
|
if lovr.load then lovr.load(arg) end
|
|
return function()
|
|
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
|
|
local dt = 0
|
|
if lovr.timer then dt = lovr.timer.step() end
|
|
if lovr.headset then dt = lovr.headset.update() end
|
|
if lovr.update then lovr.update(dt) end
|
|
if lovr.graphics then
|
|
local headset = lovr.headset and lovr.headset.getPass()
|
|
if headset and (not lovr.draw or lovr.draw(headset)) then headset = nil end
|
|
local window = lovr.graphics.getWindowPass()
|
|
if window and (not lovr.mirror or lovr.mirror(window)) 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
|
|
|
|
function lovr.mirror(pass)
|
|
if lovr.headset then
|
|
local texture = lovr.headset.getTexture()
|
|
if texture then
|
|
pass:fill(texture)
|
|
end
|
|
else
|
|
return lovr.draw and lovr.draw(pass)
|
|
end
|
|
end
|
|
|
|
local function formatTraceback(s)
|
|
return s:gsub('\n[^\n]+$', ''):gsub('\t', ''):gsub('stack traceback:', '\nStack:\n')
|
|
end
|
|
|
|
function lovr.errhand(message)
|
|
message = 'Error:\n\n' .. tostring(message) .. formatTraceback(debug and debug.traceback('', 4) or '')
|
|
|
|
print(message)
|
|
|
|
if not lovr.graphics or not lovr.graphics.isInitialized() then
|
|
return function() return 1 end
|
|
end
|
|
|
|
if lovr.audio then lovr.audio.stop() end
|
|
|
|
if not lovr.headset or lovr.headset.getPassthrough() == 'opaque' then
|
|
lovr.graphics.setBackgroundColor(.11, .10, .14)
|
|
else
|
|
lovr.graphics.setBackgroundColor(0, 0, 0, 0)
|
|
end
|
|
|
|
local font = lovr.graphics.getDefaultFont()
|
|
|
|
return function()
|
|
lovr.system.pollEvents()
|
|
|
|
for name, a in lovr.event.poll() do
|
|
if name == 'quit' then return a or 1
|
|
elseif name == 'restart' then return 'restart', lovr.restart and lovr.restart()
|
|
elseif name == 'keypressed' and a == 'f5' then lovr.event.restart()
|
|
elseif name == 'keypressed' and a == 'escape' then lovr.event.quit() end
|
|
end
|
|
|
|
if lovr.headset and lovr.headset.getDriver() ~= 'desktop' then
|
|
lovr.headset.update()
|
|
local pass = lovr.headset.getPass()
|
|
if pass then
|
|
font:setPixelDensity()
|
|
|
|
local scale = .35
|
|
local font = lovr.graphics.getDefaultFont()
|
|
local wrap = .7 * font:getPixelDensity()
|
|
local lines = font:getLines(message, wrap)
|
|
local maxWidth = 0
|
|
for i, line in ipairs(lines) do maxWidth = math.max(maxWidth, font:getWidth(line)) end
|
|
local width = maxWidth * scale
|
|
local height = .8 + #lines * font:getHeight() * scale
|
|
local x = -width / 2
|
|
local y = math.min(height / 2, 10)
|
|
local z = -10
|
|
|
|
pass:setColor(.95, .95, .95)
|
|
pass:text(message, x, y, z, scale, 0, 0, 0, 0, wrap, 'left', 'top')
|
|
|
|
lovr.graphics.submit(pass)
|
|
lovr.headset.submit()
|
|
end
|
|
end
|
|
|
|
if lovr.system.isWindowOpen() then
|
|
local pass = lovr.graphics.getWindowPass()
|
|
if pass then
|
|
local w, h = lovr.system.getWindowDimensions()
|
|
pass:setProjection(1, lovr.math.mat4():orthographic(w, h))
|
|
font:setPixelDensity(1)
|
|
|
|
local scale = .6
|
|
local wrap = w * .8 / scale
|
|
local lines = font:getLines(message, wrap)
|
|
local maxWidth = 0
|
|
for i, line in ipairs(lines) do maxWidth = math.max(maxWidth, font:getWidth(line)) end
|
|
local width = maxWidth * scale
|
|
local x = w / 2 - width / 2
|
|
|
|
pass:setColor(.95, .95, .95)
|
|
pass:text(message, x, h / 2, 0, scale, 0, 0, 0, 0, wrap, 'left', 'middle')
|
|
|
|
lovr.graphics.submit(pass)
|
|
lovr.graphics.present()
|
|
end
|
|
end
|
|
|
|
lovr.math.drain()
|
|
end
|
|
end
|
|
|
|
function lovr.threaderror(thread, err)
|
|
error('Thread error\n\n' .. err, 0)
|
|
end
|
|
|
|
function lovr.log(message, level, tag)
|
|
message = message:gsub('\n$', '')
|
|
print(message)
|
|
end
|
|
|
|
lovr.handlers = setmetatable({}, { __index = lovr })
|
|
|
|
return coroutine.create(function()
|
|
local function onerror(...)
|
|
onerror = function(...)
|
|
print('Error:\n\n' .. tostring(...) .. formatTraceback(debug and debug.traceback('', 1) or ''))
|
|
return function() return 1 end
|
|
end
|
|
|
|
local ok, result = pcall(lovr.errhand or onerror, ...)
|
|
|
|
if ok then
|
|
return result or function() return 1 end
|
|
else
|
|
return onerror(result)
|
|
end
|
|
end
|
|
|
|
local thread = select(2, xpcall(lovr.boot, onerror))
|
|
|
|
while true do
|
|
local ok, result, cookie = xpcall(thread, onerror)
|
|
if not ok then thread = result
|
|
elseif result then return result, cookie end
|
|
coroutine.yield()
|
|
end
|
|
end)
|