unity-lua运行时重载重启

开发环境中, 在不停止运行游戏的情况下, 刷新修改后的脚本逻辑, 提升开发效率


lua 虚拟机重启

就是销毁掉 旧的 luastate, 实例化新的 luastate

另一个使用场景是在热更完之后, 需要重启 lua 虚拟机, 使最新的 lua 脚本生效

  • csharp 重启逻辑

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 重启 lua 虚拟机
    public void RestartLua() {
    StartCoroutine(RestartLuaEngine());
    }

    IEnumerator RestartLuaEngine() {
    yield return 1;
    if (_luaMgr != null) {
    GameObject.Destroy(_luaMgr);
    yield return 1;
    _luaMgr = gameObject.AddComponent<LuaMgr>();
    yield return StartLuaMain();
    }
    }

    IEnumerator StartLuaMain() {
    yield return 1; // 跳一帧执行lua
    LuaMgr.GetMainState().DoFile("main.lua");
    _luaMgr.OnStart();
    CallLuaFunction("Main");
    }
  • 编辑器扩展提供一个按钮


lua 重载

重载只是刷新脚本的方法, 使修改后的方法逻辑生效

原理:

  1. 将 require 方法保存起来, 然后重写 require 逻辑
  2. 在 require 逻辑中捕获需要重载的 lua 文件
  3. 重新加载 这些 lua 文件.

例如:

  • 某个 class

    1
    2
    3
    4
    5
    6
    7
    8
    CUIPnlSettingLogic = CUIPnlSettingLogic or class() -- 这里使用全局变量
    CUIPnlSettingLogic.__name = "CUIPnlSettingLogic" -- 类名标记

    function CUIPnlSettingLogic.Init(self, id, obj)
    print("--- CUIPnlSettingLogic.Init 111")
    end

    return CUIPnlSettingLogic
  • 重写 require

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    local ipairs = ipairs
    local pairs = pairs
    local string = string
    local print = print
    local orgRequire = require
    local error = error

    local checkTbl = {
    "logic.",
    "module.",
    "main",
    "tolua_patch",
    }

    local ignoreTbl = {
    "debug_unit_test",
    }

    local notUsTbl = {} -- 不是我们的 lua 文件
    local usTbl1 = {} -- 有 xxx.__name
    local usTbl2 = {} -- 无 xxx.__name

    local function checkSameName(name, path)
    for k,v in pairs(usTbl1) do
    if name == v then
    error(string.format("--- same name: %s\npath1: %s\npath2: %s", name, k, path))
    end
    end
    end

    local function isCheck(path)
    for k,v in ipairs(checkTbl) do
    local i, _ = string.find(path, v)
    if i ~= nil then
    return true
    end
    end

    return false
    end

    local function isIgnore(path)
    for k,v in ipairs(ignoreTbl) do
    local i, _ = string.find(path, v)
    if i ~= nil then
    return true
    end
    end
    end

    require = function(path)
    local retTbl = orgRequire(path)

    if isIgnore(path) or not isCheck(path) then
    notUsTbl[path] = true
    return retTbl
    end

    if usTbl1[path] or usTbl2[path] then
    return retTbl
    end

    if type(retTbl) == "table" and retTbl.__name then
    checkSameName(retTbl.__name, path)
    usTbl1[path] = retTbl.__name
    else
    usTbl2[path] = true
    end
    return retTbl
    end

    gDumpReqTbl = function()
    dump(usTbl1, "--- usTbl1, has xxx.__name")
    dump(usTbl2, "--- usTbl2, no xxx.__name")
    dump(notUsTbl, "--- notUsTbl, has xxx.__name")
    end

    gRefreshReqTbl = function()

    local usTmpTbl1 = usTbl1
    usTbl1 = {}
    for k,_ in pairs(usTmpTbl1) do
    package.loaded[k] = nil
    end

    local usTmpTbl2 = usTbl2
    usTbl2 = {}
    for k,_ in pairs(usTmpTbl2) do
    package.loaded[k] = nil
    end

    for k,_ in pairs(usTmpTbl1) do
    require(k)
    end

    for k,_ in pairs(usTmpTbl2) do
    require(k)
    end

    gLog("<color=#00ff00ff>--- 重载 lua ok</color>")
    end

    需要刷的时候调用一下 gRefreshReqTbl 即可

  • 编辑器扩展提供一个按钮