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:
Simon Lübeß
2023-07-27 21:58:42 +02:00
parent 0bc517842f
commit 1254d01545
17 changed files with 750 additions and 59 deletions
@@ -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>
@@ -114,6 +130,9 @@ namespace Sandbox
force.Y += JumpForce;
}
if (Input.IsKeyPressing(Key.N))
SubVoid();
_rigidBody.ApplyForceToCenter(force);
if (Input.IsMouseButtonReleasing(MouseButton.MiddleButton))
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.
+367
View File
@@ -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);
}
}
+9
View File
@@ -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();
}
}
}
+3
View File
@@ -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()
+8
View File
@@ -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)
+8
View File
@@ -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()
@@ -808,6 +813,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();
}
+128
View File
@@ -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});
}
}
}
+12
View File
@@ -99,6 +99,18 @@ namespace ImGui
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);
@@ -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);
}
}
+27 -44
View File
@@ -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);
}
}
+28 -3
View File
@@ -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();
}
}
+29 -6
View File
@@ -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;
}
}
+13
View File
@@ -233,11 +233,24 @@ 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;