mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13: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:
@@ -83,6 +83,22 @@ namespace Sandbox
|
||||
Log.Error("Camera not found.");
|
||||
}
|
||||
}
|
||||
Log.Warning("Achtung.");
|
||||
|
||||
try
|
||||
{
|
||||
SubVoid();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
void SubVoid()
|
||||
{
|
||||
throw new Exception("Ouha!", new IndexOutOfRangeException("Bist du jecke2?!", new AccessViolationException("Haleluja")));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -113,6 +129,9 @@ namespace Sandbox
|
||||
{
|
||||
force.Y += JumpForce;
|
||||
}
|
||||
|
||||
if (Input.IsKeyPressing(Key.N))
|
||||
SubVoid();
|
||||
|
||||
_rigidBody.ApplyForceToCenter(force);
|
||||
|
||||
|
||||
Binary file not shown.
@@ -9,8 +9,8 @@
|
||||
AddressModeU = .Clamp,
|
||||
AddressModeV = .Clamp,
|
||||
AddressModeW = .Clamp,
|
||||
MipMinLOD = -340282346638528859811704183484516925440,
|
||||
MipMaxLOD = 340282346638528859811704183484516925440,
|
||||
MipMinLOD = -3.40282347e+38,
|
||||
MipMaxLOD = 3.40282347e+38,
|
||||
MaxAnisotropy = 1,
|
||||
BorderColor = {
|
||||
R = 1,
|
||||
|
||||
Binary file not shown.
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ namespace GlitchyEditor
|
||||
private ContentBrowserWindow _contentBrowserWindow ~ delete _;
|
||||
private PropertiesWindow _propertiesWindow ~ delete _;
|
||||
private AssetViewer _assetViewer ~ delete _;
|
||||
private LogWindow _logWindow ~ delete _;
|
||||
|
||||
public Scene CurrentScene
|
||||
{
|
||||
@@ -45,6 +46,7 @@ namespace GlitchyEditor
|
||||
public ContentBrowserWindow ContentBrowserWindow => _contentBrowserWindow;
|
||||
public PropertiesWindow PropertiesWindow => _propertiesWindow;
|
||||
public AssetViewer AssetViewer => _assetViewer;
|
||||
public LogWindow LogWindow => _logWindow;
|
||||
|
||||
public EditorCamera* CurrentCamera { get; set; }
|
||||
|
||||
@@ -69,6 +71,11 @@ namespace GlitchyEditor
|
||||
InitWindows();
|
||||
}
|
||||
|
||||
public ~this()
|
||||
{
|
||||
s_Instance = null;
|
||||
}
|
||||
|
||||
private void InitWindows()
|
||||
{
|
||||
_sceneViewportWindow = new EditorViewportWindow(this);
|
||||
@@ -78,6 +85,7 @@ namespace GlitchyEditor
|
||||
_contentBrowserWindow = new ContentBrowserWindow((.)Application.Get().ContentManager);
|
||||
_propertiesWindow = new PropertiesWindow(this);
|
||||
_assetViewer = new AssetViewer((.)Application.Get().ContentManager);
|
||||
_logWindow = new LogWindow();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
@@ -89,6 +97,7 @@ namespace GlitchyEditor
|
||||
_contentBrowserWindow.Show();
|
||||
_propertiesWindow.Show();
|
||||
_assetViewer.Show();
|
||||
_logWindow.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace GlitchyEditor
|
||||
public this(String[] args)
|
||||
{
|
||||
PushLayer(new EditorLayer(args, _contentManager));
|
||||
|
||||
Log.ClientLogger = new EditorLogger();
|
||||
Log.EngineLogger = new EditorLogger() { IsEngineLogger = true };
|
||||
}
|
||||
|
||||
protected override IContentManager InitContentManager()
|
||||
|
||||
@@ -19,6 +19,10 @@ namespace GlitchyEditor
|
||||
public SubTexture2D Simulate ~ _.ReleaseRef();
|
||||
public SubTexture2D Pause ~ _.ReleaseRef();
|
||||
public SubTexture2D SingleStep ~ _.ReleaseRef();
|
||||
public SubTexture2D Error ~ _.ReleaseRef();
|
||||
public SubTexture2D Warning ~ _.ReleaseRef();
|
||||
public SubTexture2D Info ~ _.ReleaseRef();
|
||||
public SubTexture2D Trace ~ _.ReleaseRef();
|
||||
|
||||
public SamplerState SamplerState
|
||||
{
|
||||
@@ -41,6 +45,10 @@ namespace GlitchyEditor
|
||||
Simulate = GetNextGridTexture(ref pen, iconSize);
|
||||
Pause = GetNextGridTexture(ref pen, iconSize);
|
||||
SingleStep = GetNextGridTexture(ref pen, iconSize);
|
||||
Error = GetNextGridTexture(ref pen, iconSize);
|
||||
Warning = GetNextGridTexture(ref pen, iconSize);
|
||||
Info = GetNextGridTexture(ref pen, iconSize);
|
||||
Trace = GetNextGridTexture(ref pen, iconSize);
|
||||
}
|
||||
|
||||
private SubTexture2D GetNextGridTexture(ref float2 pen, float2 iconSize)
|
||||
|
||||
@@ -171,6 +171,11 @@ namespace GlitchyEditor
|
||||
|
||||
ContentBrowserWindow.s_FolderTexture = _editorIcons.Folder;
|
||||
ContentBrowserWindow.s_FileTexture = _editorIcons.File;
|
||||
|
||||
LogWindow.s_ErrorIcon = _editorIcons.Error;
|
||||
LogWindow.s_WarningIcon = _editorIcons.Warning;
|
||||
LogWindow.s_InfoIcon = _editorIcons.Info;
|
||||
LogWindow.s_TraceIcon = _editorIcons.Trace;
|
||||
}
|
||||
|
||||
private void InitEditor()
|
||||
@@ -807,6 +812,9 @@ namespace GlitchyEditor
|
||||
|
||||
if(ImGui.MenuItem(AssetViewer.s_WindowTitle))
|
||||
_editor.AssetViewer.Open = true;
|
||||
|
||||
if(ImGui.MenuItem(LogWindow.s_WindowTitle))
|
||||
_editor.LogWindow.Open = true;
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
using GlitchLog;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using GlitchyEngine.Scripting;
|
||||
|
||||
namespace GlitchyEditor;
|
||||
|
||||
public class EditorLogger : Logger
|
||||
{
|
||||
// {l} = log level (first parameter)
|
||||
// {t} = current date time (second parameter)
|
||||
// {n} = logger name (third parameter)
|
||||
// {m} = message
|
||||
|
||||
private String _name;
|
||||
|
||||
public override String Name
|
||||
{
|
||||
get => _name;
|
||||
set => _name = value;
|
||||
}
|
||||
|
||||
public bool IsEngineLogger {get; set;}
|
||||
|
||||
public this()
|
||||
{
|
||||
//Debug.Assert(Debug.IsDebuggerPresent, "The DebugLogger requires a debugger to be present.");
|
||||
}
|
||||
|
||||
#if GL_NOLOG || GL_LOG_NOTRACE
|
||||
[SkipCall]
|
||||
#endif
|
||||
[Inline]
|
||||
public override void Trace(StringView format, params Object[] args)
|
||||
{
|
||||
InternalLog(.Trace, format, params args);
|
||||
}
|
||||
|
||||
#if GL_NOLOG || GL_LOG_NOINFO
|
||||
[SkipCall]
|
||||
#endif
|
||||
[Inline]
|
||||
public override void Info(StringView format, params Object[] args)
|
||||
{
|
||||
InternalLog(.Info, format, params args);
|
||||
}
|
||||
|
||||
#if GL_NOLOG || GL_LOG_NOWARNING
|
||||
[SkipCall]
|
||||
#endif
|
||||
[Inline]
|
||||
public override void Warning(StringView format, params Object[] args)
|
||||
{
|
||||
InternalLog(.Warning, format, params args);
|
||||
}
|
||||
|
||||
#if GL_NOLOG || GL_LOG_NOERROR
|
||||
[SkipCall]
|
||||
#endif
|
||||
[Inline]
|
||||
public override void Error(StringView format, params Object[] args)
|
||||
{
|
||||
InternalLog(.Error, format, params args);
|
||||
}
|
||||
|
||||
#if GL_NOLOG || GL_LOG_NOCRITICAL
|
||||
[SkipCall]
|
||||
#endif
|
||||
[Inline]
|
||||
public override void Critical(StringView format, params Object[] args)
|
||||
{
|
||||
InternalLog(.Critical, format, params args);
|
||||
}
|
||||
|
||||
#if GL_NOLOG
|
||||
[SkipCall]
|
||||
#endif
|
||||
[Inline]
|
||||
public override void Log(LogLevel level, StringView format, params Object[] args)
|
||||
{
|
||||
InternalLog(level, format, params args);
|
||||
}
|
||||
|
||||
public override void Assert(bool condition, String error = Compiler.CallerExpression[0], String filePath = Compiler.CallerFilePath, int line = Compiler.CallerLineNum)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
String failStr = scope .()..AppendF("Assert failed: {} at line {} in {}", error, line, filePath);
|
||||
InternalLog(.Critical, failStr);
|
||||
Internal.FatalError(failStr, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public override void AssertDebug(bool condition, String error = Compiler.CallerExpression[0], String filePath = Compiler.CallerFilePath, int line = Compiler.CallerLineNum)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
String failStr = scope .()..AppendF("Assert failed: {} at line {} in {}", error, line, filePath);
|
||||
InternalLog(.Critical, failStr);
|
||||
Internal.FatalError(failStr, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void InternalLog(LogLevel level, StringView format, params Object[] args)
|
||||
{
|
||||
if(_logLevel > level)
|
||||
return;
|
||||
|
||||
DateTime timestamp = DateTime.Now;
|
||||
|
||||
String message = scope String(4096);
|
||||
message.AppendF(format, params args);
|
||||
|
||||
Debug.Write($"[{timestamp:HH:mm:ss.fff}] ({_name})|{level.UpperString}: {message}");
|
||||
|
||||
if (Editor.Instance == null)
|
||||
return;
|
||||
|
||||
if (args.Count > 0 && (var ex = args[^1] as MonoExceptionHelper))
|
||||
{
|
||||
Editor.Instance.LogWindow.LogException(timestamp, ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
Editor.Instance.LogWindow.Log(timestamp, level, message, new .() {IsEngineMessage = IsEngineLogger});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,9 +96,21 @@ namespace ImGui
|
||||
|
||||
return ImageButton(subTexture.Texture.GetViewBinding(), size, (.)subTexture.TexCoords.XY, (.)v, frame_padding, bg_col, tint_col);
|
||||
}
|
||||
|
||||
|
||||
public static extern bool ImageButton(TextureViewBinding textureViewBinding, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, int32 frame_padding = -1, Vec4 bg_col = Vec4.Zero, Vec4 tint_col = Vec4.Ones);
|
||||
|
||||
|
||||
public static bool ImageButtonEx(uint32 id, SubTexture2D subTexture, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, Vec2 frame_padding = .Zero, Vec4 bg_col = Vec4.Zero, Vec4 tint_col = Vec4.Ones)
|
||||
{
|
||||
if (uv0 != .Zero || uv1 != .Ones)
|
||||
Runtime.NotImplemented();
|
||||
|
||||
float2 v = (.)subTexture.TexCoords.XY + subTexture.TexCoords.ZW;
|
||||
|
||||
return ImageButtonEx(id, subTexture.Texture.GetViewBinding(), size, (.)subTexture.TexCoords.XY, (.)v, frame_padding, bg_col, tint_col);
|
||||
}
|
||||
|
||||
public static extern bool ImageButtonEx(uint32 id, TextureViewBinding textureViewBinding, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, Vec2 frame_padding = .Zero, Vec4 bg_col = Vec4.Zero, Vec4 tint_col = Vec4.Ones);
|
||||
|
||||
public static void TextUnformatted(StringView text) => TextUnformattedImpl(text.Ptr, text.Ptr + text.Length);
|
||||
|
||||
public static void PushID(StringView id) => PushID(id.Ptr, id.Ptr + id.Length);
|
||||
|
||||
+12
@@ -34,6 +34,18 @@ namespace ImGui
|
||||
return pressed;
|
||||
}
|
||||
|
||||
public static override bool ImageButtonEx(uint32 id, TextureViewBinding textureViewBinding, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, Vec2 frame_padding = .Zero, Vec4 bg_col = Vec4.Zero, Vec4 tint_col = Vec4.Ones)
|
||||
{
|
||||
var view = textureViewBinding._nativeShaderResourceView..AddRef();
|
||||
_resourceViews.Add(view);
|
||||
|
||||
bool pressed = ImGui.ImageButtonEx(id, view, size, uv0, uv1, frame_padding, bg_col, tint_col);
|
||||
|
||||
textureViewBinding.Release();
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
protected internal static override void CleanupFrame()
|
||||
{
|
||||
for(var view in _resourceViews)
|
||||
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using GlitchyEngine.Core;
|
||||
using Mono;
|
||||
|
||||
namespace GlitchyEngine.Scripting;
|
||||
|
||||
|
||||
public class MonoExceptionHelper : RefCounter
|
||||
{
|
||||
private String _fullName ~ delete _;
|
||||
|
||||
private String _message ~ delete _;
|
||||
|
||||
private String _stackTrace ~ delete _;
|
||||
/// The clean stack trace only contains the Managed Stack (the full trace contains one line for the native-to-managed entry)
|
||||
private StringView _cleanStackTrace;
|
||||
|
||||
private MonoExceptionHelper _innerException ~ _?.ReleaseRef();
|
||||
|
||||
public StringView FullName => _fullName;
|
||||
public StringView Message => _message;
|
||||
|
||||
public StringView StackTrace => _stackTrace;
|
||||
public StringView CleanStackTrace => _cleanStackTrace;
|
||||
|
||||
public MonoExceptionHelper InnerException => _innerException;
|
||||
|
||||
public UUID Instance { get; set; }
|
||||
|
||||
public this(MonoException* exception)
|
||||
{
|
||||
MonoObject* exObject = (MonoObject*)exception;
|
||||
|
||||
MonoClass* monoClass = Mono.mono_object_get_class(exObject);
|
||||
|
||||
StringView classNamespace = .(Mono.mono_class_get_namespace(monoClass));
|
||||
StringView className = .(Mono.mono_class_get_name(monoClass));
|
||||
_fullName = new $"{classNamespace}.{className}";
|
||||
|
||||
GetMessage(exObject, monoClass);
|
||||
|
||||
GetStackTrace(exception);
|
||||
|
||||
GetInnerException(exObject, monoClass);
|
||||
}
|
||||
|
||||
private void GetMessage(MonoObject* exceptionObject, MonoClass* monoClass)
|
||||
{
|
||||
var messageProperty = Mono.mono_class_get_property_from_name(monoClass, "Message");
|
||||
|
||||
MonoObject* message = Mono.mono_property_get_value(messageProperty, exceptionObject, null, null);
|
||||
char8* exMessage = Mono.mono_string_to_utf8((.)message);
|
||||
|
||||
_message = new String(exMessage);
|
||||
|
||||
Mono.mono_free(exMessage);
|
||||
}
|
||||
|
||||
private void GetStackTrace(MonoException* exception)
|
||||
{
|
||||
char8* stacktracePtr = Mono.mono_exception_get_managed_backtrace(exception);
|
||||
_stackTrace = new String(stacktracePtr);
|
||||
|
||||
int entryIndex = _stackTrace.IndexOf("at (wrapper native-to-managed)");
|
||||
|
||||
if (entryIndex != -1)
|
||||
_cleanStackTrace = _stackTrace.Substring(0, entryIndex);
|
||||
else
|
||||
_cleanStackTrace = _stackTrace;
|
||||
}
|
||||
|
||||
private void GetInnerException(MonoObject* exceptionObject, MonoClass* monoClass)
|
||||
{
|
||||
MonoProperty* innerExceptionProperty = Mono.mono_class_get_property_from_name(monoClass, "InnerException");
|
||||
|
||||
MonoObject* innerException = Mono.mono_property_get_value(innerExceptionProperty, exceptionObject, null, null);
|
||||
|
||||
if (innerException != null)
|
||||
_innerException = new MonoExceptionHelper((MonoException*)innerException);
|
||||
}
|
||||
}
|
||||
@@ -227,59 +227,40 @@ class ScriptClass : SharpClass
|
||||
_onDestroy = (OnDestroyMethod)GetMethodThunk("OnDestroy");
|
||||
}
|
||||
|
||||
public void OnCreate(MonoObject* instance)
|
||||
public void OnCreate(MonoObject* instance, out MonoException* exception)
|
||||
{
|
||||
MonoException* exception = null;
|
||||
exception = null;
|
||||
|
||||
if (_onCreate != null)
|
||||
_onCreate(instance, &exception);
|
||||
}
|
||||
|
||||
public void OnUpdate(MonoObject* instance, float deltaTime)
|
||||
public void OnUpdate(MonoObject* instance, float deltaTime, out MonoException* exception)
|
||||
{
|
||||
MonoException* exception = null;
|
||||
exception = null;
|
||||
|
||||
if (_onUpdate != null)
|
||||
_onUpdate(instance, deltaTime, &exception);
|
||||
|
||||
if (exception != null)
|
||||
{
|
||||
char8* str = Mono.mono_string_to_utf8(exception.Message);
|
||||
|
||||
Log.EngineLogger.Error($"Exception in \"{_fullName}.OnUpdate\". Message:\"{StringView(str)}\"");
|
||||
|
||||
Mono.mono_free(str);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDestroy(MonoObject* instance)
|
||||
public void OnDestroy(MonoObject* instance, out MonoException* exception)
|
||||
{
|
||||
MonoException* exception;
|
||||
exception = null;
|
||||
|
||||
if (_onDestroy != null)
|
||||
_onDestroy(instance, &exception);
|
||||
}
|
||||
|
||||
public MonoObject* CreateInstance(UUID uuid)
|
||||
public MonoObject* CreateInstance(UUID uuid, out MonoException* exception)
|
||||
{
|
||||
MonoObject* instance = Mono.mono_object_new(ScriptEngine.[Friend]s_AppDomain, _monoClass);
|
||||
|
||||
// TODO: I think this is a bit dirty
|
||||
// Invoke empty constructor to fill fields
|
||||
Mono.mono_runtime_object_init(instance);
|
||||
|
||||
// Invoke constructor with UUID
|
||||
#unwarn
|
||||
ScriptEngine.[Friend]s_EngineObject.Invoke(ScriptEngine.[Friend]s_EngineObject._constructor, instance, &uuid);
|
||||
|
||||
//MonoException* exception = null;
|
||||
//#unwarn
|
||||
//ScriptEngine.[Friend]s_EntityRoot._constructor(instance, uuid, &exception);
|
||||
//ScriptEngine.[Friend]s_EntityRoot.Invoke(_constructor, instance, &uuid);
|
||||
|
||||
/*MonoObject* exception = null;
|
||||
#unwarn*/
|
||||
//Mono.mono_runtime_invoke(_constructor, instance, (.)&uuid, &exception);
|
||||
//Mono.mono_runtime_object_init(instance);
|
||||
//MonoException* exception;
|
||||
//_constructor(instance, uuid, &exception);
|
||||
ScriptEngine.[Friend]s_EngineObject.Invoke(ScriptEngine.[Friend]s_EngineObject._constructor, instance, out exception, &uuid);
|
||||
|
||||
return instance;
|
||||
}
|
||||
@@ -312,12 +293,22 @@ class ScriptClass : SharpClass
|
||||
public MonoObject* Invoke(MonoMethod* method, MonoObject* instance, void** args = null)
|
||||
{
|
||||
MonoObject* exception = null;
|
||||
return Mono.mono_runtime_invoke(method, instance, args, &exception);
|
||||
|
||||
MonoObject* result = Mono.mono_runtime_invoke(method, instance, args, &exception);
|
||||
|
||||
if (exception != null)
|
||||
ScriptEngine.HandleMonoException((MonoException*)exception);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public MonoObject* Invoke(MonoMethod* method, MonoObject* instance, params void*[] args)
|
||||
public MonoObject* Invoke(MonoMethod* method, MonoObject* instance, out MonoException* exception, params void*[] args)
|
||||
{
|
||||
return Mono.mono_runtime_invoke(method, instance, args.Ptr, null);
|
||||
exception = null;
|
||||
|
||||
MonoObject* result = Mono.mono_runtime_invoke(method, instance, args.Ptr, (.)&exception);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public T Invoke<T>(MonoMethod* method, MonoObject* instance, params void*[] args)
|
||||
@@ -329,25 +320,17 @@ class ScriptClass : SharpClass
|
||||
public T GetFieldValue<T>(MonoObject* instance, MonoClassField* field)
|
||||
{
|
||||
T value = default;
|
||||
Mono.Mono.mono_field_get_value(instance, field, &value);
|
||||
Mono.mono_field_get_value(instance, field, &value);
|
||||
return value;
|
||||
}
|
||||
|
||||
public void SetFieldValue<T>(MonoObject* instance, MonoClassField* field, in T value)
|
||||
{
|
||||
/*if (typeof(T) == typeof(MonoObject*))
|
||||
{
|
||||
// TODO: MonoObject* is a pointer already, so we don't take the pointer
|
||||
Mono.Mono.mono_field_set_value(instance, field, (void*)value);
|
||||
}
|
||||
else
|
||||
{*/
|
||||
Mono.Mono.mono_field_set_value(instance, field, &value);
|
||||
//}
|
||||
Mono.mono_field_set_value(instance, field, &value);
|
||||
}
|
||||
|
||||
public void SetFieldValue<T>(MonoObject* instance, MonoClassField* field, in T value) where T : struct*
|
||||
{
|
||||
Mono.Mono.mono_field_set_value(instance, field, value);
|
||||
Mono.mono_field_set_value(instance, field, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,12 +209,14 @@ static class ScriptEngine
|
||||
if (scriptClass == null)
|
||||
return false;
|
||||
|
||||
script.Instance = new ScriptInstance(scriptClass);
|
||||
UUID entityId = entity.UUID;
|
||||
|
||||
script.Instance = new ScriptInstance(entityId, scriptClass);
|
||||
script.Instance..ReleaseRef();
|
||||
|
||||
_entityScriptInstances[entity.UUID] = script.Instance..AddRef();
|
||||
_entityScriptInstances[entityId] = script.Instance..AddRef();
|
||||
|
||||
script.Instance.Instantiate(entity.UUID);
|
||||
script.Instance.Instantiate(entityId);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -544,4 +546,27 @@ static class ScriptEngine
|
||||
|
||||
return scriptClass;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void HandleMonoException(MonoException* exception, ScriptInstance sourceInstance = null)
|
||||
{
|
||||
MonoExceptionHelper wrappedException = new MonoExceptionHelper(exception);
|
||||
|
||||
String entityInfo = scope .();
|
||||
|
||||
if (sourceInstance != null)
|
||||
{
|
||||
wrappedException.Instance = sourceInstance.EntityId;
|
||||
|
||||
Result<Entity> sourceEntity = Context.GetEntityByID(sourceInstance.EntityId);
|
||||
|
||||
if (sourceEntity case .Ok(let e))
|
||||
{
|
||||
entityInfo.AppendF($" ({e.Name} | {sourceInstance.EntityId})");
|
||||
}
|
||||
}
|
||||
|
||||
Log.ClientLogger.Error($"Mono Exception \"{wrappedException.FullName}\": \"{wrappedException.Message}\"{entityInfo}\nStackTrace:\n{wrappedException.StackTrace}", wrappedException);
|
||||
|
||||
wrappedException.ReleaseRef();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ using System;
|
||||
|
||||
namespace GlitchyEngine.Scripting;
|
||||
|
||||
using internal GlitchyEngine.Scripting;
|
||||
|
||||
class ScriptInstance : RefCounter
|
||||
{
|
||||
private ScriptClass _scriptClass;
|
||||
@@ -11,6 +13,8 @@ class ScriptInstance : RefCounter
|
||||
private MonoObject* _instance;
|
||||
private uint32 _gcHandle;
|
||||
|
||||
private UUID _entityId;
|
||||
|
||||
public ScriptClass ScriptClass => _scriptClass;
|
||||
|
||||
/// Gets whether or not the instance has ben initialized.
|
||||
@@ -23,9 +27,13 @@ class ScriptInstance : RefCounter
|
||||
|
||||
internal MonoObject* MonoInstance => _instance;
|
||||
|
||||
public this(ScriptClass scriptClass)
|
||||
public UUID EntityId => _entityId;
|
||||
|
||||
public this(UUID entityId, ScriptClass scriptClass)
|
||||
{
|
||||
Log.EngineLogger.AssertDebug(scriptClass != null);
|
||||
|
||||
_entityId = entityId;
|
||||
_scriptClass = scriptClass..AddRef();
|
||||
}
|
||||
|
||||
@@ -33,7 +41,7 @@ class ScriptInstance : RefCounter
|
||||
{
|
||||
if (_instance != null)
|
||||
{
|
||||
_scriptClass.OnDestroy(_instance);
|
||||
InvokeOnDestroy();
|
||||
Mono.mono_gchandle_free(_gcHandle);
|
||||
}
|
||||
_scriptClass?.ReleaseRef();
|
||||
@@ -41,24 +49,36 @@ class ScriptInstance : RefCounter
|
||||
|
||||
public void Instantiate(UUID uuid)
|
||||
{
|
||||
_instance = _scriptClass.CreateInstance(uuid);
|
||||
_instance = _scriptClass.CreateInstance(uuid, let exception);
|
||||
_gcHandle = Mono.mono_gchandle_new(_instance, true);
|
||||
|
||||
if (exception != null)
|
||||
ScriptEngine.HandleMonoException(exception, this);
|
||||
}
|
||||
|
||||
public void InvokeOnCreate()
|
||||
{
|
||||
_scriptClass.OnCreate(_instance);
|
||||
_scriptClass.OnCreate(_instance, let exception);
|
||||
_isCreated = true;
|
||||
|
||||
if (exception != null)
|
||||
ScriptEngine.HandleMonoException(exception, this);
|
||||
}
|
||||
|
||||
public void InvokeOnUpdate(float deltaTime)
|
||||
{
|
||||
_scriptClass.OnUpdate(_instance, deltaTime);
|
||||
_scriptClass.OnUpdate(_instance, deltaTime, let exception);
|
||||
|
||||
if (exception != null)
|
||||
ScriptEngine.HandleMonoException(exception, this);
|
||||
}
|
||||
|
||||
public void InvokeOnDestroy()
|
||||
{
|
||||
_scriptClass.OnDestroy(_instance);
|
||||
_scriptClass.OnDestroy(_instance, let exception);
|
||||
|
||||
if (exception != null)
|
||||
ScriptEngine.HandleMonoException(exception, this);
|
||||
}
|
||||
|
||||
public T GetFieldValue<T>(ScriptField field)
|
||||
@@ -83,6 +103,9 @@ class ScriptInstance : RefCounter
|
||||
|
||||
Mono.mono_property_set_value(entityProperty, componentInstance, (void**)&_instance, &exception);
|
||||
|
||||
if (exception != null)
|
||||
ScriptEngine.HandleMonoException((MonoException*)exception, this);
|
||||
|
||||
return componentInstance;
|
||||
}
|
||||
}
|
||||
@@ -232,12 +232,25 @@ static class Mono
|
||||
|
||||
[LinkName(.C)]
|
||||
public static extern MonoThread* mono_thread_current();
|
||||
|
||||
|
||||
#region Property
|
||||
|
||||
[LinkName(.C)]
|
||||
public static extern MonoProperty* mono_class_get_property_from_name(MonoClass *klass, char8* name);
|
||||
|
||||
[LinkName(.C)]
|
||||
public static extern void mono_property_set_value(MonoProperty *prop, void *obj, void **@params, MonoObject **exc);
|
||||
|
||||
[LinkName(.C)]
|
||||
public static extern MonoObject* mono_property_get_value(MonoProperty *prop, void *obj, void** @params, MonoObject** exc);
|
||||
|
||||
#endregion
|
||||
|
||||
[LinkName(.C)]
|
||||
public static extern char8* mono_exception_get_managed_backtrace(MonoException* exc);
|
||||
|
||||
[LinkName(.C)]
|
||||
public static extern MonoClass* mono_object_get_class(MonoObject* obj);
|
||||
}
|
||||
|
||||
struct MonoDomain;
|
||||
|
||||
Reference in New Issue
Block a user