Implement VMF Package Manager (#29)

This commit is contained in:
Azumgi 2018-12-11 19:22:28 +03:00 committed by GitHub
commit 7e2faac683
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 197 additions and 1 deletions

View file

@ -116,6 +116,14 @@ function vmf.initialize_mod_data(mod, mod_data)
vmf.set_internal_data(mod, "is_mutator", mod_data.is_mutator)
vmf.set_internal_data(mod, "allow_rehooking", mod_data.allow_rehooking)
local mod_manager = Managers.mod
local current_mod_load_index = mod_manager._mod_load_index
if current_mod_load_index then
vmf.set_internal_data(mod, "mod_handle", mod_manager._mods[current_mod_load_index].handle)
else
mod:warning("Could not determine current mod load index. Package management won't be available for this mod.")
end
-- Register mod as mutator @TODO: calling this after options initialization would be better, I guess?
if mod_data.is_mutator then
vmf.register_mod_as_mutator(mod, mod_data.mutator_settings)
@ -163,4 +171,4 @@ end
-- VARIABLES
vmf.mods = _mods
vmf.mods_unloading_order = _mods_unloading_order
vmf.mods_unloading_order = _mods_unloading_order

View file

@ -0,0 +1,185 @@
local vmf = get_mod("VMF")
local NOOP = function() end
local _queued_packages = {}
local _loading_package = nil
local _loaded_packages = {}
-- #####################################################################################################################
-- ##### VMFMod ########################################################################################################
-- #####################################################################################################################
--[[
Loads a mod package.
* package_name [string] : package name. needs to be the full path to the `.package` file without the extension
* callback [function]: (optional) callback for asynchronous loading
* sync [boolean] : (optional) load the packages synchronously, freezing the game until it is loaded
--]]
function VMFMod:load_package(package_name, callback, sync)
if vmf.check_wrong_argument_type(self, "load_package", "package_name", package_name, "string") or
vmf.check_wrong_argument_type(self, "load_package", "callback", callback, "function", "nil") or
vmf.check_wrong_argument_type(self, "load_package", "sync", sync, "boolean", "nil")
then
return
end
if self:has_package_loaded(package_name) then
self:error("Package '%s' has already been loaded", package_name)
return
end
local mod_handle = self:get_internal_data("mod_handle")
if not mod_handle then
self:error("Failed to get mod handle. Package management is not available.")
return
end
if not _loaded_packages[self] then
_loaded_packages[self] = {}
end
local resource_package = Mod.resource_package(mod_handle, package_name)
if not resource_package then
self:error("Could not find package '%s'.", package_name)
return
end
local is_loading = self:is_package_loading(package_name)
if sync then
if not is_loading then
resource_package:load()
end
resource_package:flush()
_loaded_packages[self][package_name] = resource_package
else
if is_loading then
self:error("Package '%s' is currently loading", package_name)
return
end
table.insert(_queued_packages, {
mod = self,
package_name = package_name,
resource_package = resource_package,
callback = callback or NOOP,
})
end
end
--[[
Unlaods a loaded mod package.
* package_name [string]: package name. needs to be the full path to the `.package` file without the extension
--]]
function VMFMod:unload_package(package_name)
if vmf.check_wrong_argument_type(self, "unload_package", "package_name", package_name, "string") then
return
end
if not self:has_package_loaded(package_name) then
self:error("Package '%s' has not been loaded", package_name)
return
end
local resource_package = _loaded_packages[self][package_name]
resource_package:unload()
Mod.release_resource_package(resource_package)
_loaded_packages[self][package_name] = nil
end
--[[
Returns whether the mod package is currently being loaded.
* package_name [string]: package name. needs to be the full path to the `.package` file without the extension
--]]
function VMFMod:is_package_loading(package_name)
if vmf.check_wrong_argument_type(self, "is_package_loading", "package_name", package_name, "string") then
return
end
if _loading_package and _loading_package.mod == self and _loading_package.package_name == package_name then
return true
end
for _, queued_package in ipairs(_queued_packages) do
if queued_package.mod == self and queued_package.package_name == package_name then
return true
end
end
return false
end
--[[
Returns whether the mod package has been fully loaded.
* package_name [string]: package name. needs to be the full path to the `.package` file without the extension
--]]
function VMFMod:has_package_loaded(package_name)
if vmf.check_wrong_argument_type(self, "has_package_loaded", "package_name", package_name, "string") then
return
end
local loaded_packages = _loaded_packages[self]
return loaded_packages and loaded_packages[package_name] ~= nil
end
-- #####################################################################################################################
-- ##### VMF internal functions and variables ##########################################################################
-- #####################################################################################################################
-- Loads queued packages one at a time
function vmf.update_package_manager()
local loading_package = _loading_package
if loading_package and loading_package.resource_package:has_loaded() then
loading_package.resource_package:flush()
_loaded_packages[loading_package.mod][loading_package.package_name] = loading_package.resource_package
_loading_package = nil
-- The callback has to be called last, so that any calls to `has_package_loaded` or `is_package_loading`
-- return the correct value
vmf.safe_call_nr(loading_package.mod, {"'%s' package loaded callback", loading_package.package_name},
loading_package.callback, loading_package.package_name)
end
local queued_package = _queued_packages[1]
if queued_package and not _loading_package then
_loading_package = queued_package
table.remove(_queued_packages, 1)
_loading_package.resource_package:load()
end
end
-- Forcefully unloads all mods and cleans the queue.
function vmf.unload_all_resource_packages()
for mod, packages in pairs(_loaded_packages) do
for package_name in pairs(packages) do
mod:warning(
"Force-unloading package '%s'. Please make sure to properly release packages when the mod is unloaded",
package_name
)
mod:unload_package(package_name)
end
end
_queued_packages = {}
if _loading_package then
_loading_package.mod:warning(
"Still loading package '%s'. Memory leaks may occur when unloading while a package is loading.",
_loading_package.package_name
)
_loading_package.callback = function(package_name)
_loading_package.mod:unload_package(package_name)
end
end
end

View file

@ -13,6 +13,7 @@ local vmf_mod_object = {}
function vmf_mod_object:init()
dofile("scripts/mods/vmf/modules/vmf_mod_data")
dofile("scripts/mods/vmf/modules/vmf_mod_manager")
dofile("scripts/mods/vmf/modules/vmf_package_manager")
dofile("scripts/mods/vmf/modules/core/safe_calls")
dofile("scripts/mods/vmf/modules/core/events")
dofile("scripts/mods/vmf/modules/core/settings")
@ -55,6 +56,7 @@ end
-- #####################################################################################################################
function vmf_mod_object:update(dt)
vmf.update_package_manager()
vmf.mods_update_event(dt)
vmf.check_keybinds()
vmf.execute_queued_chat_command()
@ -90,6 +92,7 @@ function vmf_mod_object:on_reload()
if VT1 then vmf.reset_map_view() end
vmf.mods_unload_event(false)
vmf.remove_custom_views()
vmf.unload_all_resource_packages()
vmf.hooks_unload()
vmf.reset_guis()
end