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
+14 -2
View File
@@ -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);
@@ -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);
}
}
+29 -4
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;
}
}