無論是 如今開發中的遊戲服務端, 仍是近期love2D 開發的前端, 都使用 Lua 作腳本引擎, 須要涉及到 腳本的修改和重啓. 第一種方法是 寫個封裝函數, 裏面進行對全部 lua 腳本文件的 require() 操做, 這就要求 :前端
1.對每一個支持從新加載的文件進行函數
package.loaded[ filename] = nil require( filename)
2.文件加載要保持必定的順序, 以避免形成資源的錯亂.ui
就當前使用 love2D 前端來看, 其實只有一個 "啓動"文件: main.lua, 並在其內進行 各個子功能腳本的 require 加載.若是在 從新加載時, 自動按照 main.lua 提供的lua
require(...) 順序進行自動加載就行了, 而且無需像上面的針對每一個文件編寫:spa
function reload_files() require( f1) require( f2) ... end
總體目標有幾個:
1.無需靜態維護一個從新加載的文件, 或函數, 進行編寫 各個腳本文件的 require() 進行從新加載;code
2.可以按照當前文件中各個文件的 順序進行加載, 即若是orm
--main.lua require( "config") require( "function") require( "globals") require( "gameplayer") require( "scene")
NeedReset = true
...
這種順序編寫main.lua( 或其餘文件), 都儘可能保持 config > function > globals > gameplayer > scene 的順序進行從新加載;
3.可以避免 "已被從新記載的文件" 再次被從新加載;blog
4.可以避免 嵌套遞歸加載;遞歸
5.可以對 外部庫進行識別, 即 遊戲
require( "bit")
是在加載 "位操做"的 庫 bit.dll , 而不是 bit.lua, 不該該進行 嵌套加載;
6.可以識別某些 "禁止從新加載"的文件, 例如:
-- global.lua require( "skill_cfg") require( "effect_cfg") g_object_list = {}
global.lua 文件自己不能被 屢次require(), 否則 g_object_list 全局變量會被重置, 但又可以不會影響 skill_cfg 和 effect_cfg 的從新加載;
7.應該要支持 "後序" 方式進行加載, 記載加載 main.lua 過程當中, 應該如今遞歸加載完 子腳本文件:
require( "config") require( "function") require( "globals") require( "gameplayer") require( "scene")
而後在進行 加載 main.lua 的後序內容:
NeedReset = true ...
8.可以 識別 文件中的 require(...) 行.
大概這 8 點目標 和要求, 但對於第7點, 有個問題:
假設 從新加載 的 遞歸函數爲
function recursive_reload( filename) package.loaded[ filename] = nil
require( filename ) end
而且main.lua 的內容簡單有如:
--main.lua require( "config") require( "function") require( "globals") require( "gameplayer") require( "scene") NeedReset = true
在 觸發從新加載的 入口中:
function main_reloader() recursive_reload( "mian" ) end
調用 main_reloader() 進行從新加載的過程 展開將會如:
--先遞歸地使用 recursive_reload() 從新加載子文件 package.loaded[ 'config'] = nil require( 'config') package.loaded[ 'function'] = nil require( 'function') package.loaded[ 'globals'] = nil require( 'globals') package.loaded[ 'gameplayer'] = nil require( 'gameplayer') package.loaded[ 'scene'] = nil require( 'scene') --再最後加載 main.lua package.loaded[ 'main'] = nil require( 'main') --但就在這個操做中, 還會涉及到嵌套的:
require( "config") require( "function") require( "globals") require( "gameplayer") require( "scene") NeedReset = true
這 5 個 文件不就會被 屢次 require() 了嗎? 雖然 完整的 recursive_reload() 可以防止 "顯示的" 重複require(), 可是不能禁止 "隱式的" require() 其實, 就算第二次的 "隱式" requre() 確實會調用, 但不會從新加載 實際的物理文件, 見於 lua 開發手冊上:
require (modname) Loads the given module. The function starts by looking into the package.loaded table to determine whether modname is already loaded.
If it is, then require returns the value stored at package.loaded[modname].
Otherwise, it tries to find a loader for the module.
便是說, 只要曾經加載了 文件, 並在 package.loaded 內有記錄, 後序的 requre() 將會直接返回.
這 5 個 文件不就會被 屢次 require() 了嗎? 雖然 完整的 recursive_reload() 可以防止 "顯示的" 重複require(), 可是不能禁止 "隱式的" require() 其實, 就算第二次的 "隱式" requre() 確實會調用, 但不會從新加載 實際的物理文件, 見於 lua 開發手冊上:
require (modname) Loads the given module. The function starts by looking into the package.loaded table to determine whether modname is already loaded. If it is, then require returns the value stored at package.loaded[modname]. Otherwise, it tries to find a loader for the module.
便是說, 只要曾經加載了 文件, 並在 package.loaded 內有記錄, 後序的 requre() 將會直接返回.
具體運行效果:
只是具體的實現代碼:
-- 外部庫 登記 local package_list = { bit = true } -- 全局性質類/或禁止從新加載的文件記錄 local ignored_file_list = { global = true , } --已從新加載的文件記錄 local loaded_file_list = {} --視圖排版控制 function leading_tag( indent ) -- body if indent < 1 then return '' else return string.rep( ' |', indent - 1 ) .. ' ' end end --關鍵遞歸從新加載函數 --filename 文件名 --indent 遞歸深度, 用於控制排版顯示 function recursive_reload( filename, indent ) -- body if package_list[ filename] then --對於 外部庫, 只進行從新加載, 不作遞歸子文件 --卸載舊文件 package.loaded[ filename] = nil --裝載信文件 require( filename ) --標記"已被從新加載" loaded_file_list[ filename] = true print( leading_tag(indent) .. filename .. "... done" ) return true end --普通文件 --進行 "已被從新加載" 檢測 if loaded_file_list[ filename] then print( leading_tag(indent) .. filename .. "...already been reloaded IGNORED" ) return true end --讀取當前文件內容, 以進行子文件遞歸從新加載 local file, err = io.open( filename..".lua" ) if file == nil then print( string.format( "failed to reaload file(%s), with error:%s", filename, err or "unknown" ) ) return false end print( leading_tag(indent) .. filename .. "..." ) --讀取每一行 for line in file:lines() do --識別 require(...)行, 正則表達? 模式匹配? 並拾取文件名 到 subFileName line = string.gsub( line, '%s', '' ) local subFileName = nil local ret = string.gsub( line, '^require%("(.+)"%)', function ( s ) subFileName = s end ) if subFileName then --進行遞歸 local success = recursive_reload( subFileName, indent + 1 ) if not success then print( string.format( "failed to reload sub file of (%s)", filename ) ) return false end end end -- "後序" 處理當前文件... if ignored_file_list[ filename] then --忽略 "禁止被從新加載"的文件 print( leading_tag(indent) .. filename .. "... IGNORED" ) return true else --卸載舊文件 package.loaded[ filename] = nil --裝載新文件 require( filename ) --設置"已被從新加載" 標記 loaded_file_list[ filename] = true print( leading_tag(indent) .. filename .. "... done" ) return true end end --主入口函數 function reload_script_files() print( "[reload_script_files...]") loaded_file_list = {} --本項目是以 main.lua 爲主文件 recursive_reload( "main", 0 ) print( "[reload_script_files...done]") return "reload ok" end
備註: 該機制只支持簡單文件目錄