Lua runs on a single thread, so any check must be explicitly performed by your code.
The act of executing code immediately after changing a variable is known as "lookup."
If you program in an environment where a set of codes for each frame is started (for example, a game), you can check it manually. For instance:
WatchedVariables = { a = 5, b = 22, } WatchedVariables_cache = {} for k,v in pairs(WatchedVariables) do WatchedVariables_cache[k] = v end function OnFrame() print("NEXT FRAME! (possibly 1 second later or something)") for k,v in pairs(WatchedVariables) do local v_old = WatchedVariables_cache[k] if v ~= v_old then -- this is the "callback" print(tostring(k).." changed from "..tostring(v_old).." to "..tostring(v)) WatchedVariables_cache[k] = v end end end function SomeFunctionThatOperatesSomeTime() print("about to change a, brother!") WatchedVariables.a = -7 print("a is changed") end
After the next frame, a callback code (print) will be executed. The disadvantage of this approach is that the callback code does not print immediately after WatchedVariables.a
set to -7
, that is: the output will be:
about to change a, brother! a is changed NEXT FRAME! (possibly 1 second later or something) a changed from 5 to -7
To prevent this potentially unwanted behavior, you can use the setter function, for example:
MyObject = { _private_a = 5, set_a = function(self, new_value_of_a) self._private_a = 5 -- callback code print("a set to "..tostring(new_value_of_a)) end, get_a = function(self) return self._private_a end } function SomeFunctionThatOperatesSomeTime() print("about to change a, brother!") MyObject:set_a(-7) print("a is changed") end
The result of this code shows that the callback is immediate:
about to change a, brother! a set to -7 a is changed
To make this more comfortable, Lua provides metatables that make this behavior transparent to the programmer. Example:
MyObject = { __privates = { a = 5, } } MyObject_meta = { __index = function(self, k) return rawget(self, "__privates")[k] end, __newindex = function(self, k, v) rawget(self, "__privates")[k] = v -- callback code print("a set to "..tostring(v)) end, } setmetatable(MyObject, MyObject_meta) function SomeFunctionThatOperatesSomeTime() print("about to change a, brother!") MyObject.a = -7 print("a is changed") end
The result of this code will be the same as in the previous example:
about to change a, brother! a set to -7 a is changed
Here is an implementation for an example of your example:
MyObject = { __privates = { a = 5, } __private_callback = function(self, k, ov, v) if k == "a" and v == "100" then print("a is 100!") end end } MyObject_meta = { __index = function(self, k) return rawget(self, "__privates")[k] end, __newindex = function(self, k, v) local privates = rawget(self, "__privates") local ov = privates[k] privates[k] = v rawget(self, "__private_callback")(self, k, ov, v) end, } setmetatable(MyObject, MyObject_meta) function SomeFunctionThatOperatesSomeTime() MyObject.a = -7 -- prints nothing MyObject.a = 100 -- prints "a is 100!" MyObject.a = 22 -- prints nothing end
Why are the __privates
and __private_callback
variables prefixed with two underscores? Matching the prefix of private members that should not be addressed in typical programming situations with two underscores. If you are familiar with the object-oriented methodology and its implementation in languages such as Java and C ++, you will understand how they look like the private
and protected
keywords.
If you are familiar with C #, you can see how the set_a
/ get_a
and metatable implementers look like accessors ( set
/ get
).