One of the main reasons why game servers use Lua is that it is easy to hot update. Even if the server is running, you can rewrite some of its functions for hot update purposes by simply having it execute a piece of code. For example, the module
app has a function
If we want to hot change
foo to multiply
b, we just need to have the server load and run the following code:
In many cases, however, functions are not so simple. Functions often depend on a number of upvalues, for a more complex example:
In this example, we have to be careful when writing hotfix code,
foo has upper values
bar . Some people say it’s better to just execute the whole file? No, Lua is flexible, and executing the whole file may cause other problems. In this case it would cause
M.n to be reset (I personally don’t recommend storing state in module space, but people do it all the time). In some complex cases, functions may have multiple dependencies, e.g.
bar in the upper value of
bar in its upper value, etc. This can make hot-replacement a lot more difficult.
To solve this problem, I wrote a tool hotfix-gen that analyzes the code, extracts the dependencies of the functions, and generates the hotfix code. We can install it using
We want to hotfix the
foo function, by running
hotfix app foo:
This allows it to automatically generate hotfix code. It assumes that the upper value itself (not the reference) on which the function depends is immutable, as in the following code:
The extraction will be problematic. So the generated code still needs to be reviewed and tested. However, as long as the code meets certain specifications, the generated results will be fine; and it is much faster and more accurate than writing it manually.
The hotfix-gen implementation uses a dumb approach, which is to read the code, compile it into a syntax tree, and then analyze the syntax tree. There is a
debug.getupvalue, but this must be run with the code. In addition, for statements like
local a = b * 2 we need to know that
a depends on
b. But the good news is that parsing the code is not that complicated, we have a ready-made library: lua-parser. The lua-parser will use
lpeg to parse the Lua source code into a syntax tree. We just need to parse the syntax tree.
The main task is to identify variable definitions and references, which requires consideration of scope. For example, in the following code,
foo depends on
a but not on
b . But if
print(b) is outside the
for block, then
foo will depend on
There are also some subtle syntaxes that must be considered. For example,
local function f() is not the same as
local f = function(). In the example below,
foo depends on the
local foo = 1 defined on top of it, but
bar does not, the
bar in the function
bar is itself.
The implementation consists of the following steps:
- Scan each local variable in the block scope of the file, and analyze their dependencies.
- If a target function is encountered, analyze the target function’s dependencies as well.
- Iterate through the dependency network starting from the target function to get all the statements to be extracted. The order of the statements remains the same.
- Generate the target code.
The final code is only about 300 lines, which is not very complicated. After testing, it can handle various cases accurately.