2018-09-27 03:14:16 +00:00
|
|
|
lovr = require 'lovr'
|
2023-11-02 20:15:37 +00:00
|
|
|
|
2023-11-01 21:35:55 +00:00
|
|
|
local lovr = lovr
|
2018-09-27 03:14:16 +00:00
|
|
|
|
2023-11-01 21:38:04 +00:00
|
|
|
local conf = {
|
2024-03-12 20:12:29 +00:00
|
|
|
version = '0.17.1',
|
2023-11-01 21:38:04 +00:00
|
|
|
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
|
|
|
|
},
|
2024-01-04 20:39:32 +00:00
|
|
|
thread = {
|
|
|
|
workers = -1
|
|
|
|
},
|
2023-11-01 21:38:04 +00:00
|
|
|
window = {
|
|
|
|
width = 720,
|
|
|
|
height = 800,
|
|
|
|
fullscreen = false,
|
|
|
|
resizable = false,
|
|
|
|
title = 'LÖVR',
|
|
|
|
icon = nil
|
2018-04-26 03:07:28 +00:00
|
|
|
}
|
2023-11-01 21:38:04 +00:00
|
|
|
}
|
2017-12-19 00:01:12 +00:00
|
|
|
|
2023-11-01 21:38:04 +00:00
|
|
|
function lovr.boot()
|
2018-04-26 03:07:28 +00:00
|
|
|
lovr.filesystem = require('lovr.filesystem')
|
2023-11-01 22:19:21 +00:00
|
|
|
|
2023-11-18 06:36:42 +00:00
|
|
|
-- See if there's a ZIP archive fused to the executable, and set up the fused CLI if it exists
|
|
|
|
|
2023-11-01 22:19:21 +00:00
|
|
|
local bundle, root = lovr.filesystem.getBundlePath()
|
2023-11-02 20:15:37 +00:00
|
|
|
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)
|
2023-11-18 06:36:42 +00:00
|
|
|
|
|
|
|
-- Implement a barebones CLI if there is no bundled CLI/project
|
2023-11-02 20:15:37 +00:00
|
|
|
|
|
|
|
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'))
|
2024-03-12 20:32:26 +00:00
|
|
|
return 0
|
2023-11-02 20:15:37 +00:00
|
|
|
end
|
2023-11-01 22:19:21 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-11-18 06:36:42 +00:00
|
|
|
-- Figure out source archive and main module. CLI places source at arg[0]
|
|
|
|
|
|
|
|
local source, main
|
2023-11-18 07:58:48 +00:00
|
|
|
if (cli or not fused) and arg[0] then
|
|
|
|
if arg[0]:match('[^/\\]+%.lua$') then
|
2023-11-02 20:15:37 +00:00
|
|
|
source = arg[0]:match('[/\\]') and arg[0]:match('(.+)[/\\][^/\\]+$') or '.'
|
|
|
|
main = arg[0]:match('[^/\\]+%.lua$')
|
|
|
|
else
|
|
|
|
source = arg[0]
|
2023-11-18 06:36:42 +00:00
|
|
|
main = 'main.lua'
|
2022-03-28 20:24:44 +00:00
|
|
|
end
|
2023-11-18 06:36:42 +00:00
|
|
|
elseif fused then
|
|
|
|
source = bundle
|
|
|
|
main = 'main.lua'
|
2022-03-28 20:24:44 +00:00
|
|
|
end
|
|
|
|
|
2023-11-18 06:36:42 +00:00
|
|
|
-- Mount source archive, make sure it's got the main file, and load conf.lua
|
2018-04-26 03:07:28 +00:00
|
|
|
|
2023-11-18 06:36:42 +00:00
|
|
|
local ok, failure = true, nil
|
2023-11-02 20:15:37 +00:00
|
|
|
if source ~= bundle and not lovr.filesystem.mount(source) then
|
2023-11-28 03:02:31 +00:00
|
|
|
ok, failure = false, ('Failed to load project at %q\nMake sure the path or archive is valid.'):format(source)
|
2023-11-02 20:15:37 +00:00
|
|
|
elseif not lovr.filesystem.isFile(main) then
|
2023-11-18 06:36:42 +00:00
|
|
|
local location = source == '.' and '' or (' in %q'):format(source:match('[^/\\]+[/\\]?$'))
|
2023-11-28 03:02:31 +00:00
|
|
|
ok, failure = false, ('No %s file found%s.\nThe project may be packaged incorrectly.'):format(main, location)
|
2023-11-02 20:15:37 +00:00
|
|
|
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
|
2023-05-04 03:54:19 +00:00
|
|
|
|
2018-04-26 03:07:28 +00:00
|
|
|
lovr._setConf(conf)
|
2020-06-21 15:02:36 +00:00
|
|
|
lovr.filesystem.setIdentity(conf.identity, conf.saveprecedence)
|
2017-12-19 00:01:12 +00:00
|
|
|
|
2023-11-18 06:36:42 +00:00
|
|
|
-- 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!
|
2023-11-02 20:15:37 +00:00
|
|
|
|
2018-11-16 10:27:34 +00:00
|
|
|
for module in pairs(conf.modules) do
|
2018-04-26 03:07:28 +00:00
|
|
|
if conf.modules[module] then
|
2023-11-18 06:36:42 +00:00
|
|
|
local loaded, result = pcall(require, 'lovr.' .. module)
|
|
|
|
if not loaded then
|
2024-01-05 22:23:34 +00:00
|
|
|
lovr.log(string.format('Could not load module %q: %s', module, result), 'warn')
|
2018-11-16 10:27:34 +00:00
|
|
|
else
|
|
|
|
lovr[module] = result
|
|
|
|
end
|
2018-04-26 03:07:28 +00:00
|
|
|
end
|
2017-12-19 00:01:12 +00:00
|
|
|
end
|
|
|
|
|
2023-11-09 21:31:56 +00:00
|
|
|
if lovr.graphics then
|
|
|
|
lovr.graphics.initialize()
|
|
|
|
end
|
|
|
|
|
2022-05-09 19:43:19 +00:00
|
|
|
if lovr.system and conf.window then
|
|
|
|
lovr.system.openWindow(conf.window)
|
|
|
|
end
|
|
|
|
|
2022-11-26 22:40:39 +00:00
|
|
|
if lovr.headset then
|
2021-06-10 23:26:15 +00:00
|
|
|
lovr.headset.start()
|
2020-08-25 21:49:19 +00:00
|
|
|
end
|
|
|
|
|
2023-11-28 03:02:31 +00:00
|
|
|
if not ok and failure then
|
2023-11-02 20:15:37 +00:00
|
|
|
error(failure)
|
|
|
|
end
|
|
|
|
|
|
|
|
require(main:sub(1, -5))
|
|
|
|
|
2018-04-26 03:07:28 +00:00
|
|
|
return lovr.run()
|
2017-12-19 00:01:12 +00:00
|
|
|
end
|
|
|
|
|
2018-04-26 03:07:28 +00:00
|
|
|
function lovr.run()
|
2021-02-09 15:26:44 +00:00
|
|
|
if lovr.timer then lovr.timer.step() end
|
2018-04-26 03:07:28 +00:00
|
|
|
if lovr.load then lovr.load(arg) end
|
|
|
|
return function()
|
2023-05-09 23:54:20 +00:00
|
|
|
if lovr.system then lovr.system.pollEvents() end
|
2021-02-09 15:26:44 +00:00
|
|
|
if lovr.event then
|
|
|
|
for name, a, b, c, d in lovr.event.poll() do
|
2023-10-18 19:51:25 +00:00
|
|
|
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
|
2018-04-26 03:07:28 +00:00
|
|
|
end
|
2017-12-19 00:01:12 +00:00
|
|
|
end
|
2022-03-23 00:52:16 +00:00
|
|
|
local dt = 0
|
2021-02-09 15:26:44 +00:00
|
|
|
if lovr.timer then dt = lovr.timer.step() end
|
2022-03-23 00:52:16 +00:00
|
|
|
if lovr.headset then dt = lovr.headset.update() end
|
2018-04-26 03:07:28 +00:00
|
|
|
if lovr.update then lovr.update(dt) end
|
2022-05-30 22:35:07 +00:00
|
|
|
if lovr.graphics then
|
2023-04-30 06:02:37 +00:00
|
|
|
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()
|
2022-05-30 22:35:07 +00:00
|
|
|
end
|
2022-06-06 03:38:14 +00:00
|
|
|
if lovr.headset then lovr.headset.submit() end
|
2021-02-09 15:26:44 +00:00
|
|
|
if lovr.math then lovr.math.drain() end
|
2017-12-19 00:01:12 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-06-06 03:38:14 +00:00
|
|
|
function lovr.mirror(pass)
|
|
|
|
if lovr.headset then
|
2022-07-18 03:07:31 +00:00
|
|
|
local texture = lovr.headset.getTexture()
|
|
|
|
if texture then
|
|
|
|
pass:fill(texture)
|
|
|
|
end
|
2022-06-06 03:38:14 +00:00
|
|
|
else
|
2022-07-18 03:07:31 +00:00
|
|
|
return lovr.draw and lovr.draw(pass)
|
2022-06-06 03:38:14 +00:00
|
|
|
end
|
2019-01-24 21:20:14 +00:00
|
|
|
end
|
|
|
|
|
2018-11-13 06:34:02 +00:00
|
|
|
local function formatTraceback(s)
|
2023-05-12 16:16:22 +00:00
|
|
|
return s:gsub('\n[^\n]+$', ''):gsub('\t', ''):gsub('stack traceback:', '\nStack:\n')
|
2018-11-13 06:34:02 +00:00
|
|
|
end
|
|
|
|
|
2022-08-13 03:32:45 +00:00
|
|
|
function lovr.errhand(message)
|
2024-01-25 01:54:07 +00:00
|
|
|
message = 'Error:\n\n' .. tostring(message) .. formatTraceback(debug and debug.traceback('', 4) or '')
|
2023-05-12 16:16:22 +00:00
|
|
|
|
|
|
|
print(message)
|
2022-06-21 01:26:32 +00:00
|
|
|
|
2023-12-02 00:50:45 +00:00
|
|
|
if not lovr.graphics or not lovr.graphics.isInitialized() then
|
2022-11-08 03:12:11 +00:00
|
|
|
return function() return 1 end
|
|
|
|
end
|
2022-07-11 04:37:52 +00:00
|
|
|
|
2022-08-13 03:46:59 +00:00
|
|
|
if lovr.audio then lovr.audio.stop() end
|
|
|
|
|
2023-07-17 07:10:02 +00:00
|
|
|
if not lovr.headset or lovr.headset.getPassthrough() == 'opaque' then
|
2023-05-12 13:31:31 +00:00
|
|
|
lovr.graphics.setBackgroundColor(.11, .10, .14)
|
|
|
|
else
|
|
|
|
lovr.graphics.setBackgroundColor(0, 0, 0, 0)
|
2022-06-21 01:26:32 +00:00
|
|
|
end
|
|
|
|
|
2023-05-12 16:16:22 +00:00
|
|
|
local font = lovr.graphics.getDefaultFont()
|
2022-06-21 01:26:32 +00:00
|
|
|
|
|
|
|
return function()
|
2023-05-10 07:46:56 +00:00
|
|
|
lovr.system.pollEvents()
|
2022-08-13 03:32:45 +00:00
|
|
|
|
2022-06-21 01:26:32 +00:00
|
|
|
for name, a in lovr.event.poll() do
|
|
|
|
if name == 'quit' then return a or 1
|
2022-07-11 04:37:52 +00:00
|
|
|
elseif name == 'restart' then return 'restart', lovr.restart and lovr.restart()
|
2023-05-12 16:16:22 +00:00
|
|
|
elseif name == 'keypressed' and a == 'f5' then lovr.event.restart()
|
|
|
|
elseif name == 'keypressed' and a == 'escape' then lovr.event.quit() end
|
2022-06-21 01:26:32 +00:00
|
|
|
end
|
2022-08-13 03:32:45 +00:00
|
|
|
|
2022-08-13 02:28:25 +00:00
|
|
|
if lovr.headset and lovr.headset.getDriver() ~= 'desktop' then
|
2022-06-21 01:26:32 +00:00
|
|
|
lovr.headset.update()
|
2022-08-06 07:29:40 +00:00
|
|
|
local pass = lovr.headset.getPass()
|
|
|
|
if pass then
|
2023-05-12 16:16:22 +00:00
|
|
|
font:setPixelDensity()
|
|
|
|
|
|
|
|
local scale = .35
|
|
|
|
local font = lovr.graphics.getDefaultFont()
|
|
|
|
local wrap = .7 * font:getPixelDensity()
|
|
|
|
local lines = font:getLines(message, wrap)
|
2023-11-18 06:36:25 +00:00
|
|
|
local maxWidth = 0
|
|
|
|
for i, line in ipairs(lines) do maxWidth = math.max(maxWidth, font:getWidth(line)) end
|
|
|
|
local width = maxWidth * scale
|
2023-05-12 16:16:22 +00:00
|
|
|
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')
|
|
|
|
|
2022-08-13 02:28:25 +00:00
|
|
|
lovr.graphics.submit(pass)
|
2022-08-13 03:32:45 +00:00
|
|
|
lovr.headset.submit()
|
2022-06-21 01:26:32 +00:00
|
|
|
end
|
|
|
|
end
|
2022-08-13 03:32:45 +00:00
|
|
|
|
2022-06-21 01:26:32 +00:00
|
|
|
if lovr.system.isWindowOpen() then
|
2022-08-06 07:29:40 +00:00
|
|
|
local pass = lovr.graphics.getWindowPass()
|
2022-11-08 06:45:10 +00:00
|
|
|
if pass then
|
2023-05-12 16:16:22 +00:00
|
|
|
local w, h = lovr.system.getWindowDimensions()
|
2023-11-18 06:36:25 +00:00
|
|
|
pass:setProjection(1, lovr.math.mat4():orthographic(w, h))
|
2023-05-12 16:16:22 +00:00
|
|
|
font:setPixelDensity(1)
|
|
|
|
|
|
|
|
local scale = .6
|
|
|
|
local wrap = w * .8 / scale
|
2023-11-18 06:36:25 +00:00
|
|
|
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
|
2023-05-12 16:16:22 +00:00
|
|
|
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')
|
|
|
|
|
2022-11-08 06:45:10 +00:00
|
|
|
lovr.graphics.submit(pass)
|
|
|
|
lovr.graphics.present()
|
|
|
|
end
|
2022-06-21 01:26:32 +00:00
|
|
|
end
|
2023-05-12 16:16:22 +00:00
|
|
|
|
|
|
|
lovr.math.drain()
|
2022-06-21 01:26:32 +00:00
|
|
|
end
|
2017-12-19 00:01:12 +00:00
|
|
|
end
|
|
|
|
|
2018-03-12 17:37:45 +00:00
|
|
|
function lovr.threaderror(thread, err)
|
2018-04-26 03:07:28 +00:00
|
|
|
error('Thread error\n\n' .. err, 0)
|
2018-03-12 17:37:45 +00:00
|
|
|
end
|
|
|
|
|
2023-12-03 03:57:58 +00:00
|
|
|
function lovr.filechanged(path, action, oldpath)
|
2024-05-27 11:19:50 +00:00
|
|
|
if not path:match('^%.') then
|
|
|
|
lovr.event.restart()
|
|
|
|
end
|
2023-12-03 03:57:58 +00:00
|
|
|
end
|
|
|
|
|
lovr.log;
lovr.log is a new callback that is invoked whenever LÖVR wants to
send the project a message. For example, this could be a performance
warning from the graphics module, an error message from one of the
headset backends, or an API deprecation notice.
The callback's signature is (message, level, tag). The message is a
string containing the message to log, level is a string that is currently
one of "debug", "info", "warn", "error", and tag is an optional string
that is used to indicate the source of the message for grouping purposes.
The default implementation of the callback just prints the message,
but the callback can be overridden to do things like filter messages,
write them to a file, or even render them in VR. Projects can also
invoke the callback directly to log their own messages.
2020-07-06 22:20:55 +00:00
|
|
|
function lovr.log(message, level, tag)
|
2022-12-31 05:39:52 +00:00
|
|
|
message = message:gsub('\n$', '')
|
|
|
|
print(message)
|
lovr.log;
lovr.log is a new callback that is invoked whenever LÖVR wants to
send the project a message. For example, this could be a performance
warning from the graphics module, an error message from one of the
headset backends, or an API deprecation notice.
The callback's signature is (message, level, tag). The message is a
string containing the message to log, level is a string that is currently
one of "debug", "info", "warn", "error", and tag is an optional string
that is used to indicate the source of the message for grouping purposes.
The default implementation of the callback just prints the message,
but the callback can be overridden to do things like filter messages,
write them to a file, or even render them in VR. Projects can also
invoke the callback directly to log their own messages.
2020-07-06 22:20:55 +00:00
|
|
|
end
|
|
|
|
|
2023-11-02 20:15:37 +00:00
|
|
|
lovr.handlers = setmetatable({}, { __index = lovr })
|
2022-08-13 03:18:55 +00:00
|
|
|
|
2023-11-02 20:15:37 +00:00
|
|
|
return coroutine.create(function()
|
2022-08-13 03:18:55 +00:00
|
|
|
local function onerror(...)
|
2023-11-02 20:15:37 +00:00
|
|
|
onerror = function(...)
|
2024-01-25 01:54:07 +00:00
|
|
|
print('Error:\n\n' .. tostring(...) .. formatTraceback(debug and debug.traceback('', 1) or ''))
|
2022-08-13 03:18:55 +00:00
|
|
|
return function() return 1 end
|
2018-11-13 06:34:02 +00:00
|
|
|
end
|
2023-11-18 07:18:08 +00:00
|
|
|
|
|
|
|
local ok, result = pcall(lovr.errhand or onerror, ...)
|
|
|
|
|
|
|
|
if ok then
|
|
|
|
return result or function() return 1 end
|
|
|
|
else
|
|
|
|
return onerror(result)
|
|
|
|
end
|
2018-11-13 06:34:02 +00:00
|
|
|
end
|
|
|
|
|
2023-11-02 20:15:37 +00:00
|
|
|
local thread = select(2, xpcall(lovr.boot, onerror))
|
2018-04-26 03:07:28 +00:00
|
|
|
|
|
|
|
while true do
|
2022-08-13 03:18:55 +00:00
|
|
|
local ok, result, cookie = xpcall(thread, onerror)
|
2023-11-02 20:15:37 +00:00
|
|
|
if not ok then thread = result
|
|
|
|
elseif result then return result, cookie end
|
2022-08-13 03:18:55 +00:00
|
|
|
coroutine.yield()
|
2018-11-13 06:34:02 +00:00
|
|
|
end
|
2023-11-01 21:38:04 +00:00
|
|
|
end)
|