【问题标题】:Is it possible to make a collapsing variables without making individual functions?是否可以在不制作单个函数的情况下制作折叠变量?
【发布时间】:2016-05-24 00:44:23
【问题描述】:

我的代码以少量变量开始,然后使用这些初始变量生成更多元素。

function new( x, y, width, height )
    local object = {}
    --border
    object.border = { x = x, y = y, width = width, height = height }
    --body
    object.body = { x = x+1, y = y+1, width = width-2, height = height-2 }
    --font
    object.font = {}
    object.font.size = (object.body.height+2)-(math.floor((object.body.height+2)/4)+1)
    object.font.height = love.graphics.setNewFont( object.font.size ):getHeight()
    --padding
    object.padding = {}
    object.padding.height = math.floor(object.border.height*(2/29))
    object.padding.width = object.padding.height*3
    --text
    object.text = { input = '' }
    object.text.centerHeight = math.ceil(object.body.y+((object.body.height-object.font.height)/2))
    object.text.left = object.body.x+object.padding.width+object.padding.height
    --backspacing
    object.backspace = {key = false, rate = 3, time = 0, pausetime = 20, pause = true}
    --config
    object.config = { active = true, devmode = false, debug = false, id = gui.id(), type = 'textbox' }
    gui.add(object)
    return object.config.id
end

当我在中间部分修改某些内容时,整个事情变得一团糟,因为从我更改的那个开始,直到底部的值彼此不一致

local x = gui.get(2)
x.body.height = 50

我正在寻找是否有办法重新定义这些变量,从它们开始直到底部,而不是:(a) 为每个变量创建函数。 (b) 在函数中编辑所需的参数。

如果没有,是否有另一种有效地做到这一点的方法?

编辑: 变量结构如下:

border->body->padding->font

我需要的是一种可以定义任何的方法,以便后面的内容也发生如下变化:

object.body.x = 15

它会从那个重新定义的变量折叠到底部:

body->padding->font

我可以像这样从编辑的变量到底部重新定义它们:

--not the actual code, just an example of variables dependent on the variable above
object.body.x = 15
object.padding.width = object.body.x+1
object.font.size = object.padding.width+1

但这意味着在重新定义填充时我必须这样做,直到字体效率极低,尤其是当我扩展更多元素时。

示例:

--padding->font
object.padding.width = 5
object.font.size = object.padding.width+1

【问题讨论】:

  • 这里不清楚你的意思。什么样的修改“中间”会导致事情“变得一团糟”。举个例子吧。
  • @NicolBolas 我在底部给出了一个示例,我所有的变量都存储在本地,并使用 gui.get() 和 gui.add() 等进行访问。我在中间部分的意思是当我例如更改 body.height 元素时,我还想更改 text.left 元素,因为它也是使用 body.height 定义的。有点像多米诺骨牌效应,当你影响某事时会崩溃,但只会在一个方向上而不是在所有方面。
  • 你可以使用 __newindex 和一个代理表...
  • 我所需要的只是 goto 在 C++ 中的工作方式,但这在 lua 中不可用,我需要创建一个从更改开始到底部编辑 var 的函数,(对于每个动态变量)。所以 1 个长函数用于重新定义动态变量,因此底部的变量也会发生变化,这是非常不切实际的。我希望有人能提供帮助。

标签: function variables lua


【解决方案1】:

我很无聊,看到这个问题(再次)和一个重复。 我开始写一些有趣的代码,结果是这样的:

local function getNeededVars(tab,func)
    local needed,this = {}
    this = setmetatable({},{
        __index = function(s,k)
            -- See if the requested variable exists.
            -- If it doesn't, we obviously complain.
            -- If it does, we log it and return the value.
            local var = tab.vars[k]
            if not var then
                error("Eh, "..k.." isn't registered (yet?)",5)
            end needed[k] = true return tab.vals[k]
        end;
    }) func(this) return needed
end

local function updateStuff(self,key,done)
    for k,v in pairs(self.levars) do
        if v.needed and v.needed[key] then
            if not done[v] then done[v] = true
                self.vals[v.name] = v.func(self)
                updateStuff(self,v.name,done)
            end
        end
    end
end

