zoukankan      html  css  js  c++  java
  • Lua 脚本重启 机制

    不管是 现在开发中的游戏服务端, 还是近期love2D 开发的前端, 都使用 Lua 做脚本引擎, 需要涉及到 脚本的修改和重启. 第一种方法是 写个封装函数, 里面进行对所有 lua 脚本文件的 require() 操作, 这就要求 :

    1.对每个支持重新加载的文件进行

    package.loaded[ filename]  = nil
    require( filename)

    2.文件加载要保持一定的顺序, 以免造成资源的错乱.

    就当前使用 love2D 前端来看, 其实只有一个 "启动"文件: main.lua, 并在其内进行 各个子功能脚本的 require 加载.如果在 重新加载时, 自动按照 main.lua 提供的

    require(...) 顺序进行自动加载就好了, 并且无需像上面的针对每个文件编写:

    function reload_files()
        require( f1)
        require( f2)
        ...
    
    end

    整体目标有几个:
    1.无需静态维护一个重新加载的文件, 或函数, 进行编写 各个脚本文件的 require() 进行重新加载;

    2.能够按照当前文件中各个文件的 顺序进行加载, 即如果

    --main.lua
    
    require( "config")
    require( "function")
    require( "globals")
    require( "gameplayer")
    require( "scene") 

    NeedReset = true
    ...

    这种顺序编写main.lua( 或其他文件), 都尽量保持 config > function > globals > gameplayer > scene 的顺序进行重新加载;
    3.能够避免 "已被重新记载的文件" 再次被重新加载;

    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

    备注: 该机制只支持简单文件目录

  • 相关阅读:
    【JBPM4】State 节点
    【JBPM4】EL表达式的使用,实现JAVA与JPDL的交互
    不常见的javascript调试技巧
    mac navicat premium 使用技巧
    fnb2b分支拉取注意事项
    mac上为nodejs设置环境变量
    nodejs项目进程管理器之pm2
    有关defer和async的区别
    关于viewport我自己的理解
    样式技巧总结
  • 原文地址:https://www.cnblogs.com/Wilson-Loo/p/3301989.html
Copyright © 2011-2022 走看看