Pazyryk
Deity
- Joined
- Jun 13, 2008
- Messages
- 3,584
July 12, 2013 Update: The new patch has fixed runtime errors for Lua called from GameEvents. However, I have seen at least one situation where errors crash silently without error report: when the Lua chunk was called as a registered callback from a UI button. One other reason you might want the code below is if you want errors to cause a popup in game. I've found this very useful as error lines are easy to miss when you have 100s of other lines of print.
The 60 character truncation has also been fixed, so error lines are now informative for file name.
Edit: If you want to jump straight to the Error Handler, go to post #2. This first post kind of shows how it developed and may help you understand how it works.
The Problems
Runtime Error reports used to work reliably when there was an error in your Lua code. Then they stopped (working reliably). I believe this happened with the vanilla pre-G&K patch 1.511 in June 2012, and it has always been the case for G&K. A more minor (but annoying and time-wasting) problem is that when you do get an error report, the error file path is truncated such that you can't use it. You still get a line number, but you have to guess the file. Sometimes that's easy, sometimes not (especially if you have >20 files).
I managed to track the first problem down to GameEvents here. Basically, any Lua errors downstream of a GameEvents call will crash silently without a Runtime Error report (even if it is many function calls downstream or goes through a LuaEvents call). However, errors downstream of an Events call (or a call from the Live Tuner) are reported correctly. So this is certainly a bug in the DLL somewhere. Based on whoward69's comments, it is not in the DLL we have access to. Fortunately, there is an all-Lua solution to this problem.
On the truncated file names, that sort of got fixed as a side effect of fixing problem 1 (in post #2).
Some Background
Your Lua code is always called by C++ from somewhere. To my knowledge, there are only 5 ways this can happen:
OK. When C++ calls Lua, the function called and any functions called by it (and so on...) are called a "chunk". If there is an error anywhere in that chunk, Lua stops itself and sends some information back to C++. Lua errors never (to my knowledge) cause a Crash To Desktop -- that's something only C++ can do. Of course, Lua is calling C++ any time you call something like Game.GetHolyCityForReligion(ReligionType, PlayerID) and that C++ can certainly CTD (in this example, it will do so if the religion specified isn't founded yet). But back to my point, when Lua crashes it stops and sends info back to the C++ that called it. It is up to C++ to do something with that. In situations #2-5 above, that "something" is to print an error statement. In situation #1 above, that something is nothing. So no error report. (This is clearly a bug.)
The workaround
You could wait for Firaxis to fix the GameEvents bug. I doubt that they are even aware of it and I have no idea how to communicate with them. The truncated file path issue is not actually a bug at all -- it is the default behavior of the Lua library debug.traceback method. Firaxis could fix that too (by supplying their own traceback method) but I doubt that will ever happen.
In the meantime, you can use pcall in Lua to restore error reporting (thanks to whoward69 for the tip!). Or better, you can use xpcall to restore error reporting with your own traceback function that does not truncate file paths.
The rest of this post sort of follows step-by-step the things I did when I realized I could use pcall to restore error reporting for Lua called from GameEvents. It might help if you are trying to understand what's going on. However, jump to post #2 if you just want the complete fix.
If you run this
and there is a Lua error in MyFunction or anything called by it, then Lua stops and sends info back to whatever C++ called that chunk in the first place, which was probably either GameEvents or Events. "Hello world" is never printed. However, if you run this instead
and MyFunction (or something called by it) has an error, then Lua does not stop. Well ... it does stop what it was doing, but it returns to where it was outside of the pcall rather than all the way back to GameEvents or Events in the dll. The next statement after pcall runs, printing "Hello world", and then "false" and "[some error message]" are printed. The p in pcall is for "protected". What you are really doing is making MyFunction and anything called by it into a "chunk" and running it in "protected mode". If Lua encounters an error in that chunk then it returns control back to pcall (rather than the DLL) with two return values. The first return value from pcall is always true/false (did the function run without error?). If there was an error, then the second value is a string error message. If there was not an error, then the 2nd, 3rd, 4th,... return values correspond to the 1st, 2nd, 3rd,... values returned by MyFunction.
So you could implement your own fix like this:
Original:
Change to:
You can list any number of args for OnEveryPlayerTurn after the function name itself. And be careful not to do this!: pcall(OnEveryPlayerTurn(), iPlayer). pcall doesn't want the result of your function; it wants the function itself.
Or, if your mod code is organized like mine:
you can change it to:
As I said above, pcall() is "protecting" your Lua. If there is an error in PerTurnFunctionA, then it will be "contained" by the pcall; pcall will pass two results to print: false (because there was an error) and the error string. Then the next line will run normally regardless of an error in PerTurnFunctionA. In the original code block, an error in PerTurnFunctionA will crash (silently) the whole OnEveryPlayerTurn function so that B, C and D will not run.
To allow for return values, do this (I also localize the function here for better speed):
[Edit: Below is my first attempt at an Error Handler. There is a better (though harder to understand) one in post #2.]
--------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------
Slightly more advanced, but you can set up your own error handler like this:
This is a bit limiting because the function can only have 0 or 1 arg and 0 or 1 return value, and it won't give you a traceback. For a more generic error handler with traceback, move on to post #2...
The 60 character truncation has also been fixed, so error lines are now informative for file name.
Edit: If you want to jump straight to the Error Handler, go to post #2. This first post kind of shows how it developed and may help you understand how it works.
The Problems
Runtime Error reports used to work reliably when there was an error in your Lua code. Then they stopped (working reliably). I believe this happened with the vanilla pre-G&K patch 1.511 in June 2012, and it has always been the case for G&K. A more minor (but annoying and time-wasting) problem is that when you do get an error report, the error file path is truncated such that you can't use it. You still get a line number, but you have to guess the file. Sometimes that's easy, sometimes not (especially if you have >20 files).
I managed to track the first problem down to GameEvents here. Basically, any Lua errors downstream of a GameEvents call will crash silently without a Runtime Error report (even if it is many function calls downstream or goes through a LuaEvents call). However, errors downstream of an Events call (or a call from the Live Tuner) are reported correctly. So this is certainly a bug in the DLL somewhere. Based on whoward69's comments, it is not in the DLL we have access to. Fortunately, there is an all-Lua solution to this problem.
On the truncated file names, that sort of got fixed as a side effect of fixing problem 1 (in post #2).
Some Background
Your Lua code is always called by C++ from somewhere. To my knowledge, there are only 5 ways this can happen:
- GameEvents: For example, the line below is telling C++ to call MyFunction at the beginning of each turn for each player
Code:
GameEvents.PlayerDoTurn.Add(MyFunction)
- Events: Similar to above, but it's different C++ calling your function
- Registered UI events: these look like the code below and are also ways for C++ to call a Lua function[July 12, 2013 Edit: the button RegisterCallback is still broken. Not sure about others.]
Code:
Controls.DiscussButton:RegisterCallback( Mouse.eLClick, OnDiscuss ); ContextPtr:SetShowHideHandler( OnShowHide ); ContextPtr:SetInputHandler( InputHandler );
- InGameUIAddin: You probably added your mod's main Lua code this way and possibly other Lua code for driving various UI. The code in your mod that is "naked" -- i.e., outside of any function, like print("Running MyMod.lua") -- is called at some point during game init based on being added this way.
- the Live Tuner -- i.e., you type MyFunction() in the Live Tuner
OK. When C++ calls Lua, the function called and any functions called by it (and so on...) are called a "chunk". If there is an error anywhere in that chunk, Lua stops itself and sends some information back to C++. Lua errors never (to my knowledge) cause a Crash To Desktop -- that's something only C++ can do. Of course, Lua is calling C++ any time you call something like Game.GetHolyCityForReligion(ReligionType, PlayerID) and that C++ can certainly CTD (in this example, it will do so if the religion specified isn't founded yet). But back to my point, when Lua crashes it stops and sends info back to the C++ that called it. It is up to C++ to do something with that. In situations #2-5 above, that "something" is to print an error statement. In situation #1 above, that something is nothing. So no error report. (This is clearly a bug.)
The workaround
You could wait for Firaxis to fix the GameEvents bug. I doubt that they are even aware of it and I have no idea how to communicate with them. The truncated file path issue is not actually a bug at all -- it is the default behavior of the Lua library debug.traceback method. Firaxis could fix that too (by supplying their own traceback method) but I doubt that will ever happen.
In the meantime, you can use pcall in Lua to restore error reporting (thanks to whoward69 for the tip!). Or better, you can use xpcall to restore error reporting with your own traceback function that does not truncate file paths.
The rest of this post sort of follows step-by-step the things I did when I realized I could use pcall to restore error reporting for Lua called from GameEvents. It might help if you are trying to understand what's going on. However, jump to post #2 if you just want the complete fix.
If you run this
Code:
MyFunction(arg1, arg2)
print("Hello world")
Code:
value1, value2 = pcall(MyFunction, arg1, arg2)
print("Hello world")
print(value1)
print(value2)
So you could implement your own fix like this:
Original:
Code:
function OnEveryPlayerTurn(iPlayer)
--Your function code
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurn)
Code:
function OnEveryPlayerTurn(iPlayer)
--Your function code
end
function OnEveryPlayerTurnA(iPlayer)
print(pcall(OnEveryPlayerTurn, iPlayer))
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurnA)
Or, if your mod code is organized like mine:
Code:
function OnEveryPlayerTurn(iPlayer)
PerTurnFunctionA(iPlayer)
PerTurnFunctionB(iPlayer)
PerTurnFunctionC(iPlayer)
PerTurnFunctionD(iPlayer)
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurn)
you can change it to:
Code:
function OnEveryPlayerTurn(iPlayer)
print(pcall(PerTurnFunctionA, iPlayer))
print(pcall(PerTurnFunctionB, iPlayer))
print(pcall(PerTurnFunctionC, iPlayer))
print(pcall(PerTurnFunctionD, iPlayer))
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurn)
As I said above, pcall() is "protecting" your Lua. If there is an error in PerTurnFunctionA, then it will be "contained" by the pcall; pcall will pass two results to print: false (because there was an error) and the error string. Then the next line will run normally regardless of an error in PerTurnFunctionA. In the original code block, an error in PerTurnFunctionA will crash (silently) the whole OnEveryPlayerTurn function so that B, C and D will not run.
To allow for return values, do this (I also localize the function here for better speed):
Code:
function OnEveryPlayerTurn(iPlayer)
local PerTurnFunctionA = PerTurnFunctionA
local success, value1, value2, value3, value4 = pcall(PerTurnFunctionA, iPlayer)
--value1, value2, value3, value4 are the 1st through 4th return values you would have got from PerTurnFunctionA(iPlayer)
--value1 is the error string if PerTurnFunctionA fails
if not success then print(value1) end
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurn)
[Edit: Below is my first attempt at an Error Handler. There is a better (though harder to understand) one in post #2.]
--------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------
Slightly more advanced, but you can set up your own error handler like this:
Code:
function OnEveryPlayerTurn(iPlayer)
local HandleError1 = HandleError1
local PerTurnFunctionA = PerTurnFunctionA
local PerTurnFunctionB = PerTurnFunctionB
local PerTurnFunctionC = PerTurnFunctionC
local PerTurnFunctionD = PerTurnFunctionD
HandleError1(PerTurnFunctionA, iPlayer)
HandleError1(PerTurnFunctionB, iPlayer)
HandleError1(PerTurnFunctionC, iPlayer)
local value = HandleError1(PerTurnFunctionD, iPlayer)
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurn)
function HandleError1(f, arg1)
--f can have 0 or 1 arg and 0 or 1 return value
local success, value = pcall(f, arg1)
if success then
return value
else
print(value)
--popup code here to inform the player
end
end