xLua icon indicating copy to clipboard operation
xLua copied to clipboard

util.state 实现方案的疑问

Open kimomi opened this issue 2 years ago • 1 comments

现在 util 文件中给 C# 对象新增字段的方案为 util.state,实现其实就是使用 Lua 的元表。 但是现在发现会有这样的问题:

-- 在某个地方加了一个新字段
util.state(csObj, {
    lua_newTable1 = {}
})
-- 在另一个地方加了一个新字段
util.state(csObj, {
    lua_newTable2 = {}
})

csObj.lua_newTable1.flag = true
-- 想要置空 lua_newTable1 这个字段
csObj.lua_newTable1 = nil 

print('csObj.lua_newTable1:' .. tostring(csObj.lua_newTable1))

期望的结果是 csObj 中 lua_newTable1 这个字段被确实置空。 但是,实际结果是访问 csObj.lua_newTable1还是会有数据。 输出为:

LUA: csObj.lua_newTable1:table: 000001F0D9079CA0

原因是,util.state 的实现有把旧的 table 数据完全复制到新的 table 中

-- 现在的实现方案
local function state(csobj, state)
    local csobj_mt = getmetatable(csobj)
    -- 把上次的 state 表数据放到这次的 state 表中
    for k, v in pairs(csobj_mt) do rawset(state, k, v) end
    local csobj_index, csobj_newindex = state.__index, state.__newindex
    state.__index = function(obj, k)
        return rawget(state, k) or csobj_index(obj, k)
    end
    state.__newindex = function(obj, k, v)
        if rawget(state, k) ~= nil then
            rawset(state, k, v)
        else
            csobj_newindex(obj, k, v)
        end
    end
    debug.setmetatable(csobj, state)
    return state
end

这就导致,数据其实是有两份的,设置 csObj.lua_newTable1 = nil 其实只是把最上一层state表的 lua_newTable1 置空了,最开始那层state表的 lua_newTable1 还是存在的。 感觉比较合适的实现方案是这样的:

-- 多层嵌套 state 不是走复制数据,而是走元表,保证数据唯一
local function state(csobj, state)
    local csobj_mt = getmetatable(csobj)
    -- for k, v in pairs(csobj_mt) do rawset(state, k, v) end
    local csobj_index, csobj_newindex = state.__index, state.__newindex
    state.__index = function(obj, k)
        return rawget(state, k) or (csobj_index and csobj_index(obj, k)) or csobj_mt[k]
    end
    state.__newindex = function(obj, k, v)
        if rawget(state, k) ~= nil then
            rawset(state, k, v)
        elseif csobj_newindex then
            csobj_newindex(obj, k, v)
        else
            csobj_mt[k] = v
        end
    end
    debug.setmetatable(csobj, state)
    return state
end

kimomi avatar Apr 22 '22 04:04 kimomi

最后的写法有点问题,好像这样更合理

local function state(csobj, state)
    local csobj_mt = getmetatable(csobj)
    -- for k, v in pairs(csobj_mt) do rawset(state, k, v) end
    local csobj_index, csobj_newindex = state.__index, state.__newindex
    state.__index = function(obj, k)
        -- 先看最上层走__index元方法,再看下一层
        return rawget(state, k) or (csobj_index and csobj_index(obj, k)) or rawget(csobj_mt, k) or csobj_mt.__index(obj, k)
    end
    state.__newindex = function(obj, k, v)
        -- 对应__index元方法的实现
        if rawget(state, k) ~= nil then
            rawset(state, k, v)
        elseif csobj_newindex then
            csobj_newindex(obj, k, v)
        elseif rawget(csobj_mt, k) ~= nil then
            rawset(csobj_mt, k, v)
        else
            csobj_mt.__newindex(obj, k, v)
        end
    end
    debug.setmetatable(csobj, state)
    return state
end

kimomi avatar Apr 22 '22 06:04 kimomi