These mysterious addon incompatibilities have been appearing for a while now, generally involving what seemed like well-sanitized and thoughtfully crafted addons. The other addon owners I've seen to fall afoul of them have realized that changing the weights of their addons was enough to fix them, blamed the fault on some obscure feature of superloading and then moved on.
A mere few days after uploading my latest big update to the Beholder, there've been two incompatibility reports with two separate addons that have been mysterious in the same way. I was desperate to find out what was happening, but all I could do was comment lines out in hooks/load.lua files until I came across the culprit. And here it is: The humble require call.
(Warning! Dense chunk of technical mumbo-jumbo incoming...)
When T-Engine uses the loadAddons function, it goes through each addon in the list, mounting superloaded files to the virtual directory structure, mounting overloaded files to the virtual directory structure, and then running files in the "hooks" folder so as to execute the class:bindHook function. The distinction, however, with superloads and overloads being mounted and hook files being run causes a difficulty. When libraries are loaded with the require function, they are kept in memory permanently in their current state. If require loads files before T-Engine builds the references to all superloaded versions of those files, then any superloads coming after are disregarded.
(Warning! Frustatingly simple solution incoming...)
All you have to do is: Load hook files after all superloading and overloading.
Here's my proposed fix to the loadAddons function in default/engine/Module.lua. I have tested it and it works beautifully!
Code: Select all
function _M:loadAddons(mod, saveuse)
local hashlist = {}
local adds = self:listAddons(mod, true)
if saveuse then saveuse = table.reverse(saveuse) end
-- Filter based on settings
for i = #adds, 1, -1 do
local add = adds[i]
local removed = false
if saveuse then
if not saveuse[add.short_name] then
print("Removing addon "..add.short_name..": not allowed by savefile")
table.remove(adds, i) removed = true
end
else
if add.cheat_only and not config.settings.cheat then
print("Removing addon "..add.short_name..": cheat mode required")
table.remove(adds, i) removed = true
elseif add.dlc == "no" then
print("Removing addon "..add.short_name..": donator required")
table.remove(adds, i) removed = true
elseif add.id_dlc == "no" then
print("Removing addon "..add.short_name..": DLC not granted")
table.remove(adds, i) removed = true
elseif config.settings.addons[add.for_module] and config.settings.addons[add.for_module][add.short_name] ~= nil then
-- Forbidden by config
if config.settings.addons[add.for_module][add.short_name] == false then
print("Removing addon "..add.short_name..": not allowed by config")
table.remove(adds, i) removed = true
end
else
-- Forbidden by version
if not add.natural_compatible then
table.remove(adds, i) removed = true
end
end
end
if add.dlc and add.dlc_files then
if not removed and add.dlc_files.classes then
for _, name in ipairs(add.dlc_files.classes) do
print("Preloading DLC class", name)
local data = profile:getDLCD(add.for_module.."-"..add.short_name, ("%d.%d.%d"):format(add.version[1],add.version[2],add.version[3]), name:gsub("%.", "/")..".lua")
if data and data ~= '' then
profile.dlc_files.classes[name] = data
elseif not __module_extra_info.ignore_addons_not_loading then
print("Removing addon "..add.short_name..": DLC class not received")
table.remove(adds, i) removed = true
if saveuse then
-- The savefile requires it, but we couldnt activate it, abord
core.game.setRebootMessage(([[The savefile requires the #YELLOW#%s#WHITE# addon.
Some of its features require being online and could not be enabled. To prevent damaging the savefile loading was aborded.
You may try to force loading if you are sure the savefile does not use that addon, at your own risk, by checking the "Ignore unloadable addons" checkbox on the load game screen..]]):format(add.long_name))
util.showMainMenu(nil, nil, nil, nil, nil, nil, "show_ignore_addons_not_loading=true")
end
break
else
add.dlc = "no"
print("Removing addon "..add.short_name..": dlc file required not found")
table.remove(adds, i) removed = true
end
end
end
end
end
mod.addons = {}
local hooks_list = {}
_G.__addons_superload_order = {}
for i, add in ipairs(adds) do
add.version_name = ("%s-%s-%d.%d.%d"):format(mod.short_name, add.short_name, add.version[1], add.version[2], add.version[3])
print("Binding addon", add.long_name, add.teaa, add.version_name)
local base
if add.teaa then
base = fs.getRealPath(add.teaa)
else
base = fs.getRealPath(add.dir)
end
if add.data then
print(" * with data")
if add.teaa then fs.mount("subdir:/data/|"..fs.getRealPath(add.teaa), "/data-"..add.short_name, true)
else fs.mount(base.."/data", "/data-"..add.short_name, true)
end
end
if add.superload then
print(" * with superload")
if add.teaa then fs.mount("subdir:/superload/|"..fs.getRealPath(add.teaa), "/mod/addons/"..add.short_name.."/superload", true)
else fs.mount(base.."/superload", "/mod/addons/"..add.short_name.."/superload", true)
end
table.insert(_G.__addons_superload_order, add.short_name)
end
if add.overload then
print(" * with overload")
if add.teaa then fs.mount("subdir:/overload/|"..fs.getRealPath(add.teaa), "/", false)
else fs.mount(base.."/overload", "/", false)
end
end
if add.hooks then
if add.teaa then fs.mount("subdir:/hooks/|"..fs.getRealPath(add.teaa), "/hooks/"..add.short_name, true)
else fs.mount(base.."/hooks", "/hooks/"..add.short_name, true)
end
hooks_list[#hooks_list+1] = "/hooks/"..add.short_name
print(" * with hooks (passed to post-processing))")
end
-- Compute addon md5
local hash_valid, hash_err
if config.settings.cheat then
hash_valid, hash_err = false, "cheat mode skipping addon validation"
else
local fmd5 = self:addonMD5(add)
hashlist[#hashlist+1] = {module=mod.short_name, addon=add.version_name, md5=fmd5}
-- hash_valid, hash_err = profile:checkAddonHash(mod.short_name, add.version_name, fmd5)
end
-- if hash_err then hash_err = hash_err .. " [addon: "..add.short_name.."]" end
-- add.hash_valid, add.hash_err = hash_valid, hash_err
mod.addons[add.short_name] = add
end
--We load hooks at the end of all superloads and overloads
--If the code in /hooks/ is run earlier, it will load early versions of the dependent files and subsequent superloads and overloads will be disregarded.
print("Post-processing hooks.")
for i, dir in ipairs(hooks_list) do
self:setCurrentHookDir(dir.."/")
dofile(dir.."/load.lua")
self:setCurrentHookDir(nil)
end
return hashlist
end