Refactor error/exception/message handling

This adds:
- LuaCsSetup.ExceptionHandler so we can decide how we want to handle
exceptions for unit tests
- LuaCsSetup.MessageHandler so we can redirect logs to the XUnit output
  helper
This commit is contained in:
peelz
2022-08-06 18:10:54 -04:00
parent 3de2d8e550
commit dd1b404c9b
14 changed files with 184 additions and 190 deletions
@@ -25,6 +25,18 @@ namespace Barotrauma
public LuaCsSetupConfig() { }
}
internal delegate void LuaCsMessageLogger(string prefix, object o);
internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin);
internal enum LuaCsMessageOrigin
{
LuaCs,
Unknown,
LuaMod,
CSharpMod,
}
partial class LuaCsSetup
{
public const string LuaSetupFile = "Lua/LuaSetup.lua";
@@ -56,7 +68,7 @@ namespace Barotrauma
/// </summary>
public void RecreateCsScript()
{
GameMain.LuaCs.CsScript = new CsScriptRunner(GameMain.LuaCs.CsScript.setup);
CsScript = new CsScriptRunner(CsScript.setup);
lua.Globals["CsScript"] = CsScript;
}
@@ -77,7 +89,10 @@ namespace Barotrauma
public LuaCsSetup()
{
Hook = new LuaCsHook(this);
MessageLogger = DefaultMessageLogger;
ExceptionHandler = DefaultExceptionHandler;
Hook = new LuaCsHook(this);
ModStore = new LuaCsModStore();
Game = new LuaGame();
@@ -137,123 +152,141 @@ namespace Barotrauma
return null;
}
public enum ExceptionType
private void DefaultExceptionHandler(Exception ex, LuaCsMessageOrigin origin)
{
Lua,
CSharp,
Both
switch (ex)
{
case NetRuntimeException netRuntimeException:
if (netRuntimeException.DecoratedMessage == null)
{
PrintError(netRuntimeException, origin);
}
else
{
// FIXME: netRuntimeException.ToString() doesn't print the InnerException's stack trace...
PrintError($"{netRuntimeException.DecoratedMessage}: {netRuntimeException}", origin);
}
break;
case InterpreterException interpreterException:
if (interpreterException.DecoratedMessage == null)
{
PrintError(interpreterException, origin);
}
else
{
PrintError(interpreterException.DecoratedMessage, origin);
}
break;
default:
var msg = ex.StackTrace != null
? ex.ToString()
: $"{ex}\n{Environment.StackTrace}";
PrintError(msg, origin);
break;
}
}
public void HandleException(Exception ex, string extra = "", ExceptionType exceptionType = ExceptionType.Lua)
{
if (!string.IsNullOrWhiteSpace(extra))
if (exceptionType == ExceptionType.Lua) PrintError(extra);
else if (exceptionType == ExceptionType.CSharp) PrintCsError(extra);
else PrintBothError(extra);
if (ex is NetRuntimeException netRuntimeException)
{
if (netRuntimeException.DecoratedMessage == null)
{
PrintError(netRuntimeException);
}
else
{
PrintError(netRuntimeException.DecoratedMessage + ": " + netRuntimeException.ToString());
}
}
else if (ex is InterpreterException interpreterException)
{
if (interpreterException.DecoratedMessage == null)
{
PrintError(interpreterException);
}
else
{
PrintError(interpreterException.DecoratedMessage);
}
}
else
{
string msg = ex.StackTrace != null
? ex.ToString()
: $"{ex}\n{Environment.StackTrace}";
internal LuaCsExceptionHandler ExceptionHandler { get; set; }
if (exceptionType == ExceptionType.Lua) { PrintError(msg); }
else if (exceptionType == ExceptionType.CSharp) { PrintCsError(msg); }
else { PrintBothError(msg); }
}
}
private static void PrintErrorBase(string prefix, object message, string empty)
internal void HandleException(Exception ex, LuaCsMessageOrigin origin)
{
if (message == null) { message = empty; }
string str = message.ToString();
this.ExceptionHandler?.Invoke(ex, origin);
}
for (int i = 0; i < str.Length; i += 1024)
{
string subStr = str.Substring(i, Math.Min(1024, str.Length - i));
string errorMsg = subStr;
if (i == 0) errorMsg = prefix + errorMsg;
DebugConsole.ThrowError(errorMsg);
#if SERVER
if (GameMain.Server != null)
{
foreach (var c in GameMain.Server.ConnectedClients)
{
GameMain.Server.SendDirectChatMessage(ChatMessage.Create("", errorMsg, ChatMessageType.Console, null, textColor: Color.Red), c);
}
GameServer.Log(errorMsg, ServerLog.MessageType.Error);
}
#endif
}
}
#if SERVER
public void PrintError(object message) => PrintErrorBase("[SV LUA ERROR] ", message, "nil");
public static void PrintCsError(object message) => PrintErrorBase("[SV CS ERROR] ", message, "Null");
public static void PrintBothError(object message) => PrintErrorBase("[SV ERROR] ", message, "Null");
#else
public void PrintError(object message) => PrintErrorBase("[CL LUA ERROR] ", message, "nil");
public static void PrintCsError(object message) => PrintErrorBase("[CL CS ERROR] ", message, "Null");
public static void PrintBothError(object message) => PrintErrorBase("[CL ERROR] ", message, "Null");
#endif
private static void PrintMessageBase(string prefix, object message, string empty)
private static void PrintErrorBase(string prefix, object message, string empty)
{
if (message == null) { message = empty; }
string str = message.ToString();
message ??= empty;
var str = message.ToString();
for (int i = 0; i < str.Length; i += 1024)
{
string subStr = str.Substring(i, Math.Min(1024, str.Length - i));
for (int i = 0; i < str.Length; i += 1024)
{
var subStr = str.Substring(i, Math.Min(1024, str.Length - i));
var errorMsg = subStr;
if (i == 0) errorMsg = prefix + errorMsg;
DebugConsole.ThrowError(errorMsg);
#if SERVER
if (GameMain.Server != null)
{
foreach (var c in GameMain.Server.ConnectedClients)
{
GameMain.Server.SendDirectChatMessage(ChatMessage.Create("", subStr, ChatMessageType.Console, null, textColor: Color.MediumPurple), c);
}
if (GameMain.Server != null)
{
foreach (var c in GameMain.Server.ConnectedClients)
{
GameMain.Server.SendDirectChatMessage(ChatMessage.Create("", errorMsg, ChatMessageType.Console, null, textColor: Color.Red), c);
}
GameServer.Log(prefix + subStr, ServerLog.MessageType.ServerMessage);
}
GameServer.Log(errorMsg, ServerLog.MessageType.Error);
}
#endif
}
}
}
#if SERVER
DebugConsole.NewMessage(message.ToString(), Color.MediumPurple);
private const string LOG_PREFIX = "SV";
#else
DebugConsole.NewMessage(message.ToString(), Color.Purple);
private const string LOG_PREFIX = "CL";
#endif
}
private void PrintMessage(object message) => PrintMessageBase("[LUA] ", message, "nil");
public static void PrintCsMessage(object message) => PrintMessageBase("[CS] ", message, "Null");
public static void PrintLogMessage(object message) => PrintMessageBase("[LuaCs LOG] ", message, "Null");
// TODO: deprecate this (in an effort to get rid of as much global state as possible)
public void PrintError(object o, LuaCsMessageOrigin origin)
{
switch (origin)
{
case LuaCsMessageOrigin.LuaCs:
PrintGenericError(o);
break;
case LuaCsMessageOrigin.LuaMod:
PrintLuaError(o);
break;
case LuaCsMessageOrigin.CSharpMod:
PrintCsError(o);
break;
}
}
private static void PrintLuaError(object o) => PrintErrorBase($"[{LOG_PREFIX} LUA ERROR] ", o, "nil");
// TODO: deprecate this
// XXX: this is only public so that we don't break backward compat with C# mods
public static void PrintCsError(object o) => PrintErrorBase($"[{LOG_PREFIX} CS ERROR] ", o, "Null");
private static void PrintGenericError(object o) => PrintErrorBase($"[{LOG_PREFIX} ERROR] ", o, "Null");
internal LuaCsMessageLogger MessageLogger { get; set; }
private static void DefaultMessageLogger(string prefix, object o)
{
var message = o.ToString();
for (int i = 0; i < message.Length; i += 1024)
{
var subStr = message.Substring(i, Math.Min(1024, message.Length - i));
#if SERVER
if (GameMain.Server != null)
{
foreach (var c in GameMain.Server.ConnectedClients)
{
GameMain.Server.SendDirectChatMessage(ChatMessage.Create("", subStr, ChatMessageType.Console, null, textColor: Color.MediumPurple), c);
}
GameServer.Log(prefix + subStr, ServerLog.MessageType.ServerMessage);
}
#endif
}
#if SERVER
DebugConsole.NewMessage(message.ToString(), Color.MediumPurple);
#else
DebugConsole.NewMessage(message.ToString(), Color.Purple);
#endif
}
private void PrintMessageBase(string prefix, object message, string empty) => MessageLogger?.Invoke(prefix, message ?? empty);
internal void PrintMessage(object message) => PrintMessageBase("[LuaCs] ", message, "nil");
// TODO: deprecate this (in an effort to get rid of as much global state as possible)
public static void PrintCsMessage(object message) => GameMain.LuaCs.PrintMessage(message);
private DynValue DoFile(string file, Table globalContext = null, string codeStringFriendly = null)
{
@@ -295,7 +328,7 @@ namespace Barotrauma
}
catch (Exception e)
{
HandleException(e);
HandleException(e, LuaCsMessageOrigin.LuaMod);
}
return null;
}
@@ -410,7 +443,7 @@ namespace Barotrauma
UserData.RegisterType<LuaCsPerformanceCounter>();
UserData.RegisterType<IUserDataDescriptor>();
lua.Globals["printerror"] = (Action<object>)PrintError;
lua.Globals["printerror"] = (Action<object>)PrintLuaError;
lua.Globals["setmodulepaths"] = (Action<string[]>)SetModulePaths;
@@ -465,13 +498,13 @@ namespace Barotrauma
}
catch (Exception ex)
{
HandleException(ex, exceptionType: ExceptionType.CSharp);
HandleException(ex, LuaCsMessageOrigin.CSharpMod);
}
});
}
catch (Exception ex)
{
HandleException(ex, exceptionType: ExceptionType.CSharp);
HandleException(ex, LuaCsMessageOrigin.CSharpMod);
}
}
@@ -491,7 +524,7 @@ namespace Barotrauma
}
catch (Exception e)
{
HandleException(e);
HandleException(e, LuaCsMessageOrigin.LuaMod);
}
}
else if (luaPackage != null)
@@ -507,20 +540,15 @@ namespace Barotrauma
}
catch (Exception e)
{
HandleException(e);
HandleException(e, LuaCsMessageOrigin.LuaMod);
}
}
else
{
PrintError("LuaSetup.lua not found! Lua/LuaSetup.lua, no Lua scripts will be executed or work.");
PrintLuaError("LuaSetup.lua not found! Lua/LuaSetup.lua, no Lua scripts will be executed or work.");
}
executionNumber++;
}
}
}
}
}