Skip to content

Instantly share code, notes, and snippets.

@gphg
Created October 31, 2024 12:17
Show Gist options
  • Save gphg/349d8084d3a02a35d408e7fa5368cc34 to your computer and use it in GitHub Desktop.
Save gphg/349d8084d3a02a35d408e7fa5368cc34 to your computer and use it in GitHub Desktop.
My "plug and play"-ish entry point Lua script for LOVE2D.
-------------------------------------------------------------------------------
--- 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
@gphg
Copy link
Author

gphg commented Oct 31, 2024

Prepacked ready to distributed (prototype) that has identical contents as above to here.

Additional information:

  • These are products from my project (hosted on GitHub privately), there aren't much, it is just embarrassing how little progress I have been doing. Contact me personally to gain the access.
  • These uploaded files are hosted on pomf-like file hosting. GitHub has restricted file type for attachment. Uploaded file stays for 90 days.
  • Executable binary (.exe) for Win32 is false positive as I checked these on VirusTotal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment