mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 21:01:52 +00:00
Handle exceptions from C# Scripts + Log window + EditorLogger
+ Renamed Platform/DX11/ImGui.bf to .../Dx11ImGui.bf for clarity + ImGuiExtension: ImageButtonEx thata takes a TextureViewBinding + EditorLogger that logs to the LogWindow + Current logger can now be changed + Info, Trace, Warning and Error Icons
This commit is contained in:
@@ -0,0 +1,367 @@
|
||||
using System;
|
||||
using ImGui;
|
||||
using System.Collections;
|
||||
using GlitchyEngine.Core;
|
||||
using GlitchyEngine.World;
|
||||
using GlitchLog;
|
||||
using GlitchyEngine.Scripting;
|
||||
using GlitchyEngine.Renderer;
|
||||
|
||||
namespace GlitchyEditor.EditWindows;
|
||||
|
||||
enum MessageType
|
||||
{
|
||||
case None = 0;
|
||||
case Trace = 1;
|
||||
case Info = 2;
|
||||
case Warning = 4;
|
||||
case Error = 8;
|
||||
|
||||
public this(LogLevel level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case .Error:
|
||||
this = Error;
|
||||
case .Warning:
|
||||
this = Warning;
|
||||
case .Info:
|
||||
this = Info;
|
||||
case .Trace:
|
||||
this = Trace;
|
||||
default:
|
||||
this = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MessageSource
|
||||
{
|
||||
public UUID? Entity = null;
|
||||
public StringView? ScriptName = null;
|
||||
public int? Line = null;
|
||||
|
||||
/// If true, the message is only meant for engine developers... so only me :(
|
||||
public bool IsEngineMessage = false;
|
||||
|
||||
public MonoExceptionHelper Exception = null ~ _?.ReleaseRef();
|
||||
|
||||
public String AdditionalData = null ~ delete _;
|
||||
}
|
||||
|
||||
class LogMessage
|
||||
{
|
||||
private String _message ~ delete _;
|
||||
|
||||
public MessageType MessageType { get; private set; }
|
||||
|
||||
public DateTime Timestamp { get; private set; }
|
||||
|
||||
public MessageSource Source { get; private set; } ~ delete _;
|
||||
|
||||
public StringView Message => _message;
|
||||
|
||||
public this(DateTime timestamp, StringView message, MessageType logLevel, MessageSource ownSource)
|
||||
{
|
||||
Timestamp = timestamp;
|
||||
_message = new String(message);
|
||||
MessageType = logLevel;
|
||||
Source = ownSource;
|
||||
}
|
||||
}
|
||||
|
||||
class LogWindow : EditorWindow
|
||||
{
|
||||
public const String s_WindowTitle = "Log";
|
||||
|
||||
private append List<LogMessage> _messages = .() ~ ClearAndDeleteItems!(_);
|
||||
|
||||
public static SubTexture2D s_ErrorIcon;
|
||||
public static SubTexture2D s_WarningIcon;
|
||||
public static SubTexture2D s_InfoIcon;
|
||||
public static SubTexture2D s_TraceIcon;
|
||||
|
||||
private bool _showGameMessages = true;
|
||||
private bool _showEngineMessages = false;
|
||||
private bool _autoScroll = true;
|
||||
private bool _collapseMessages = true;
|
||||
|
||||
private MessageType _visibleMessageTypes = .Error | .Warning | .Info | .Trace;
|
||||
|
||||
protected override void InternalShow()
|
||||
{
|
||||
defer { ImGui.End(); }
|
||||
if(!ImGui.Begin(s_WindowTitle, &_open, .MenuBar))
|
||||
return;
|
||||
|
||||
ShowMenuBar();
|
||||
|
||||
ShowMessages();
|
||||
}
|
||||
|
||||
private void ShowMenuBar()
|
||||
{
|
||||
if(ImGui.BeginMenuBar())
|
||||
{
|
||||
if (ImGui.MenuItem("Clear"))
|
||||
{
|
||||
ClearLog();
|
||||
}
|
||||
|
||||
if (ImGui.BeginMenu("Filter"))
|
||||
{
|
||||
ImGui.Checkbox("Show game messages", &_showGameMessages);
|
||||
ImGui.AttachTooltip("If checked, the log will show messages generated by the game (e.g. scripts).");
|
||||
|
||||
ImGui.Checkbox("Show engine messages", &_showEngineMessages);
|
||||
ImGui.AttachTooltip("""
|
||||
If checked, the log will show messages generated by the engine.
|
||||
These messages are usually only necessary for engine debugging/development and don't provide practical information for game developers.
|
||||
""");
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
ImGui.Checkbox("Collapse", &_collapseMessages);
|
||||
ImGui.AttachTooltip("If checked, identical messages will be collapsed into one.");
|
||||
|
||||
var col = ImGui.GetStyleColorVec4(.Button);
|
||||
|
||||
if (_visibleMessageTypes.HasFlag(.Trace))
|
||||
ImGui.PushStyleColor(.Button, *col);
|
||||
else
|
||||
ImGui.PushStyleColor(.Button, .(0, 0, 0, 0));
|
||||
|
||||
if (ImGui.ImageButtonEx(1, s_TraceIcon, .(14, 14), .Zero, .Ones, .(2, 2)))
|
||||
_visibleMessageTypes ^= .Trace;
|
||||
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
if (_visibleMessageTypes.HasFlag(.Info))
|
||||
ImGui.PushStyleColor(.Button, *col);
|
||||
else
|
||||
ImGui.PushStyleColor(.Button, .(0, 0, 0, 0));
|
||||
|
||||
if (ImGui.ImageButtonEx(2, s_InfoIcon, .(16, 16)))
|
||||
_visibleMessageTypes ^= .Info;
|
||||
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
if (_visibleMessageTypes.HasFlag(.Warning))
|
||||
ImGui.PushStyleColor(.Button, *col);
|
||||
else
|
||||
ImGui.PushStyleColor(.Button, .(0, 0, 0, 0));
|
||||
|
||||
if (ImGui.ImageButtonEx(3, s_WarningIcon, .(16, 16)))
|
||||
_visibleMessageTypes ^= .Warning;
|
||||
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
if (_visibleMessageTypes.HasFlag(.Error))
|
||||
ImGui.PushStyleColor(.Button, *col);
|
||||
else
|
||||
ImGui.PushStyleColor(.Button, .(0, 0, 0, 0));
|
||||
|
||||
if (ImGui.ImageButtonEx(4, s_ErrorIcon, .(16, 16)))
|
||||
_visibleMessageTypes ^= .Error;
|
||||
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
ImGui.EndMenuBar();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowMessages()
|
||||
{
|
||||
if (ImGui.BeginTable("Messages", 3, .BordersInnerH | .SizingFixedFit))
|
||||
{
|
||||
ImGui.TableSetupColumn("", .WidthFixed);
|
||||
ImGui.TableSetupColumn("", .WidthStretch);
|
||||
ImGui.TableSetupColumn("", .WidthFixed);
|
||||
|
||||
//LogMessage lastMessage = null;
|
||||
int count = 1;
|
||||
|
||||
// Message ID for ImGui
|
||||
int imGuiMessageId = 0;
|
||||
|
||||
for (let message in _messages)
|
||||
{
|
||||
if (!_visibleMessageTypes.HasFlag(message.MessageType))
|
||||
continue;
|
||||
|
||||
if ((message.Source.IsEngineMessage && !_showEngineMessages) || (!message.Source.IsEngineMessage && !_showGameMessages))
|
||||
continue;
|
||||
|
||||
/*defer
|
||||
{
|
||||
lastMessage = message;
|
||||
}*/
|
||||
|
||||
do
|
||||
{
|
||||
LogMessage lastMessage = (message != _messages.Back) ? _messages[@message.Index + 1] : null;
|
||||
|
||||
if (_collapseMessages && lastMessage != null)
|
||||
{
|
||||
if (message.MessageType != lastMessage.MessageType)
|
||||
break;
|
||||
|
||||
if (message.Message != lastMessage.Message)
|
||||
break;
|
||||
|
||||
if (message.Source.Entity != lastMessage.Source.Entity)
|
||||
break;
|
||||
|
||||
// For exceptions the stack trace is basically the only relevant thing
|
||||
if (message.Source.Exception?.StackTrace != lastMessage.Source.Exception?.StackTrace)
|
||||
break;
|
||||
|
||||
// We collapse this message with the previous one:
|
||||
// Increment counter and go to next message.
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Push current index as ID
|
||||
ImGui.PushID((void*)++imGuiMessageId);
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableSetColumnIndex(0);
|
||||
|
||||
switch (message.MessageType)
|
||||
{
|
||||
case .Error:
|
||||
ImGui.Image(s_ErrorIcon, ImGui.Vec2(32, 32));
|
||||
ImGui.AttachTooltip("Error");
|
||||
case .Warning:
|
||||
ImGui.Image(s_WarningIcon, ImGui.Vec2(32, 32));
|
||||
ImGui.AttachTooltip("Warning");
|
||||
case .Info:
|
||||
ImGui.Image(s_InfoIcon, ImGui.Vec2(32, 32));
|
||||
ImGui.AttachTooltip("Info");
|
||||
case .Trace:
|
||||
ImGui.Image(s_TraceIcon, ImGui.Vec2(32, 32));
|
||||
ImGui.AttachTooltip("Trace");
|
||||
default:
|
||||
ImGui.TextUnformatted("Unknown");
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
// Timestamp
|
||||
ImGui.TextWrapped($"[{message.Timestamp:HH:mm:ss.fff}]");
|
||||
|
||||
if (message.Source.IsEngineMessage)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted("Engine");
|
||||
}
|
||||
|
||||
// Show entity
|
||||
if (message.Source?.Entity != null)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
|
||||
Result<Entity> entity = Editor.Instance.CurrentScene.GetEntityByID(message.Source.Entity.Value);
|
||||
|
||||
if (entity case .Ok(let e))
|
||||
{
|
||||
ImGui.Text($"Entity: \"{e.Name}\" (ID: {message.Source.Entity})");
|
||||
|
||||
if (ImGui.IsItemClicked())
|
||||
Editor.Instance.EntityHierarchyWindow.HighlightEntity(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text($"Entity: (ID: {message.Source.Entity})");
|
||||
}
|
||||
}
|
||||
|
||||
if (message.Source.Exception != null)
|
||||
{
|
||||
if (ImGui.CollapsingHeader(message.Message.Ptr))
|
||||
{
|
||||
// Show the native to managed entry point only if we show engine messages
|
||||
|
||||
if (_showEngineMessages)
|
||||
ImGui.TextUnformatted(message.Source.Exception.StackTrace);
|
||||
else
|
||||
ImGui.TextUnformatted(message.Source.Exception.CleanStackTrace);
|
||||
|
||||
ImGui.NewLine();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextUnformatted(message.Message);
|
||||
}
|
||||
|
||||
// Dont show the counter if we only have one message.
|
||||
if (count > 1)
|
||||
{
|
||||
// the message is not collapsible with the previous one.
|
||||
// Print message count for last message and reset counter.
|
||||
// This message will be printed normally.
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text($"{count}");
|
||||
}
|
||||
|
||||
count = 1;
|
||||
|
||||
ImGui.PopID();
|
||||
}
|
||||
|
||||
if (_autoScroll)
|
||||
{
|
||||
if (ImGui.GetIO().MouseWheel > 0)
|
||||
{
|
||||
_autoScroll = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.SetScrollY(ImGui.GetScrollMaxY());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui.GetScrollMaxY() == ImGui.GetScrollY())
|
||||
{
|
||||
_autoScroll = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearLog()
|
||||
{
|
||||
ClearAndDeleteItems!(_messages);
|
||||
}
|
||||
|
||||
public void Log(DateTime timestamp, LogLevel severity, StringView message, MessageSource source)
|
||||
{
|
||||
LogMessage logMessage = new LogMessage(timestamp, message, MessageType(severity), source);
|
||||
_messages.Add(logMessage);
|
||||
}
|
||||
|
||||
public void LogException(DateTime timestamp, MonoExceptionHelper exception)
|
||||
{
|
||||
StringView firstLine = exception.StackTrace;
|
||||
|
||||
int firstInIndex = exception.StackTrace.IndexOf("\n");
|
||||
|
||||
if (firstInIndex != -1)
|
||||
firstLine = firstLine.Substring(0, firstInIndex);
|
||||
|
||||
String message = scope .(128);
|
||||
message.AppendF($"Exception: \"{exception.FullName}\" | Message: \"{exception.Message}\" {firstLine}\0");
|
||||
|
||||
// TODO: are mono exceptions never engine only?
|
||||
LogMessage logMessage = new LogMessage(timestamp, message, .Error, new MessageSource(){Entity = exception.Instance, Exception = exception..AddRef(), IsEngineMessage = false});
|
||||
_messages.Add(logMessage);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user