LUA - 创建元表,使每个子表也成为一个元表

但这肯定会让人感到困惑。

我对LUA还很陌生,其中一个我尚未深入研究的问题就是元表。

我需要找到一种方法来创建一个元表,在编辑值时运行函数。如果我保持在“一个级别”上,也就是第一个索引,那么这不是问题。然后我可以简单地使用__newindex来运行它。但是我正在尝试的是无论修改什么值都运行该函数。

这将需要某种方式将元表内的任何表再次设置为运行与“主”元表相同的函数的元表

在我的用例中,这将是一个“保存”函数:

function MySaveFunction(tbl)
   FileSave(my_settings_path, tbl)
end

MyTable = setmetatable()
MyTable.Value = value --> 运行MySaveFunction(MyTable.Value)

MyTable.SubTable = {} --> 在SubTable上运行setmetatable()
MyTable.SubTable.Value = value --> 运行MySaveFunction(MyTable.SubTable.Value)

MyTable.SubTable.SubSubTable = {} --> 在SubSubTable上运行setmetatable()
MyTable.SubTable.SubSubTable.Value = value --> 运行MySaveFunction(MyTable.SubTable.SubSubTable.Value)

MyTable.SubTable.SubSubSubTable = {} --> 在SubSubSubTable上运行setmetatable()
MyTable.SubTable.SubSubSubTable.Value = value --> 运行MySaveFunction(MyTable.SubTable.SubSubSubTable.Value)

希望有人能帮我 <.<

点赞
用户4567755
用户4567755

首先需要注意的是,__newindex__index 元方法只在处理目标表中的 nil 值时触发。如果想要追踪每个变化,就不能只使用 __newindex,因为一旦写入一个值,后续调用将不会做任何事情。相反,需要使用代理表。

另一个需要考虑的重要问题是跟踪访问成员的路径。

最后,还需要记住在实现处理程序时使用原始访问函数,例如 rawget。否则,可能会遇到堆栈溢出或其他奇怪的行为。

让我们举个简单的例子来说明这些问题:

local mt = {}
function mt.__newindex (t, key, value)
    if type(value) == "table" then
        rawset(t, key, setmetatable(value, mt)) -- 为嵌套表设置元表
        -- 在这里使用 `t[key] = setmetatable(value, mt)` 会导致溢出。
    else
        print(t, key, "=", value) -- 我们希望在标准输出中看到每次写入。
        rawset(t, key, value)
    end
end

local root = setmetatable({}, mt)
root.first = 1                   -- table: 0xa40c30 first   =   1
root.second = 2                  -- table: 0xa40c30 second  =   2
root.nested_table = {}           -- /nothing/
root.nested_table.another = 4    -- table: 0xa403a0 another =   4
root.first = 5                   -- /nothing/

现在,我们需要处理它们。让我们先从创建代理表的方法开始:

local
function make_proxy (data)
    local proxy = {}
    local metatable = {
        __index = function (_, key) return rawget(data, key) end,
        __newindex = function (_, key, value)
            if type(value) == "table" then
                rawset(data, key, make_proxy(value))
            else
                print(data, key, "=", value) -- 或者你的保存函数在这里!
                rawset(data, key, value)
            end
        end
    }
    return setmetatable(proxy, metatable) -- setmetatable() 简单地返回 `proxy`
end

这样,你有三个表:proxy_、_metatable 和 _data_。用户访问 _proxy_,但因为每次访问它时都是完全空的,所以从 metatable 中调用 __index__newindex 元方法。这些处理程序访问 data 表格,以检索或设置用户感兴趣的实际值。

以与前面相同的方式运行并改善代码:

local root = make_proxy{}
root.first = 1                   -- table: 0xa40c30 first   =   1
root.second = 2                  -- table: 0xa40c30 second  =   2
root.nested_table = {}           -- /nothing/
root.nested_table.another = 4    -- table: 0xa403a0 another =   4
root.first = 5                   -- table: 0xa40c30 first   =   5

这应该让你了解在这里为什么要使用代理表以及如何处理其元方法。

剩下的问题就是如何确定要访问的字段的路径。这部分在 另一个问题的另一个答案 中有涉及。我不觉得有必要重复它。

2020-12-02 17:25:16