无论是 如今开发中的游戏服务端, 仍是近期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
备注: 该机制只支持简单文件目录