From f12bc8a9f0a9843b79a42127ea13d16b786da802 Mon Sep 17 00:00:00 2001 From: Azumgi <4zumgi@gmail.com> Date: Thu, 3 Jan 2019 15:28:19 +0300 Subject: [PATCH 1/6] [VMF Package Manager] Big overhaul --- .../mods/vmf/modules/vmf_package_manager.lua | 217 ++++++++++-------- 1 file changed, 120 insertions(+), 97 deletions(-) diff --git a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua index 17ea5d2..b98faa3 100644 --- a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua +++ b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua @@ -1,10 +1,58 @@ local vmf = get_mod("VMF") -local NOOP = function() end - +local _packages = {} local _queued_packages = {} -local _loading_package = nil -local _loaded_packages = {} + +local ERRORS = { + REGULAR = { + -- check_vt1: + cant_use_vmf_package_manager_in_vt1 = "[VMF Package Manager] (%s): you can't use VMF package manager in VT1 " .. + "because VT1 mods don't support more than 1 resource package.", + -- VMFMod:load_package: + package_already_loaded = "[VMF Package Manager] (load_package): package '%s' has already been loaded.", + package_not_found = "[VMF Package Manager] (load_package): could not find package '%s'.", + package_already_queued = "[VMF Package Manager] (load_package): package '%s' is already queued for loading.", + -- VMFMod:unload_package: + package_not_loaded = "[VMF Package Manager] (unload_package): package '%s' has not been loaded.", + cant_unload_loading_package = "[VMF Package Manager] (unload_package): package '%s' can't be unloaded because " .. + "it's currently loading." + + }, + PREFIX = { + package_loaded_callback = "[VMF Package Manager] '%s' package loaded callback execution" + } +} + +local WARNINGS = { + force_unloading_package = "[VMF Package Manager] Force-unloading package '%s'. Please make sure to properly " .. + "release packages when the mod is unloaded", + still_loading_package = "[VMF Package Manager] Still loading package '%s'. Memory leaks may occur when unloading " .. + "while a package is loading." +} + +-- ##################################################################################################################### +-- ##### Local functions ############################################################################################### +-- ##################################################################################################################### + +local function check_vt1(mod, function_name) + if VT1 then + mod:error(ERRORS.REGULAR.cant_use_vmf_package_manager_in_vt1, function_name) + return true + end +end + + +-- Brings the resources in the loaded package in game and executes callback. +local function flush_package(package_name) + local package_data = _packages[package_name] + package_data.resource_package:flush() + package_data.status = "loaded" + + local callback = package_data.callback + if callback then + vmf.safe_call_nr(package_data.mod, {ERRORS.PREFIX.package_loaded_callback, package_name}, callback, package_name) + end +end -- ##################################################################################################################### -- ##### VMFMod ######################################################################################################## @@ -17,50 +65,49 @@ local _loaded_packages = {} * 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 + if check_vt1(self, "load_package") or + 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) + if self:package_status(package_name) == "loaded" then + self:error(ERRORS.REGULAR.package_already_loaded, package_name) return end - if not _loaded_packages[self] then - _loaded_packages[self] = {} - end - local resource_package = Mod.resource_package(self:get_internal_data("mod_handle"), package_name) if not resource_package then - self:error("Could not find package '%s'.", package_name) + self:error(ERRORS.REGULAR.package_not_found, package_name) return end - local is_loading = self:is_package_loading(package_name) + -- If package is_already_queued it means it was already loaded asynchroniously before, but not fully loaded yet. + -- It can have "queued" or "loading" status. Don't redefine data for this package. + local is_already_queued = _packages[package_name] ~= nil + if not is_already_queued then + _packages[package_name] = { + status = "queued", + resource_package = resource_package, + callback = callback, + mod = self + } + end if sync then - if not is_loading then + -- If package wasn't loaded asynchroniously before and is not already loading. + if _packages[package_name].status == "queued" then resource_package:load() end - - resource_package:flush() - - _loaded_packages[self][package_name] = resource_package + flush_package(package_name) else - if is_loading then - self:error("Package '%s' is currently loading", package_name) - return + if is_already_queued then + self:error(ERRORS.REGULAR.package_already_queued, package_name) + else + table.insert(_queued_packages, package_name) end - - table.insert(_queued_packages, { - mod = self, - package_name = package_name, - resource_package = resource_package, - callback = callback or NOOP, - }) end end @@ -70,56 +117,49 @@ end * 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 + if check_vt1(self, "unload_package") or + 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) + local package_status = self:package_status(package_name) + if not package_status then + self:error(ERRORS.REGULAR.package_not_loaded, package_name) return end - local resource_package = _loaded_packages[self][package_name] - - 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 + if package_status == "queued" then + for i, queued_package_name in ipairs(_queued_packages) do + if package_name == queued_package_name then + table.remove(_queued_packages, i) + break + end end + elseif package_status == "loading" then + self:error(ERRORS.REGULAR.cant_unload_loading_package, package_name) + return + elseif package_status == "loaded" then + Mod.release_resource_package(_packages[package_name].resource_package) end - return false + _packages[package_name] = nil end --[[ - Returns whether the mod package has been fully loaded. + Returns package status string. * 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 +function VMFMod:package_status(package_name) + if check_vt1(self, "package_status") or + vmf.check_wrong_argument_type(self, "package_status", "package_name", package_name, "string") + then return end - local loaded_packages = _loaded_packages[self] - return loaded_packages and loaded_packages[package_name] ~= nil + local package_data = _packages[package_name] + return package_data and package_data.status end -- ##################################################################################################################### @@ -128,51 +168,34 @@ end -- 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() + local queued_package_name = _queued_packages[1] + if queued_package_name then + local package_data = _packages[queued_package_name] + if package_data.status == "loading" and package_data.resource_package:has_loaded() then + flush_package(queued_package_name) + table.remove(_queued_packages, 1) + end - _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() + if package_data.status == "queued" then + package_data.resource_package:load() + package_data.status = "loading" + end end end --- Forcefully unloads all mods and cleans the queue. +-- Forcefully unloads all not unloaded pacakges. 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) + for package_name, package_data in pairs(_packages) do + local package_status = package_data.status + + if package_status == "loaded" then + package_data.mod:warning(WARNINGS.force_unloading_package, package_name) + package_data.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) + if package_status == "loading" then + package_data.mod:warning(WARNINGS.still_loading_package, package_name) end end end From 514d417626ca60e6d7a370cc44262ed3ee2c9566 Mon Sep 17 00:00:00 2001 From: Azumgi <4zumgi@gmail.com> Date: Fri, 4 Jan 2019 09:38:53 +0300 Subject: [PATCH 2/6] [VMF Package Manager] Better queue handling --- .../mods/vmf/modules/vmf_package_manager.lua | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua index b98faa3..35c1a40 100644 --- a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua +++ b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua @@ -54,6 +54,16 @@ local function flush_package(package_name) end end + +local function remove_package_from_queue(package_name) + for i, queued_package_name in ipairs(_queued_packages) do + if package_name == queued_package_name then + table.remove(_queued_packages, i) + return + end + end +end + -- ##################################################################################################################### -- ##### VMFMod ######################################################################################################## -- ##################################################################################################################### @@ -101,6 +111,9 @@ function VMFMod:load_package(package_name, callback, sync) if _packages[package_name].status == "queued" then resource_package:load() end + if is_already_queued then + remove_package_from_queue(package_name) + end flush_package(package_name) else if is_already_queued then @@ -130,12 +143,7 @@ function VMFMod:unload_package(package_name) end if package_status == "queued" then - for i, queued_package_name in ipairs(_queued_packages) do - if package_name == queued_package_name then - table.remove(_queued_packages, i) - break - end - end + remove_package_from_queue(package_name) elseif package_status == "loading" then self:error(ERRORS.REGULAR.cant_unload_loading_package, package_name) return From 646805d15eec5ed073c744c5a0995d78e878aa1e Mon Sep 17 00:00:00 2001 From: Azumgi <4zumgi@gmail.com> Date: Fri, 4 Jan 2019 09:48:40 +0300 Subject: [PATCH 3/6] [VMF Package Manager] Small changes in comments --- vmf/scripts/mods/vmf/modules/vmf_package_manager.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua index 35c1a40..d5b7ac6 100644 --- a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua +++ b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua @@ -42,7 +42,7 @@ local function check_vt1(mod, function_name) end --- Brings the resources in the loaded package in game and executes callback. +-- Brings resources of the loaded package in game and executes callback. local function flush_package(package_name) local package_data = _packages[package_name] package_data.resource_package:flush() @@ -71,7 +71,7 @@ end --[[ 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 + * callback [function]: (optional) callback for when loading is done * sync [boolean] : (optional) load the packages synchronously, freezing the game until it is loaded --]] function VMFMod:load_package(package_name, callback, sync) @@ -107,7 +107,7 @@ function VMFMod:load_package(package_name, callback, sync) end if sync then - -- If package wasn't loaded asynchroniously before and is not already loading. + -- Load resource package if it's not already loading. if _packages[package_name].status == "queued" then resource_package:load() end @@ -126,7 +126,7 @@ end --[[ - Unlaods a loaded mod package. + Unloads 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) @@ -174,7 +174,7 @@ end -- ##### VMF internal functions and variables ########################################################################## -- ##################################################################################################################### --- Loads queued packages one at a time +-- Loads queued packages one at a time. function vmf.update_package_manager() local queued_package_name = _queued_packages[1] if queued_package_name then @@ -192,7 +192,7 @@ function vmf.update_package_manager() end --- Forcefully unloads all not unloaded pacakges. +-- Forcefully unloads all not unloaded packages. function vmf.unload_all_resource_packages() for package_name, package_data in pairs(_packages) do local package_status = package_data.status From 242ae7c71095c1710fa3dc7489deac987f3efc00 Mon Sep 17 00:00:00 2001 From: Azumgi <4zumgi@gmail.com> Date: Fri, 4 Jan 2019 11:29:29 +0300 Subject: [PATCH 4/6] [VMF Package Manager] Fix typo --- vmf/scripts/mods/vmf/modules/vmf_package_manager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua index d5b7ac6..85bcb07 100644 --- a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua +++ b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua @@ -94,7 +94,7 @@ function VMFMod:load_package(package_name, callback, sync) return end - -- If package is_already_queued it means it was already loaded asynchroniously before, but not fully loaded yet. + -- If package is_already_queued it means it was already loaded asynchronously before, but not fully loaded yet. -- It can have "queued" or "loading" status. Don't redefine data for this package. local is_already_queued = _packages[package_name] ~= nil if not is_already_queued then From d9a6696174ce89b375e5925799191112589a2ed8 Mon Sep 17 00:00:00 2001 From: Azumgi <4zumgi@gmail.com> Date: Sun, 13 Jan 2019 16:44:26 +0300 Subject: [PATCH 5/6] [VMF Package Manager] Add loading cancellation --- .../mods/vmf/modules/vmf_package_manager.lua | 68 +++++++++++-------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua index 85bcb07..3b157ae 100644 --- a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua +++ b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua @@ -42,15 +42,21 @@ local function check_vt1(mod, function_name) end --- Brings resources of the loaded package in game and executes callback. +-- Brings resources of the loaded package in game and executes callback. Or unloads package's resources, if loading was +-- cancelled. local function flush_package(package_name) local package_data = _packages[package_name] - package_data.resource_package:flush() - package_data.status = "loaded" - local callback = package_data.callback - if callback then - vmf.safe_call_nr(package_data.mod, {ERRORS.PREFIX.package_loaded_callback, package_name}, callback, package_name) + if package_data.status == "loading_cancelled" then + Mod.release_resource_package(package_data.resource_package) + _packages[package_name] = nil + else + package_data.resource_package:flush() + package_data.status = "loaded" + local callback = package_data.callback + if callback then + vmf.safe_call_nr(package_data.mod, {ERRORS.PREFIX.package_loaded_callback, package_name}, callback, package_name) + end end end @@ -83,7 +89,7 @@ function VMFMod:load_package(package_name, callback, sync) return end - if self:package_status(package_name) == "loaded" then + if _packages[package_name] and _packages[package_name].status == "loaded" then self:error(ERRORS.REGULAR.package_already_loaded, package_name) return end @@ -94,15 +100,19 @@ function VMFMod:load_package(package_name, callback, sync) return end - -- If package is_already_queued it means it was already loaded asynchronously before, but not fully loaded yet. - -- It can have "queued" or "loading" status. Don't redefine data for this package. - local is_already_queued = _packages[package_name] ~= nil - if not is_already_queued then + -- (is_package_already_queued == true) => Package was already loaded asynchronously before, but not fully loaded yet. + -- (is_package_loading_cancelled == true) => Package is in the process of loading, but once it's loaded it's going + -- to be unloaded. + -- If package is not already queued create new entry for it. If it's already queued, but has "loading_cancelled" + -- status, update its callback. + local is_package_already_queued = (_packages[package_name] ~= nil) + local is_package_loading_cancelled = _packages[package_name] and _packages[package_name].status == "loading_cancelled" + if not is_package_already_queued or is_package_loading_cancelled then _packages[package_name] = { - status = "queued", - resource_package = resource_package, - callback = callback, - mod = self + status = is_package_loading_cancelled and "loading" or "queued", + resource_package = is_package_loading_cancelled and _packages[package_name].resource_package or resource_package, + callback = callback, + mod = self } end @@ -111,15 +121,18 @@ function VMFMod:load_package(package_name, callback, sync) if _packages[package_name].status == "queued" then resource_package:load() end - if is_already_queued then + if is_package_already_queued then remove_package_from_queue(package_name) end flush_package(package_name) else - if is_already_queued then - self:error(ERRORS.REGULAR.package_already_queued, package_name) - else - table.insert(_queued_packages, package_name) + -- If package loading was cancelled before, don't add it to loading queue, because it's still in loading queue. + if not is_package_loading_cancelled then + if is_package_already_queued then + self:error(ERRORS.REGULAR.package_already_queued, package_name) + else + table.insert(_queued_packages, package_name) + end end end end @@ -136,7 +149,7 @@ function VMFMod:unload_package(package_name) return end - local package_status = self:package_status(package_name) + local package_status = _packages[package_name] and _packages[package_name].status if not package_status then self:error(ERRORS.REGULAR.package_not_loaded, package_name) return @@ -144,14 +157,13 @@ function VMFMod:unload_package(package_name) if package_status == "queued" then remove_package_from_queue(package_name) + _packages[package_name] = nil elseif package_status == "loading" then - self:error(ERRORS.REGULAR.cant_unload_loading_package, package_name) - return + _packages[package_name].status = "loading_cancelled" elseif package_status == "loaded" then Mod.release_resource_package(_packages[package_name].resource_package) + _packages[package_name] = nil end - - _packages[package_name] = nil end @@ -179,7 +191,9 @@ function vmf.update_package_manager() local queued_package_name = _queued_packages[1] if queued_package_name then local package_data = _packages[queued_package_name] - if package_data.status == "loading" and package_data.resource_package:has_loaded() then + if (package_data.status == "loading" or package_data.status == "loading_cancelled") and + package_data.resource_package:has_loaded() + then flush_package(queued_package_name) table.remove(_queued_packages, 1) end @@ -202,7 +216,7 @@ function vmf.unload_all_resource_packages() package_data.mod:unload_package(package_name) end - if package_status == "loading" then + if package_status == "loading" or package_status == "loading_cancelled" then package_data.mod:warning(WARNINGS.still_loading_package, package_name) end end From 6226eb93127020423a38c683f7571707eb78c09e Mon Sep 17 00:00:00 2001 From: Azumgi <4zumgi@gmail.com> Date: Sun, 13 Jan 2019 16:47:26 +0300 Subject: [PATCH 6/6] [VMF Package Manager] Add public status list --- vmf/scripts/mods/vmf/modules/vmf_package_manager.lua | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua index 3b157ae..fceeb15 100644 --- a/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua +++ b/vmf/scripts/mods/vmf/modules/vmf_package_manager.lua @@ -3,6 +3,13 @@ local vmf = get_mod("VMF") local _packages = {} local _queued_packages = {} +local PUBLIC_STATUSES = { + queued = "loading", -- Package is in the loading queue waiting to be loaded. + loading = "loading", -- Package is loading. + loaded = "loaded", -- Package is loaded + loading_cancelled = nil -- Package is loading, but will be unloaded once done loading. +} + local ERRORS = { REGULAR = { -- check_vt1: @@ -179,7 +186,9 @@ function VMFMod:package_status(package_name) end local package_data = _packages[package_name] - return package_data and package_data.status + if package_data then + return PUBLIC_STATUSES[package_data.status] + end end -- #####################################################################################################################