-- I actually wrote this for a stackoverflow question: -- http://stackoverflow.com/questions/35369854/is-it-possible-to-make-a-collapsing-variables-without-making-individual-function/35385290#35385290 -- I had fun writing it and released it on stackoverflow anyway, so now I also put it on my pastebin. -- There's some kind of summary-ish thing at the bottom of the script, along with the output. 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) --[[OUTPUT FROM ABOVE border.x=20, test.text.left=73 border.x=30, test.text.left=83 border.x=40, test.text.left=93 ]] if true then return end -- API "summary" below 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)