local function createSubTable(self,key,tab)
    return setmetatable({},{
        __newindex = function(s,k,v)
            tab[k] = v updateStuff(self,key,{})
        end; __index = tab;
    })
end

local dependenceMeta
dependenceMeta = {
    __index = function(self,k)
        -- Allow methods, because OOP
        local method = dependenceMeta[k]
        if method then return method end
        local variable = self.vars[k]
        if not variable then
            error("Variable "..k.." not found",2)
        end return self.vals[k]
    end;
    __newindex = function(self,k,v)
        local variable = self.vars[k]
        if not variable then
            error("Use :Register() to add stuff",2)
        elseif type(v) == "table" then
            self.vals[k] = createSubTable(self,k,v)
            return updateStuff(self,k,{})
        end self.vals[k] = v updateStuff(self,k,{})
    end
}
function dependenceMeta:Register(var,value)
    local varobject = {func=value,name=var}
    self.vars[var] = varobject
    table.insert(self.levars,varobject)
    if type(value) == "function" then
        varobject.needed = getNeededVars(self,value)
        self.vals[var] = value(self)
    elseif type(value) == "table" then
        self.vals[var] = createSubTable(self,var,value)
    elseif value then
        self.vals[var] = value
    end
end
function dependenceMeta:RegisterAll(tab)
    for k,v in pairs(tab) do
        self:Register(k,v)
    end
end

local function DependenceTable()
    return setmetatable({
        levars = {};
        vars = {};
        vals = {};
    },dependenceMeta)
end

local test = DependenceTable()
test:Register("border",{
    x=20; y=50;
    height=200;
    width=100;
})
test:Register("body",function(self)
    return {x=self.border.x+1,y=self.border.y+1,
        height=self.border.height-2,
        width=self.border.width-2}
end)
test:Register("font",function(self)
    local size = (self.body.height+2)-(math.floor((self.body.height+2)/4)+1);
    return { size = size; -- Since we use it in the table constructor...
        height = size-4; --love.graphics.setNewFont( self.font.size ):getHeight();
        -- I don't run this on love, so can't use the above line. Should work though.
    }
end)
test:Register("padding",function(self)
    local height = math.floor(self.border.height*(2/29))
    return { height = height; width = height*3 } -- again dependency
end)
test:Register("text",{input=""}) -- Need this initially to keep input
test:Register("text",function(self)
    return { input = self.text.input;
        centerHeight = math.ceil(self.body.y+((self.body.height-self.font.height)/2));
        left = self.body.x+self.padding.width+self.padding.height;
    }
end)
test:Register("backspace",{key = false, rate = 3, time = 0, pausetime = 20, pause = true})
-- Again, didn't use gui.id() on the line below because my lack of LÖVE
test:Register("config",{active=true,devmode=false,debug=false,id=123,type='textbox'})

print("border.x=20, test.text.left="..test.text.left)
test.border = {x=30; y=50; height=200; width=100;}
print("border.x=30, test.text.left="..test.text.left)
test.border.x = 40
print("border.x=40, test.text.left="..test.text.left)

代码很多,但我喜欢写它。它给出了很好的输出:

border.x=20, test.text.left=73
border.x=30, test.text.left=83
border.x=40, test.text.left=93

只有在编辑其依赖项之一时才会重新计算所有属性。我让它也适用于子表,这有点棘手,但最后实际上看起来很容易。您可以编辑(例如) body 字段,方法是将其设置为全新的表或在现有表中设置字段,如最后几行所示的代码 sn-p。当您将其分配给新表时,它将在其上设置一个元表。您也不能使用对 (& co),除非您使用 5.2 并且可以使用 __pairs。

它可能会解决您的问题。如果不是这样,我写它很开心,所以至少我写这篇文章总是积极的。 (你必须承认,那是一些漂亮的代码。嗯,它的工作方式,而不是实际的格式)

注意:如果您要使用它,请取消注释 love.graphicsgui.id 部分,因为我没有有 LÖVE,我显然必须测试代码。

这是我的东西 API 的快速“摘要”,因为它一开始可能会令人困惑:

local hmm = DependenceTable() -- Create a new one
print(hmm.field) -- Would error, "field" doesn't exist yet

-- Sets the property 'idk' to 123.
-- Everything except functions and tables are "primitive".
-- They're like constants, they never change unless you do it.
hmm:Register("idk",123)
-- If you want to actually set a regular table/function, you
-- can register a random value, then do hmm.idk = func/table
-- (the "constructor registering" only happens during :Register())

-- Sets the field to a constructor, which first gets validated.
-- During registering, the constructor is already called once.
-- Afterwards, it'll get called when it has to update.
-- (Whenever 'idk' changes, since 'field' depends on 'idk' here)
hmm:Register("field",function(self) return self.idk+1 end)
-- This errors because 'nonexistant' isn't reigstered yet
hmm:Register("error",function(self) return self.nonexistant end)
-- Basicly calls hmm:Register() twice with key/value as parameters
hmm:RegisterAll{
    lower = function(self) return self.field - 5 end;
    higher = function(self) return self.field + 5 end;
}
-- This sets the property 'idk' to 5.
-- Since 'field' depends on this property, it'll also update.
-- Since 'lower' and 'higher' depend on 'field', they too.
-- (It happens in order, so there should be no conflicts)
hmm.idk = 5
-- This prints 6 since 'idk' is 5 and 'field' is idk+1
print(hmm.field)

您可以使用 setfenv(如果是 Lua 5.1)来消除对“self.FIELD”的需求。借助一些环境魔法,您可以将“字段”的构造函数 (例如) 设为 function() return idk+1 end

【讨论】:

    【解决方案2】:

    您可以使用元表,更具体地说,__newindex 字段:

    (嗯,需要和__index字段结合起来,但是诶)

    function new(x, y, width, height )
        local object = {
            font = {}, padding = {}, text = {input=''}, -- tables themself are static
            -- also I assume text.input will change and has to stay the way it is
        }
        -- more static data here (yes yes, I know. The code is a bit ugly, but if it works fine...)
        object.config = { active = true, devmode = false, debug = false, id = gui.id(), type = 'textbox' }
        object.backspace = {key = false, rate = 3, time = 0, pausetime = 20, pause = true}
            object.border = { x = x, y = y, width = width, height = height }
        -- stuff that has to be calculated from the above variables goes below
        local border = object.border
        local function calculate()
            --border
            --body
            object.body = { x = border.x+1, y = border.y+1, width = border.width-2, height = border.height-2 }
            --font
            object.font.size = height-(math.floor(height/4)+1)
            object.font.height = love.graphics.setNewFont( object.font.size ):getHeight()
            --padding
            object.padding.height = math.floor(object.border.height*(2/29))
            object.padding.width = object.padding.height*3
            --text
            object.text.centerHeight = math.ceil(object.body.y+((object.body.height-object.font.height)/2))
            object.text.left = object.body.x+object.padding.width+object.padding.height
            --backspacing
            --config
        end
        calculate()
        local proxy = setmetatable({},{
            __index = object; -- proxy.abc returns object.abc (to get width, use proxy.border.width)
            __newindex = function(s,k,v)
                -- fires whenever 'proxy[k] = v' is done
                -- I assume you'll only change x/y/width/height, as other properties are dynamic
                -- Doing 'proxy.x = 123' is the same as 'object.border.x = 123' + recalculating
                object.border[k] = v -- Actually apply the change
                calculate() -- Recalculate the other properties that depends on the above
            end;
        })
        gui.add(object)
        return object.config.id
    end
    

    您可以运行 proxy.x = 12 之类的代码来编辑 X 属性。将重新计算所有值。这不是最好的,但您的代码一点点 烦人需要改进。 (但是,嘿,如果它适合你,那很好)

    注意:您只能设置xywidthheight。您可以通过旧方式获取所有属性,例如proxy.padding.width (注意 proxy.x 不起作用。使用 proxy.border.x)

    【讨论】:

    • 重新定义 x/w/width/height 是我最小的问题,因为我可以使用我想给出的新值来回忆原始函数。然后替换存储它的表中的整个索引。
    猜你喜欢
    • 1970-01-01
    • 2013-07-13
    • 2015-01-06
    • 1970-01-01
    • 2021-11-22
    • 2021-10-11
    • 1970-01-01
    • 2022-12-20
    • 2015-04-25
    相关资源
    最近更新 更多