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 foo
If we want to hot change foo
to multiply a
and 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 M
, database
and 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 foo
, bar
in its upper value, etc. This can make hot-replacement a lot more difficult.
hotfix-gen
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 luarocks
:
|
|
We want to hotfix the app
module’s 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.
Implementation principle
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 b
again.
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.