Created
October 31, 2024 12:17
-
-
Save gphg/349d8084d3a02a35d408e7fa5368cc34 to your computer and use it in GitHub Desktop.
My "plug and play"-ish entry point Lua script for LOVE2D.
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
------------------------------------------------------------------------------- | |
--- main.lua v0.2 | |
------------------------------------------------------------------------------- | |
local main = { | |
_VERSION = '0.2', | |
} | |
local arg, os, package, love, jit = arg, os, package, love, jit | |
local assert, pcall, require, tonumber, setmetatable, print = | |
assert, pcall, require, tonumber, setmetatable, print | |
local love_ver, getenv, filesystem, timer, report, noop, concat, maxOfMount, | |
RUNTIME_VERBOSE, | |
RUNTIME_HEADLESS, | |
RUNTIME_EXTENSION, | |
RUNTIME_RANDOMSEED, | |
RUNTIME_SKIP_FUSED, | |
RUNTIME_MOUNT_POINT, | |
RUNTIME_MOUNT_LIMIT, | |
RUNTIME_GAME_MODULE, | |
_ -- yes, this is a variable. DO NOT REMOVE. | |
maxOfMount = 99 ---@todo | |
function noop() end | |
function concat(t) return table.concat(t, ', ') end | |
------------------------------------------------------------------------------- | |
-- * lock the global table: new assigment raises error | |
------------------------------------------------------------------------------- | |
setmetatable(_G, { | |
__index = function(_, k) error('referenced an undefined variable: ' .. k, 2) end, | |
__newindex = function(_, k) error('new global variables disabled: ' .. k, 2) end, | |
}) | |
------------------------------------------------------------------------------- | |
-- * simple console print for debug and other report | |
------------------------------------------------------------------------------- | |
do | |
RUNTIME_VERBOSE = tostring(os.getenv('RUNTIME_VERBOSE') or '') | |
report = RUNTIME_VERBOSE ~= '' and print or noop | |
report(jit and ('%s (%s %s)'):format(jit.version, jit.os, jit.arch) or _VERSION) | |
report(('arguments: %s'):format(concat(arg))) | |
assert(love or require 'love', 'LOVE (https://love2d.org/) is required.') | |
assert(love.getVersion() >= 11, 'LOVE version 11.0+ is required.') | |
love_ver = ('L\195\150VE %d.%d.%d (%s)'):format(love.getVersion()) | |
filesystem = love.filesystem or require 'love.filesystem' -- future-proof | |
timer = love.timer or require 'love.timer' | |
report(love_ver) | |
report('love.timer.getTime at:', timer.getTime()) | |
end | |
------------------------------------------------------------------------------- | |
-- * get environment. At the moment, cast everything locally in CAPITALIZE. | |
------------------------------------------------------------------------------- | |
do | |
function getenv(s) | |
local env = os.getenv(s) | |
report(s, '=', env) | |
return env ~= '' and env or nil | |
end | |
getenv 'LOVE_GRAPHICS_USE_OPENGLES' | |
getenv 'SDL_OPENGL_ES_DRIVER' | |
RUNTIME_HEADLESS = getenv 'RUNTIME_HEADLESS' | |
RUNTIME_EXTENSION = getenv 'RUNTIME_EXTENSION' | |
RUNTIME_RANDOMSEED = getenv 'RUNTIME_RANDOMSEED' | |
RUNTIME_SKIP_FUSED = getenv 'RUNTIME_SKIP_FUSED' | |
RUNTIME_MOUNT_POINT = getenv 'RUNTIME_MOUNT_POINT' | |
RUNTIME_MOUNT_LIMIT = getenv 'RUNTIME_MOUNT_LIMIT' | |
RUNTIME_GAME_MODULE = getenv 'RUNTIME_GAME_MODULE' | |
end | |
------------------------------------------------------------------------------- | |
-- * initialize math randomness | |
------------------------------------------------------------------------------- | |
do | |
local seed = tonumber(RUNTIME_RANDOMSEED) or os.time() | |
local math, love_math = math, (love.math or require 'love.math') | |
local step = (maxOfMount + (tonumber(('%p'):format{}) % maxOfMount)) | |
love_math.setRandomSeed(seed) | |
math.randomseed(seed) | |
report('Set math.randomseed to:', seed, 'at', step, 'steps') | |
for _ = 0, step do | |
love_math.random() | |
math.random() | |
end | |
end | |
------------------------------------------------------------------------------- | |
-- * game must be fused or run on fused mode | |
------------------------------------------------------------------------------- | |
do | |
local root = RUNTIME_MOUNT_POINT or '/' | |
local limit = tonumber(RUNTIME_MOUNT_LIMIT) or 16 | |
local skip = RUNTIME_SKIP_FUSED ~= nil | |
local _os, listOfItem, mount, success, failed, filePattern, registry, saveDir | |
local table, math, getDirectoryItems = table, math, filesystem.getDirectoryItems | |
function mount(x, y) return filesystem.mount(x, y, true) and x or nil end | |
success, failed, filePattern = {}, {}, '^[%(%)%.%%%+%-%*%?%[%]%^%$@#_&\\,:;!*"\']' | |
registry, saveDir = (main.mounted or {}), filesystem.getSaveDirectory() | |
limit = math.min(math.max(limit, 0), maxOfMount) | |
main.mount, main.mounted = mount, registry | |
-- Specific OS can't set variable environment. Assuming it is fully packaged (no fused needed) | |
_os = love.system.getOS() | |
skip = skip or _os == 'Android' or _os == 'iOS' | |
or filesystem.getInfo('.fuseskip') ~= nil | |
-- mount the source base directory (parent directory) | |
assert(mount(filesystem.getSourceBaseDirectory(), root) or skip, | |
'The game is expected to run in fused mode (with "--fused").') | |
-- known issue: getDirectoryItems lists items on Save Directory too! | |
listOfItem = getDirectoryItems(root) | |
table.sort(listOfItem) | |
for i = 1, #listOfItem do | |
local last, v = #registry, listOfItem[i] | |
if #registry > limit then break end | |
if #v > 4 -- 1. must be more than four characters (including the dot and extension) | |
and not v:match(filePattern) -- 2. must not started by special characters | |
and filesystem.getRealDirectory(root .. v) ~= saveDir -- 3. must not on save dir | |
then | |
local fileData, w | |
-- try mount ANYTHING | |
registry[#registry + 1] = mount(v, root) | |
if last >= #registry and filesystem.getInfo(v, 'file') then | |
-- try mount file: works anywhere, BUT contents are loaded in-memory | |
-- TODO: A reference to point out which path it was originally from | |
fileData = filesystem.newFileData(v) | |
registry[#registry + 1] = mount(fileData, root) | |
-- if not stored (as userdata), then release (free up memory) | |
_ = last >= #registry and fileData:release() | |
end | |
w = last >= #registry and failed or success | |
w[#w + 1] = v -- record path for console report | |
end | |
end | |
report('Successfully mounted:', concat(success)) | |
report('Unable to mount:', concat(failed)) | |
report('Items on root:', concat(getDirectoryItems(root))) | |
report('Mounting time at:', timer.getTime()) | |
end | |
------------------------------------------------------------------------------- | |
-- * modification: package.path and package.cpath | |
------------------------------------------------------------------------------- | |
do | |
local sourceDir = filesystem.getSourceBaseDirectory() | |
local execPath = filesystem.getExecutablePath --[[ @as fun(): string ]]() | |
local path, cpath, sep, exec, libext | |
path, cpath, sep = package.path, package.cpath, package.config:sub(1, 1) | |
exec = execPath:gsub('\\', '/'):match('(.*/)'):gsub('/$', '') | |
libext = { Windows = 'dll', OSX = 'dylib', _ = 'so' } | |
-- Windows has "lua/?.lua" relatively to the executable | |
if sourceDir:lower() ~= exec:lower() then | |
path = ('@;!/#/?.lua;!/?/#/init.#'):gsub('.', { | |
['@'] = path, | |
['!'] = sourceDir, | |
['#'] = RUNTIME_EXTENSION or 'lua' | |
}) | |
package.path = path:gsub('/', sep):match('^%s*(.-)%s*$') | |
report('Append', sourceDir, 'to package.path:', package.path) | |
end | |
-- The only module to detect OS and its arch | |
if jit then | |
cpath = ('@;!/lib/*/?.#;!/lib/?.#'):gsub('.', { | |
['@'] = cpath, | |
['!'] = sourceDir, | |
['*'] = (jit.os .. '_' .. jit.arch):lower(), | |
['#'] = libext[jit.os] or libext._ | |
}) | |
package.cpath = cpath:gsub('/', sep):match('^%s*(.-)%s*$') | |
report('Append', sourceDir, 'to package.cpath:', package.cpath) | |
end | |
end | |
------------------------------------------------------------------------------- | |
-- * exec: run the game | |
------------------------------------------------------------------------------- | |
do | |
local candidates, hasGame, theGame, which, window, w, h | |
candidates = { | |
--'core.nogame', | |
--'core.game', | |
'nogame', | |
'game', | |
RUNTIME_GAME_MODULE | |
} | |
-- Try the game. | |
for i = #candidates, 1, -1 do | |
which = candidates[i] | |
hasGame, theGame = pcall(require, which) | |
report(hasGame and ('Located:\t' .. which) or theGame) | |
if hasGame then break end | |
end | |
-- Play le ducklon nogame? | |
if not hasGame then | |
print 'No game to be found. Ctrl+C to quit.' | |
theGame, window = noop, (love.window or require 'love.window') | |
if not love.graphics.isActive() and not RUNTIME_HEADLESS then | |
print '\nInitiating uninitialized window and graphics.' | |
print 'Known issue: it crashes on a machine with no display!\n' | |
w, h = window.getMode() | |
window.setMode(w, h, { | |
resizable = true, | |
highdpi = true, | |
}) | |
-- https://github.com/love2d/love/blob/11.x/src/scripts/nogame.lua | |
theGame = setfenv(require 'love.nogame', setmetatable({}, { __index = _G })) | |
end | |
window.setTitle(love_ver) | |
end | |
---@cast theGame fun(t: table, ...) | |
---Run the game. | |
theGame(main, ...) | |
end | |
return main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Prepacked ready to distributed (prototype) that has identical contents as above to here.
Additional information: