From 403167b3663c93061da9486deb646d62607f2b08 Mon Sep 17 00:00:00 2001 From: David Vogel Date: Sat, 30 Jul 2022 00:32:11 +0200 Subject: [PATCH] Fix compatibility stuff - Make dofile more conform to standard lua - Move dofile from live-reload.lua to compatibility.lua - Let dofile and require throw errors on script errors - Fix bug in recursion detection - Remove Noita's dofile annotation - Fix some EmmyLua annotations - Improve print replacement --- files/libraries/noita-api/annotations.lua | 4 -- files/libraries/noita-api/compatibility.lua | 63 ++++++++++++++------- files/libraries/noita-api/live-reload.lua | 27 +-------- init.lua | 4 +- 4 files changed, 48 insertions(+), 50 deletions(-) diff --git a/files/libraries/noita-api/annotations.lua b/files/libraries/noita-api/annotations.lua index 7001de6..6215eb6 100644 --- a/files/libraries/noita-api/annotations.lua +++ b/files/libraries/noita-api/annotations.lua @@ -1416,10 +1416,6 @@ function SetValueBool(key, value) end ---@param default_value number ---@return boolean function GetValueBool(key, default_value) end ----Returns the script's return value, if any. Returns nil,error_string if the script had errors. ----@param filename string ----@return any script_return_type, string|nil error_string -function dofile(filename) end ---Runs the script only once per lua context, returns the script's return value, if any. Returns nil,error_string if the script had errors. For performance reasons it is recommended scripts use dofile_once(), unless the standard dofile behaviour is required. ---@param filename string ---@return any script_return_type, string|nil error_string diff --git a/files/libraries/noita-api/compatibility.lua b/files/libraries/noita-api/compatibility.lua index 6f977f5..9ad3cb9 100644 --- a/files/libraries/noita-api/compatibility.lua +++ b/files/libraries/noita-api/compatibility.lua @@ -10,14 +10,16 @@ if _NoitaAPICompatibilityWrapperGuard_ then return function(dummy) end end _NoitaAPICompatibilityWrapperGuard_ = true --- Override print function to behave more like the standard lua one. local oldPrint = print -function print(...) - local arg = { ... } - local stringArgs = {} - for i, v in ipairs(arg) do - table.insert(stringArgs, tostring(v)) +-- Emulated print function that behaves more like the standard lua one. +function print(...) + local n = select("#", ...) + + --for i, v in ipairs(arg) do + local stringArgs = {} + for i = 1, n do + table.insert(stringArgs, tostring(select(i, ...))) end oldPrint(unpack(stringArgs)) @@ -42,25 +44,40 @@ package.loaded = package.loaded or { --os = os, } +local oldDofile = dofile + +---Emulated dofile to execute a lua script from disk and circumvent any caching. +---Noita for some reason caches script files (Or loads them into its virtual filesystem)(Or caches compiled bytecode), so reloading script files from disk does not work without this. +--- +---This conforms more with standard lua. +---@param path string +---@return any ... +function dofile(path) + local func, err = loadfile(path) + if not func then error(err) end + + return func() +end + local oldRequire = require local recursionSet = {} -local recursionLast ---Emulated require function in case the Lua API is restricted. ---It's probably good enough for most usecases. --- ----We need to override the default require in any case, as only dofile can access stuff in the virtual filesystem. +---We need to override the default require in any case, as only dofile and loadfile can access stuff in the virtual filesystem. ---@param modName string ----@return any +---@return any ... function require(modName) -- Check if package was already loaded, return previous result. if package.loaded[modName] ~= nil then return package.loaded[modName] end if recursionSet[modName] then - error(string.format("Cyclic dependency with module %q called by %q", modName, recursionLast)) + recursionSet = {} + error(string.format("Cyclic dependency with module %q", modName)) end - recursionSet[modName], recursionLast = true, modName + recursionSet[modName] = true local notFoundStr = "" @@ -71,7 +88,7 @@ function require(modName) if res == nil then res = true end package.loaded[modName] = res - recursionSet[modName], recursionLast = nil, nil + recursionSet[modName] = nil return res else notFoundStr = notFoundStr .. string.format("\tno field package.preload['%s']\n", modName) @@ -84,14 +101,22 @@ function require(modName) local filePath = string.gsub(pathEntry, "?", modPath, 1) -- Insert modPath into "?" placeholder. local fixedPath = string.gsub(filePath, "^%.[\\/]", "") -- Need to remove "./" or ".\" at the beginning, as Noita allows only "data" and "mods". if fixedPath:sub(1, 4) == "data" or fixedPath:sub(1, 4) == "mods" then -- Ignore everything other than data and mod root path elements. It's not perfect, but this is just there to prevent console spam. - local res, err = dofile(fixedPath) - if err then - notFoundStr = notFoundStr .. string.format("\tno file '%s'\n", filePath) - else + local func, err = loadfile(fixedPath) + if func then + local state, res = pcall(func) + if not state then + recursionSet = {} + error(res) + end if res == nil then res = true end package.loaded[modName] = res - recursionSet[modName], recursionLast = nil, nil + recursionSet[modName] = nil return res + elseif err and err:sub(1, 45) == "Error loading lua script - file doesn't exist" then -- I hate to do that. + notFoundStr = notFoundStr .. string.format("\tno file '%s'\n", filePath) + else + recursionSet = {} + error(err) end else notFoundStr = notFoundStr .. string.format("\tnot allowed '%s'\n", filePath) @@ -102,14 +127,14 @@ function require(modName) if oldRequire then local ok, res = pcall(oldRequire, modName) if ok then - recursionSet[modName], recursionLast = nil, nil + recursionSet[modName] = nil return res else notFoundStr = notFoundStr .. string.format("\toriginal require:%s", res) end end - recursionSet[modName], recursionLast = nil, nil + recursionSet = {} error(string.format("module %q not found:\n%s", modName, notFoundStr)) end diff --git a/files/libraries/noita-api/live-reload.lua b/files/libraries/noita-api/live-reload.lua index 5d1b76c..8b267c5 100644 --- a/files/libraries/noita-api/live-reload.lua +++ b/files/libraries/noita-api/live-reload.lua @@ -5,31 +5,12 @@ -- Allows Noita mods to reload themselves every now and then. -- This helps dramatically with development, as we don't have to restart Noita for every change. --- To accomplish this, we need to override the default behavior of dofile and some other things. local LiveReload = {} -local oldDofile = dofile ----Overwritten dofile to execute a lua script from disk and cirumvent any caching. ----Noita for some reason caches script files (Or loads them into its virtual filesystem)(Or caches compiled bytecode), so reloading script files from disk does not work without this. ---- ----This is not fully conform the the standard lua implementation, but so isn't Noita's implementation. ----@param path string ----@return any result ----@return string|nil err -function dofile(path) ---TODO: Consider moving dofile into compatibility.lua - local func, err = loadfile(path) - if not func then return nil, err end - - local status, res = pcall(func) - if not status then return nil, res end - - return res, nil -end - ---Reloads the mod's init file in the given interval in frames. ---For reloading to work correctly, the mod has to be structured in a special way. ----Like the usage of require and namespaces. +---Like the usage of require, namespaces, correct error handling, ... --- ---Just put this into your `OnWorldPreUpdate` or `OnWorldPostUpdate` callback: --- @@ -42,11 +23,7 @@ function LiveReload:Reload(modPath, interval) if self.Counter < interval then return end self.Counter = nil - local res, err = dofile(modPath .. "init.lua") - if err then - print(string.format("Error reloading mod: %s", err)) - error(string.format("Error reloading mod: %s", err)) - end + dofile(modPath .. "init.lua") end return LiveReload diff --git a/init.lua b/init.lua index e65a1e0..f8c39ca 100644 --- a/init.lua +++ b/init.lua @@ -24,7 +24,7 @@ end local CameraAPI = require("noita-api.camera") local Coords = require("coordinates") local DebugAPI = require("noita-api.debug") ---local LiveReload = require("noita-api.live-reload") +local LiveReload = require("noita-api.live-reload") local Vec2 = require("noita-api.vec2") ----------------------- @@ -120,7 +120,7 @@ function OnWorldPostUpdate() Message:CatchException("OnWorldPostUpdate", function() -- Reload mod every 60 frames. -- This allows live updates to the mod while Noita is running. - -- !!! DISABLE THIS LINE AND THE CORRESPONDING REQUIRE BEFORE COMMITTING !!! + -- !!! DISABLE THE FOLLOWING LINE BEFORE COMMITTING !!! --LiveReload:Reload("mods/noita-mapcap/", 60) -- Run checks every 60 frames.