-
Notifications
You must be signed in to change notification settings - Fork 4
Lua preprocessor
When loading Lua scripts, CSP also precompiles them, slowing down first launch, but making subsequent launches faster (that’s what all those files in “AssettoCorsa\cache\lua” are for).
But during that step there is another thing CSP does since v0.1.80-preview400: it runs a simplest preprocessor which can help with performance. It doesn’t add any syntax sugar or anything, but if you want squeeze extra bit of performance out of your scripts, it might help.
Currently, preprocessor only runs on Lua scripts starting with ---@ext
or ---@ext:verbose
, with latter one CSP will also print the final code in its log file. Alternatively, set FORCE_LUA_PREPROCESSOR=1
in “extension/config/general.ini” and it’ll run everywhere. Once we can be sure it won’t mess up everything, it’ll be enabled everywhere by default.
Preprocessor automatically replaces CSP enums (such as, for example, “ac.Wheel.FrontRight”) by the corresponding numerical value. Feel free to use actual enums and don’t worry about performance impact anymore.
Calls to bit.band()
and bit.bor()
can be resolved during preprocess stage if their arguments are constants or enums.
That’s the biggest one. There is a new function in CSP library now called const
which is implemented like this: function const(x) return x end
. Nothing special, but preprocessor can detect its calls and process code in a few different special ways, with the assumption that whatever is inside const()
call won’t change in the future. And if for one or another reason preprocessor won’t be able to resolve such a value, it won’t affect actual behaviour anyway!
Here is what this function can be used for:
-
Const computation
- Before
local value = const(math.pow(math.pi / math.pow(2, 0.1), 4.71))
- After
local value = 158.41298314282
This stuff can be used for things like conditions, for example, you can replace
if ac.getPatchVersionCode() > 2000 then …
byif const(ac.getPatchVersionCode() > 2000) then …
, making branch cheaper. While API for thoseconst()
calls is limited and not all functions are available, calling something unavailable will simply abort resolution of this particular call, so no big deal. - Before
-
Const definition
- Before
local myConst = const(17) callSomething(myConst)
- After
--cal myConst = const(17) callSomething(17)
This is what lead me to develop the thing, LuaJIT is not entirely perfect when it comes to resolving constants like that, so I found myself inlining values manually resulting in pretty hard to deal with code. Well, now it’s no longer a concern. And as you can see from that example, if name of a constant is not mentioned anywhere it couldn’t be replaced in, the actual definition will be commented out saving some memory.
Other types, such as strings or tables, are also supported:
- Before
local myConstTable = const(({ key = 2 * myConst })) callSomething(myConstTable.key)
- After
--cal myConstTable = const(({ key = 2 * myConst })) callSomething(34)
One caveat: tables will be inlined only if the code always refers to primitive table properties and never to a table itself. Otherwise, inlining it wouldn’t make a lot of sense.
- Before
-
Functions
- Before
local sumNumbers = const(function(x, y) return x + y end) callSomething(sumNumbers(1, 2), sumNumbers(A, B))
- After
--cal sumNumbers = const(function(x, y) return x + y end) callSomething(3, A + B)
This one can kind of mimic C macros in a strange way. Again, fully resolvable when possible, but if not, it’ll at least substitute call with the arguments. Might not always be the best idea to use it, but sometimes it might help.
- Before
-
A heavier computation
- Before
local fibonacci = const((function () local function loop(n) if n == 0 then return 0 elseif n == 1 or n == 2 then return 1 else local f1, f2, f3 = 1, 1, 1 for _ = 3, n do f3 = f1 + f2 f1 = f2 f2 = f3 end return f3 end end local result = {} for i = 1, 10 do result[i] = loop(i) end return result end)())
- After
local fibonacci = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }
CSP uses
stringify()
to turn complex types returned byconst()
to Lua-parseable values, so everything like vectors or matrices would also work. - Before
One good use for this system is for car physics scripts, where performance is really important. For example, you can move some of the settings in INI and LUT files, load them into const tables and then refer to them from your code, and from Lua perspective all those values will become constants, working even faster than if they’d be defined in Lua files directly. And don’t worry about editing: physics scripts specifically will be recompiled when any of files in data folder (or in “data.acd” file) change.
Another potentially interesting use case is to use const()
to prepare some Lua script itself, not just inlining some config-based settings, but altering the entire code logic, and then passing the result to loadstring()
. Basic loadstring(const(…))
will do the trick.